diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8f8b1c5425..90dbb52ba7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "features": { "ghcr.io/devcontainers/features/go:1": { - "version": "1.21" + "version": "1.22" }, "ghcr.io/devcontainers/features/node:1": { "version": "18" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a56b2ed11f..ddac45696f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: with: node_version: "20" buf_version: "latest" - go_version: "1.21" + go_version: "1.22" console: uses: ./.github/workflows/console.yml @@ -31,14 +31,14 @@ jobs: version: uses: ./.github/workflows/version.yml with: - semantic_version: "19.0.2" + semantic_version: "23.0.7" dry_run: true compile: needs: [core, console, version] uses: ./.github/workflows/compile.yml with: - go_version: "1.21" + go_version: "1.22" core_cache_key: ${{ needs.core.outputs.cache_key }} console_cache_key: ${{ needs.console.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} @@ -49,7 +49,7 @@ jobs: needs: core uses: ./.github/workflows/core-test.yml with: - go_version: "1.21" + go_version: "1.22" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} @@ -57,7 +57,7 @@ jobs: needs: [core, console] uses: ./.github/workflows/lint.yml with: - go_version: "1.21" + go_version: "1.22" node_version: "18" buf_version: "latest" go_lint_version: "v1.55.2" @@ -94,6 +94,6 @@ jobs: APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} with: build_image_name: ${{ needs.container.outputs.build_image }} - semantic_version: "19.0.2" + semantic_version: "23.0.7" image_name: "ghcr.io/zitadel/zitadel" google_image_name: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel" diff --git a/.github/workflows/core-test.yml b/.github/workflows/core-test.yml index a34fda5831..4a27c07404 100644 --- a/.github/workflows/core-test.yml +++ b/.github/workflows/core-test.yml @@ -76,7 +76,7 @@ jobs: run: make core_integration_test - name: publish coverage - uses: codecov/codecov-action@v4.1.0 + uses: codecov/codecov-action@v4.3.0 with: file: profile.cov name: core-integration-tests-postgres @@ -145,7 +145,7 @@ jobs: # run: make core_integration_test # - # name: publish coverage - # uses: codecov/codecov-action@v4.1.0 + # uses: codecov/codecov-action@v4.3.0 # with: # file: profile.cov # name: core-integration-tests-cockroach diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 1138d78e1e..5b1febf989 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: add issue - uses: actions/add-to-project@v0.6.1 + uses: actions/add-to-project@v1.0.1 if: ${{ github.event_name == 'issues' }} with: # You can target a repository in a different organization @@ -28,7 +28,7 @@ jobs: username: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }} - name: add pr - uses: actions/add-to-project@v0.6.1 + uses: actions/add-to-project@v1.0.1 if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}} with: # You can target a repository in a different organization diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 063623b3d8..73efd29cd0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,33 @@ jobs: semantic_version: ${{ inputs.semantic_version }} dry_run: false + # TODO: remove the publish job and publish releases directly with the @semantic-release/github plugin (remove draftRelease: true) + # as soon as it supports configuring the create release payload property make_latest to "legacy" + # https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release--parameters + publish: + runs-on: ubuntu-22.04 + needs: [ version ] + steps: + - id: get_release + uses: cardinalby/git-get-release-action@v1 + with: + commitSha: ${{ github.sha }} + draft: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish Release + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: ${{ steps.get_release.outputs.id }}, + draft: false, + make_latest: "legacy" + }); + docker: runs-on: ubuntu-22.04 needs: [ version ] diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index a3cd304658..cf11e944f8 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -43,6 +43,7 @@ jobs: semantic_version: ${{ inputs.semantic_version }} extra_plugins: | @semantic-release/exec@6.0.3 + @semantic-release/github@10.0.2 - name: output id: output diff --git a/.golangci.yaml b/.golangci.yaml index bc14721197..f480eb8c10 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -8,7 +8,7 @@ issues: run: concurrency: 4 timeout: 10m - go: '1.21' + go: '1.22' skip-dirs: - .artifacts - .backups diff --git a/.releaserc.js b/.releaserc.js index 6716a53f73..b5347a7504 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -9,6 +9,7 @@ module.exports = { [ "@semantic-release/github", { + draftRelease: true, assets: [ { path: ".artifacts/zitadel-linux-amd64/zitadel-linux-amd64.tar.gz", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfec7f20ca..6f28dbf042 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -154,7 +154,7 @@ ZITADEL uses [golangci-lint](https://golangci-lint.run) for code quality checks. The commands in this section are tested against the following software versions: - [Docker version 20.10.17](https://docs.docker.com/engine/install/) -- [Go version 1.21](https://go.dev/doc/install) +- [Go version 1.22](https://go.dev/doc/install) - [Delve 1.9.1](https://github.com/go-delve/delve/tree/v1.9.1/Documentation/installation) Make some changes to the source code, then run the database locally. diff --git a/Makefile b/Makefile index c2f0b029d6..8f9be14bc0 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,10 @@ core_integration_setup: core_integration_test: core_integration_setup go test -tags=integration -race -p 1 -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./... +.PHONY: core_integration_test_fast +core_integration_test_fast: core_integration_setup + go test -tags=integration -p 1 ./... + .PHONY: console_lint console_lint: cd console && \ diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 3c93e1d490..2437a4ef3d 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -428,7 +428,6 @@ SystemAPIUsers: SystemDefaults: SecretGenerators: - PasswordSaltCost: 14 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_PASSWORDSALTCOST MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE PasswordHasher: @@ -482,6 +481,13 @@ SystemDefaults: # - "md5" # - "scrypt" # - "pbkdf2" # verifier for all pbkdf2 hash modes. + SecretHasher: + # Set hasher configuration for machine users, API and OIDC client secrets. + # See PasswordHasher for all possible options + Hasher: + Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_SECRETHASHER_HASHER_ALGORITHM + Cost: 4 # ZITADEL_SYSTEMDEFAULTS_SECRETHASHER_HASHER_COST + Verifiers: Multifactors: OTP: # If this is empty, the issuer is the requested domain @@ -590,7 +596,6 @@ DefaultInstance: # date format: 2023-01-01T00:00:00Z ExpirationDate: # ZITADEL_DEFAULTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE SecretGenerators: - PasswordSaltCost: 14 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_PASSWORDSALTCOST ClientSecret: Length: 64 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_LENGTH IncludeLowerLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_INCLUDELOWERLETTERS @@ -705,7 +710,8 @@ DefaultInstance: ErrorMsgPopup: false # ZITADEL_DEFAULTINSTANCE_LABELPOLICY_ERRORMSGPOPUP DisableWatermark: false # ZITADEL_DEFAULTINSTANCE_LABELPOLICY_DISABLEWATERMARK LockoutPolicy: - MaxAttempts: 0 # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_MAXATTEMPTS + MaxPasswordAttempts: 0 # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_MAXPASSWORDATTEMPTS + MaxOTPAttempts: 0 # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_MAXOTPATTEMPTS ShouldShowLockoutFailure: true # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_SHOULDSHOWLOCKOUTFAILURE EmailTemplate: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhY2VGYW1pbHl9fSc7CiAgICAgIGZvbnQtc3R5bGU6IG5vcm1hbDsKICAgICAgZm9udC1kaXNwbGF5OiBzd2FwOwogICAgICBzcmM6IHVybCh7ey5Gb250VVJMfX0pOwogICAgfQogIDwvc3R5bGU+CiAge3tlbmR9fQoKPC9oZWFkPgo8Ym9keSBzdHlsZT0id29yZC1zcGFjaW5nOm5vcm1hbDsiPgoKCjxkaXYKICAgICAgICBzdHlsZT0iIgo+CgogIDx0YWJsZQogICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJhY2tncm91bmQ6e3suQmFja2dyb3VuZENvbG9yfX07YmFja2dyb3VuZC1jb2xvcjp7ey5CYWNrZ3JvdW5kQ29sb3J9fTt3aWR0aDoxMDAlO2JvcmRlci1yYWRpdXM6MTZweDsiCiAgPgogICAgPHRib2R5PgogICAgPHRyPgogICAgICA8dGQ+CgoKICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiIHdpZHRoPSI4MDAiID48dHI+PHRkIHN0eWxlPSJsaW5lLWhlaWdodDowcHg7Zm9udC1zaXplOjBweDttc28tbGluZS1oZWlnaHQtcnVsZTpleGFjdGx5OyI+PCFbZW5kaWZdLS0+CgoKICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO2JvcmRlci1yYWRpdXM6MTZweDttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICAgICAgICAgID4KICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MjBweCAwO3BhZGRpbmctbGVmdDowO3RleHQtYWxpZ246Y2VudGVyOyIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiB3aWR0aD0iODAwcHgiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+CgoKICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgICAgPGRpdiAgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItMTAwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjA7bGluZS1oZWlnaHQ6MDt0ZXh0LWFsaWduOmxlZnQ7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MTAwJTtkaXJlY3Rpb246bHRyOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiA+PHRyPjx0ZCBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjgwMHB4OyIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7cGFkZGluZzowOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5Mb2dvVVJMfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzo1MHB4IDAgMzBweCAwO3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO2JvcmRlci1zcGFjaW5nOjBweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9IndpZHRoOjE4MHB4OyI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGltZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQ9ImF1dG8iIHNyYz0ie3suTG9nb1VSTH19IiBzdHlsZT0iYm9yZGVyOjA7Ym9yZGVyLXJhZGl1czo4cHg7ZGlzcGxheTpibG9jaztvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7aGVpZ2h0OmF1dG87d2lkdGg6MTAwJTtmb250LXNpemU6MTNweDsiIHdpZHRoPSIxODAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3tlbmR9fQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CgoKICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PHRyPjx0ZCBjbGFzcz0iIiB3aWR0aD0iODAwcHgiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+CgoKICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgICAgPGRpdiAgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjQ4MHB4OyIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTYwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjBweDt0ZXh0LWFsaWduOmxlZnQ7ZGlyZWN0aW9uOmx0cjtkaXNwbGF5OmlubGluZS1ibG9jazt2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBzdHlsZT0iZm9udC1zaXplOjBweDtwYWRkaW5nOjEwcHggMjVweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJmb250LWZhbWlseTp7ey5Gb250RmFtaWx5fX07Zm9udC1zaXplOjI0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjE7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suRm9udENvbG9yfX07IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID57ey5HcmVldGluZ319PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTZweDtmb250LXdlaWdodDpsaWdodDtsaW5lLWhlaWdodDoxLjU7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suRm9udENvbG9yfX07IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID57ey5UZXh0fX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHZlcnRpY2FsLWFsaWduPSJtaWRkbGUiIGNsYXNzPSJzaGFkb3ciIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyLWNvbGxhcHNlOnNlcGFyYXRlO2xpbmUtaGVpZ2h0OjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYmdjb2xvcj0ie3suUHJpbWFyeUNvbG9yfX0iIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlcjpub25lO2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjphdXRvO21zby1wYWRkaW5nLWFsdDoxMHB4IDI1cHg7YmFja2dyb3VuZDp7ey5QcmltYXJ5Q29sb3J9fTsiIHZhbGlnbj0ibWlkZGxlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhyZWY9Int7LlVSTH19IiByZWw9Im5vb3BlbmVyIG5vcmVmZXJyZXIgbm90cmFjayIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTp7ey5Gb250RmFtaWx5fX07Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4K # ZITADEL_DEFAULTINSTANCE_EMAILTEMPLATE # Sets the default values for lifetime and expiration for OIDC in each newly created instance @@ -900,7 +906,7 @@ AuditLogRetention: 0s # ZITADEL_AUDITLOGRETENTION InternalAuthZ: # Configure the RolePermissionMappings by environment variable using JSON notation: - # ZITADEL_INTERNALAUTHZ_ROLEPERMISSIONMAPPINGS='[{"role": "IAM_OWNER", "permissions": ["iam.read", "iam.write"]}]' + # ZITADEL_INTERNALAUTHZ_ROLEPERMISSIONMAPPINGS='[{"role": "IAM_OWNER", "permissions": ["iam.write"]}, {"role": "ORG_OWNER", "permissions": ["org.write"]}]' # Beware that if you configure the RolePermissionMappings by environment variable, all the default RolePermissionMappings are lost. RolePermissionMappings: - Role: "SYSTEM_OWNER" diff --git a/cmd/initialise/init_test.go b/cmd/initialise/init_test.go index d549105e0f..f7b2a20a70 100644 --- a/cmd/initialise/init_test.go +++ b/cmd/initialise/init_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" + "github.com/zitadel/zitadel/internal/database" db_mock "github.com/zitadel/zitadel/internal/database/mock" ) diff --git a/cmd/setup/25.go b/cmd/setup/25.go index 1747981468..28444ef3a8 100644 --- a/cmd/setup/25.go +++ b/cmd/setup/25.go @@ -23,5 +23,5 @@ func (mig *User11AddLowerFieldsToVerifiedEmail) Execute(ctx context.Context, _ e } func (mig *User11AddLowerFieldsToVerifiedEmail) String() string { - return "25_user11_add_lower_fields_to_verified_email" + return "25_user12_add_lower_fields_to_verified_email" } diff --git a/cmd/setup/25.sql b/cmd/setup/25.sql index 870c04ea66..8450865bb2 100644 --- a/cmd/setup/25.sql +++ b/cmd/setup/25.sql @@ -1,2 +1,2 @@ -ALTER TABLE IF EXISTS projections.users11_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED; -CREATE INDEX IF NOT EXISTS users11_notifications_email_search ON projections.users11_notifications (instance_id, verified_email_lower); \ No newline at end of file +ALTER TABLE IF EXISTS projections.users12_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED; +CREATE INDEX IF NOT EXISTS users12_notifications_email_search ON projections.users12_notifications (instance_id, verified_email_lower); diff --git a/cmd/setup/config.go b/cmd/setup/config.go index 8c44f0b21a..f5547d21ca 100644 --- a/cmd/setup/config.go +++ b/cmd/setup/config.go @@ -69,6 +69,7 @@ func MustNewConfig(v *viper.Viper) *Config { hook.EnumHookFunc(authz.MemberTypeString), actions.HTTPConfigDecodeHook, hooks.MapTypeStringDecode[string, *authz.SystemAPIUser], + hooks.SliceTypeStringDecode[authz.RoleMapping], )), ) logging.OnError(err).Fatal("unable to read default config") diff --git a/cmd/start/config_test.go b/cmd/start/config_test.go index 9319e574df..90d4b9d2dc 100644 --- a/cmd/start/config_test.go +++ b/cmd/start/config_test.go @@ -223,6 +223,52 @@ Actions: Greeting: "bar", }}) }, + }, { + name: "roles ok", + args: args{yaml: ` +InternalAuthZ: + RolePermissionMappings: + - Role: IAM_OWNER + Permissions: + - iam.write + - Role: ORG_OWNER + Permissions: + - org.write + - org.read +Log: + Level: info +Actions: + HTTP: + DenyList: [] +`}, + want: func(t *testing.T, config *Config) { + assert.Equal(t, config.InternalAuthZ, authz.Config{ + RolePermissionMappings: []authz.RoleMapping{ + {Role: "IAM_OWNER", Permissions: []string{"iam.write"}}, + {Role: "ORG_OWNER", Permissions: []string{"org.write", "org.read"}}, + }, + }) + }, + }, { + name: "roles string ok", + args: args{yaml: ` +InternalAuthZ: + RolePermissionMappings: > + [{"role": "IAM_OWNER", "permissions": ["iam.write"]}, {"role": "ORG_OWNER", "permissions": ["org.write", "org.read"]}] +Log: + Level: info +Actions: + HTTP: + DenyList: [] +`}, + want: func(t *testing.T, config *Config) { + assert.Equal(t, config.InternalAuthZ, authz.Config{ + RolePermissionMappings: []authz.RoleMapping{ + {Role: "IAM_OWNER", Permissions: []string{"iam.write"}}, + {Role: "ORG_OWNER", Permissions: []string{"org.write", "org.read"}}, + }, + }) + }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/start/flags.go b/cmd/start/flags.go index 91d5b3c822..7e76177126 100644 --- a/cmd/start/flags.go +++ b/cmd/start/flags.go @@ -4,7 +4,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/cmd/key" diff --git a/cmd/start/start.go b/cmd/start/start.go index 991c7e816b..49de4d2073 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -34,9 +34,9 @@ import ( "github.com/zitadel/zitadel/internal/api" "github.com/zitadel/zitadel/internal/api/assets" internal_authz "github.com/zitadel/zitadel/internal/api/authz" + action_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/action/v3alpha" "github.com/zitadel/zitadel/internal/api/grpc/admin" "github.com/zitadel/zitadel/internal/api/grpc/auth" - execution_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/execution/v3alpha" "github.com/zitadel/zitadel/internal/api/grpc/feature/v2" "github.com/zitadel/zitadel/internal/api/grpc/management" oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2" @@ -408,7 +408,7 @@ func startAPIs( if err := apis.RegisterService(ctx, feature.CreateServer(commands, queries)); err != nil { return nil, err } - if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil { + if err := apis.RegisterService(ctx, action_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil { return nil, err } if err := apis.RegisterService(ctx, user_schema_v3_alpha.CreateServer(commands, queries)); err != nil { @@ -439,7 +439,7 @@ func startAPIs( } apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler) - oidcServer, err := oidc.NewServer(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog()) + oidcServer, err := oidc.NewServer(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog(), config.SystemDefaults.SecretHasher) if err != nil { return nil, fmt.Errorf("unable to start oidc provider: %w", err) } diff --git a/console/src/app/app.component.ts b/console/src/app/app.component.ts index 6f15b30a58..46e0168231 100644 --- a/console/src/app/app.component.ts +++ b/console/src/app/app.component.ts @@ -159,6 +159,8 @@ export class AppComponent implements OnDestroy { this.matIconRegistry.addSvgIcon('mdi_jwt', this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/jwt.svg')); + this.matIconRegistry.addSvgIcon('mdi_smtp', this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/mail.svg')); + this.matIconRegistry.addSvgIcon('mdi_symbol', this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/symbol.svg')); this.matIconRegistry.addSvgIcon( diff --git a/console/src/app/components/features/features.component.html b/console/src/app/components/features/features.component.html new file mode 100644 index 0000000000..801264fa83 --- /dev/null +++ b/console/src/app/components/features/features.component.html @@ -0,0 +1,347 @@ +
+
+

{{ 'DESCRIPTIONS.SETTINGS.FEATURES.TITLE' | translate }}

+ + info_outline + +
+

{{ 'DESCRIPTIONS.SETTINGS.FEATURES.DESCRIPTION' | translate }}

+ + + + + + +
+
+ {{ 'SETTING.FEATURES.LOGINDEFAULTORG' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.INHERITED' | translate }} + +
+
+
+
+ +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ + {{ + 'SETTING.FEATURES.LOGINDEFAULTORG_DESCRIPTION' | translate + }} +
+ +
+ {{ 'SETTING.FEATURES.OIDCLEGACYINTROSPECTION' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.INHERITED' | translate }} + +
+
+
+
+ +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ {{ + 'SETTING.FEATURES.OIDCLEGACYINTROSPECTION_DESCRIPTION' | translate + }} +
+ +
+ {{ 'SETTING.FEATURES.OIDCTOKENEXCHANGE' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.INHERITED' | translate }} + +
+
+
+
+ +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ {{ + 'SETTING.FEATURES.OIDCTOKENEXCHANGE_DESCRIPTION' | translate + }} +
+ +
+ {{ 'SETTING.FEATURES.OIDCTRIGGERINTROSPECTIONPROJECTIONS' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.INHERITED' | translate }} + +
+
+
+
+ +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ {{ + 'SETTING.FEATURES.OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION' | translate + }} +
+ +
+ {{ 'SETTING.FEATURES.USERSCHEMA' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.INHERITED' | translate }} + +
+
+
+
+ +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ {{ + 'SETTING.FEATURES.USERSCHEMA_DESCRIPTION' | translate + }} +
+ +
+ {{ 'SETTING.FEATURES.ACTIONS' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.INHERITED' | translate }} + +
+
+
+
+ +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ {{ 'SETTING.FEATURES.ACTIONS_DESCRIPTION' | translate }} +
+
+
+
+ + + + {{ 'SETTING.FEATURES.SOURCE.' + source | translate }} + + diff --git a/console/src/app/components/features/features.component.scss b/console/src/app/components/features/features.component.scss new file mode 100644 index 0000000000..841a4f48a9 --- /dev/null +++ b/console/src/app/components/features/features.component.scss @@ -0,0 +1,69 @@ +.feature-settings-wrapper { + .feature-title-row { + display: flex; + align-items: center; + + h1 { + margin: 0; + } + + a .icon { + font-size: 1.2rem; + height: 1.2rem; + line-height: 1.2rem; + } + } + + .features { + .feature-row { + display: flex; + flex-direction: column; + + .row { + display: flex; + align-items: center; + justify-content: space-between; + + .buttongroup { + margin-right: 0.5rem; + margin-top: 0.5rem; + + .toggle-row { + display: flex; + align-items: center; + + i { + margin-right: 0.5rem; + } + + .info-i { + font-size: 1.2rem; + margin-left: 0.5rem; + margin-right: 0; + } + + .current-dot { + height: 8px; + width: 8px; + border-radius: 50%; + // background-color: rgb(84, 142, 230); + margin-left: 0.5rem; + + &.enabled { + background-color: var(--success); + } + + &.disabled { + background-color: var(--warn); + } + } + } + } + } + + .feature-info { + margin-bottom: 1rem; + } + } + } +} diff --git a/console/src/app/components/features/features.component.spec.ts b/console/src/app/components/features/features.component.spec.ts new file mode 100644 index 0000000000..52616b1bf4 --- /dev/null +++ b/console/src/app/components/features/features.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { FeaturesComponent } from './features.component'; + +describe('FeaturesComponent', () => { + let component: FeaturesComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [FeaturesComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FeaturesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/components/features/features.component.ts b/console/src/app/components/features/features.component.ts new file mode 100644 index 0000000000..74750d9ccc --- /dev/null +++ b/console/src/app/components/features/features.component.ts @@ -0,0 +1,255 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnDestroy } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslateModule } from '@ngx-translate/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; +import { CardModule } from 'src/app/modules/card/card.module'; +import { DisplayJsonDialogComponent } from 'src/app/modules/display-json-dialog/display-json-dialog.component'; +import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module'; +import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; +import { Event } from 'src/app/proto/generated/zitadel/event_pb'; +import { Source } from 'src/app/proto/generated/zitadel/feature/v2beta/feature_pb'; +import { + GetInstanceFeaturesResponse, + SetInstanceFeaturesRequest, +} from 'src/app/proto/generated/zitadel/feature/v2beta/instance_pb'; +import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; +import { FeatureService } from 'src/app/services/feature.service'; +import { ToastService } from 'src/app/services/toast.service'; + +enum ToggleState { + ENABLED = 'ENABLED', + DISABLED = 'DISABLED', + INHERITED = 'INHERITED', +} + +type FeatureState = { source: Source; state: ToggleState }; +type ToggleStates = { + loginDefaultOrg?: FeatureState; + oidcTriggerIntrospectionProjections?: FeatureState; + oidcLegacyIntrospection?: FeatureState; + userSchema?: FeatureState; + oidcTokenExchange?: FeatureState; + actions?: FeatureState; +}; + +@Component({ + imports: [ + CommonModule, + FormsModule, + MatButtonToggleModule, + HasRolePipeModule, + MatIconModule, + CardModule, + TranslateModule, + MatButtonModule, + MatCheckboxModule, + InfoSectionModule, + MatTooltipModule, + HasRoleModule, + ], + standalone: true, + selector: 'cnsl-features', + templateUrl: './features.component.html', + styleUrls: ['./features.component.scss'], +}) +export class FeaturesComponent implements OnDestroy { + private destroy$: Subject = new Subject(); + + public _loading: BehaviorSubject = new BehaviorSubject(false); + public featureData: GetInstanceFeaturesResponse.AsObject | undefined = undefined; + + public toggleStates: ToggleStates | undefined = undefined; + public Source: any = Source; + public ToggleState: any = ToggleState; + + constructor( + private featureService: FeatureService, + private breadcrumbService: BreadcrumbService, + private toast: ToastService, + private dialog: MatDialog, + ) { + const breadcrumbs = [ + new Breadcrumb({ + type: BreadcrumbType.INSTANCE, + name: 'Instance', + routerLink: ['/instance'], + }), + ]; + this.breadcrumbService.setBreadcrumb(breadcrumbs); + + this.getFeatures(true); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public openDialog(event: Event): void { + this.dialog.open(DisplayJsonDialogComponent, { + data: { + event: event, + }, + width: '450px', + }); + } + + public validateAndSave() { + this.featureService.resetInstanceFeatures().then(() => { + const req = new SetInstanceFeaturesRequest(); + let changed = false; + + console.log(this.toggleStates); + + if (this.toggleStates?.loginDefaultOrg?.state !== ToggleState.INHERITED) { + req.setLoginDefaultOrg(this.toggleStates?.loginDefaultOrg?.state === ToggleState.ENABLED); + changed = true; + } + if (this.toggleStates?.oidcTriggerIntrospectionProjections?.state !== ToggleState.INHERITED) { + req.setOidcTriggerIntrospectionProjections( + this.toggleStates?.oidcTriggerIntrospectionProjections?.state === ToggleState.ENABLED, + ); + changed = true; + } + if (this.toggleStates?.oidcLegacyIntrospection?.state !== ToggleState.INHERITED) { + req.setOidcLegacyIntrospection(this.toggleStates?.oidcLegacyIntrospection?.state === ToggleState.ENABLED); + changed = true; + } + if (this.toggleStates?.userSchema?.state !== ToggleState.INHERITED) { + req.setUserSchema(this.toggleStates?.userSchema?.state === ToggleState.ENABLED); + changed = true; + } + if (this.toggleStates?.oidcTokenExchange?.state !== ToggleState.INHERITED) { + req.setOidcTokenExchange(this.toggleStates?.oidcTokenExchange?.state === ToggleState.ENABLED); + changed = true; + } + if (this.toggleStates?.actions?.state !== ToggleState.INHERITED) { + req.setActions(this.toggleStates?.actions?.state === ToggleState.ENABLED); + changed = true; + } + + if (changed) { + this.featureService + .setInstanceFeatures(req) + .then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + }); + } + + private getFeatures(inheritance: boolean) { + this.featureService.getInstanceFeatures(inheritance).then((instanceFeaturesResponse) => { + this.featureData = instanceFeaturesResponse.toObject(); + console.log(this.featureData); + + this.toggleStates = { + loginDefaultOrg: { + source: this.featureData.loginDefaultOrg?.source || Source.SOURCE_SYSTEM, + state: + this.featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM || + this.featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED + ? ToggleState.INHERITED + : !!this.featureData.loginDefaultOrg?.enabled + ? ToggleState.ENABLED + : ToggleState.DISABLED, + }, + oidcTriggerIntrospectionProjections: { + source: this.featureData.oidcTriggerIntrospectionProjections?.source || Source.SOURCE_SYSTEM, + state: + this.featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM || + this.featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED + ? ToggleState.INHERITED + : !!this.featureData.oidcTriggerIntrospectionProjections?.enabled + ? ToggleState.ENABLED + : ToggleState.DISABLED, + }, + oidcLegacyIntrospection: { + source: this.featureData.oidcLegacyIntrospection?.source || Source.SOURCE_SYSTEM, + state: + this.featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM || + this.featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED + ? ToggleState.INHERITED + : !!this.featureData.oidcLegacyIntrospection?.enabled + ? ToggleState.ENABLED + : ToggleState.DISABLED, + }, + userSchema: { + source: this.featureData.userSchema?.source || Source.SOURCE_SYSTEM, + state: + this.featureData.userSchema?.source === Source.SOURCE_SYSTEM || + this.featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED + ? ToggleState.INHERITED + : !!this.featureData.userSchema?.enabled + ? ToggleState.ENABLED + : ToggleState.DISABLED, + }, + oidcTokenExchange: { + source: this.featureData.oidcTokenExchange?.source || Source.SOURCE_SYSTEM, + state: + this.featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM || + this.featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED + ? ToggleState.INHERITED + : !!this.featureData.oidcTokenExchange?.enabled + ? ToggleState.ENABLED + : ToggleState.DISABLED, + }, + actions: { + source: Source.SOURCE_SYSTEM, + state: + this.featureData.actions?.source === Source.SOURCE_SYSTEM || + this.featureData.actions?.source === Source.SOURCE_UNSPECIFIED + ? ToggleState.INHERITED + : !!this.featureData.actions?.enabled + ? ToggleState.ENABLED + : ToggleState.DISABLED, + }, + }; + }); + } + + public resetSettings(): void { + this.featureService + .resetInstanceFeatures() + .then(() => { + this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true); + setTimeout(() => { + this.getFeatures(true); + }, 1000); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + + public saveFeatures(): void { + if (this.featureData) { + const req = new SetInstanceFeaturesRequest(); + req.setLoginDefaultOrg(!!this.featureData.loginDefaultOrg?.enabled); + req.setOidcLegacyIntrospection(!!this.featureData.oidcLegacyIntrospection?.enabled); + req.setOidcTokenExchange(!!this.featureData.oidcTokenExchange?.enabled); + req.setOidcTriggerIntrospectionProjections(!!this.featureData.oidcTriggerIntrospectionProjections?.enabled); + req.setUserSchema(!!this.featureData.userSchema?.enabled); + + this.featureService + .setInstanceFeatures(req) + .then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + } +} diff --git a/console/src/app/modules/filter-events/filter-events.component.scss b/console/src/app/modules/filter-events/filter-events.component.scss index 3d76435018..8dec65a475 100644 --- a/console/src/app/modules/filter-events/filter-events.component.scss +++ b/console/src/app/modules/filter-events/filter-events.component.scss @@ -20,7 +20,7 @@ flex-direction: column; padding: 0.5rem 0; min-width: 360px; - max-width: 360px; + max-width: 450px; padding-bottom: 0.5rem; position: relative; color: map-get($foreground, text); @@ -82,7 +82,7 @@ } .aggregate-type-select { - min-width: 100px; + min-width: 250px; margin-right: 0.5rem; } diff --git a/console/src/app/modules/idp-table/idp-table.component.ts b/console/src/app/modules/idp-table/idp-table.component.ts index 1bbb211b27..6a5c242ff7 100644 --- a/console/src/app/modules/idp-table/idp-table.component.ts +++ b/console/src/app/modules/idp-table/idp-table.component.ts @@ -36,7 +36,7 @@ import { ContextChangedWorkflowOverlays } from 'src/app/services/overlay/workflo import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'; import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; -import { ActivateIdpService } from '../../services/activate-idp.service'; +import { LoginPolicyService } from '../../services/login-policy.service'; import { first } from 'rxjs/operators'; @Component({ @@ -72,7 +72,7 @@ export class IdpTableComponent implements OnInit, OnDestroy { private toast: ToastService, private dialog: MatDialog, private router: Router, - private activateIdpSvc: ActivateIdpService, + private loginPolicySvc: LoginPolicyService, ) { this.selection.changed.subscribe(() => { this.changedSelection.emit(this.selection.selected); @@ -302,7 +302,7 @@ export class IdpTableComponent implements OnInit, OnDestroy { } public addIdp(idp: Provider.AsObject): Promise { - return firstValueFrom(this.activateIdpSvc.activateIdp(this.service, idp.id, idp.owner, this.loginPolicy)) + return firstValueFrom(this.loginPolicySvc.activateIdp(this.service, idp.id, idp.owner, this.loginPolicy)) .then(() => { this.toast.showInfo('IDP.TOAST.ADDED', true); setTimeout(() => { @@ -328,8 +328,8 @@ export class IdpTableComponent implements OnInit, OnDestroy { switch (this.serviceType) { case PolicyComponentServiceType.MGMT: if (this.isDefault) { - this.activateIdpSvc - .addLoginPolicy(this.service as ManagementService, this.loginPolicy) + this.loginPolicySvc + .createCustomLoginPolicy(this.service as ManagementService, this.loginPolicy) .then(() => { this.loginPolicy.isDefault = false; return (this.service as ManagementService) diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.ts b/console/src/app/modules/policies/login-policy/login-policy.component.ts index 1dd0c77311..b4e4557f00 100644 --- a/console/src/app/modules/policies/login-policy/login-policy.component.ts +++ b/console/src/app/modules/policies/login-policy/login-policy.component.ts @@ -26,6 +26,7 @@ import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { LoginMethodComponentType } from './factor-table/factor-table.component'; import { catchError, map, takeUntil } from 'rxjs/operators'; import { error } from 'console'; +import { LoginPolicyService } from '../../../services/login-policy.service'; const minValueValidator = (minValue: number) => (control: AbstractControl) => { const value = control.value; @@ -71,6 +72,7 @@ export class LoginPolicyComponent implements OnInit, OnDestroy { private fb: UntypedFormBuilder, private authService: GrpcAuthService, private dialog: MatDialog, + private loginPolicySvc: LoginPolicyService, ) {} ngOnDestroy(): void { @@ -172,45 +174,13 @@ export class LoginPolicyComponent implements OnInit, OnDestroy { } private async updateData(): Promise { - const calls: Observable[] = []; + const calls: Promise[] = []; if (this.loginData) { switch (this.serviceType) { case PolicyComponentServiceType.MGMT: if (this.isDefault) { - const mgmtreq = new AddCustomLoginPolicyRequest(); - mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp); - mgmtreq.setAllowRegister(this.loginData.allowRegister); - mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword); - mgmtreq.setForceMfa(this.loginData.forceMfa); - mgmtreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly); - mgmtreq.setPasswordlessType(this.loginData.passwordlessType); - mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset); - mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList); - mgmtreq.setSecondFactorsList(this.loginData.secondFactorsList); - mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail); - mgmtreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone); - - const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 0) * 60 * 60); - mgmtreq.setPasswordCheckLifetime(pcl); - - const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 0) * 60 * 60); - mgmtreq.setExternalLoginCheckLifetime(elcl); - - const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 0) * 60 * 60); - mgmtreq.setMfaInitSkipLifetime(misl); - - const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 0) * 60 * 60); - mgmtreq.setSecondFactorCheckLifetime(sfcl); - - const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 0) * 60 * 60); - mgmtreq.setMultiFactorCheckLifetime(mficl); - - mgmtreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery); - mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames); - mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri); - - calls.push(from((this.service as ManagementService).addCustomLoginPolicy(mgmtreq))); + calls.push(this.loginPolicySvc.createCustomLoginPolicy(this.service as ManagementService, this.loginData)); break; } else { const mgmtreq = new UpdateCustomLoginPolicyRequest(); @@ -243,7 +213,7 @@ export class LoginPolicyComponent implements OnInit, OnDestroy { mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames); mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri); - calls.push(from((this.service as ManagementService).updateCustomLoginPolicy(mgmtreq))); + calls.push((this.service as ManagementService).updateCustomLoginPolicy(mgmtreq)); break; } case PolicyComponentServiceType.ADMIN: @@ -276,21 +246,19 @@ export class LoginPolicyComponent implements OnInit, OnDestroy { adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames); adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri); - calls.push(from((this.service as AdminService).setRestrictions(!this.allowOrgRegistration))); - calls.push(from((this.service as AdminService).updateLoginPolicy(adminreq))); + calls.push((this.service as AdminService).setRestrictions(!this.allowOrgRegistration)); + calls.push((this.service as AdminService).updateLoginPolicy(adminreq)); break; } } else { - calls.push(from(Promise.reject())); + calls.push(Promise.reject()); } - return firstValueFrom( - forkJoin(calls).pipe( - catchError((error, caught) => { - // We just ignore the policy not changed error! - return (error as { message: string }).message.includes('INSTANCE-5M9vdd') ? of(true) : caught; - }), - ), - ); + return Promise.all(calls).catch((err) => { + if (err?.message?.includes('INSTANCE-5M9vdd')) { + return true; + } + throw err; + }); } public savePolicy(): void { diff --git a/console/src/app/modules/policies/login-texts/helper.ts b/console/src/app/modules/policies/login-texts/helper.ts index d7fd2ed457..3cd24ceb48 100644 --- a/console/src/app/modules/policies/login-texts/helper.ts +++ b/console/src/app/modules/policies/login-texts/helper.ts @@ -15,6 +15,7 @@ import { InitPasswordDoneScreenText, InitPasswordScreenText, LinkingUserDoneScreenText, + LinkingUserPromptScreenText, LoginScreenText, LogoutDoneScreenText, MFAProvidersText, @@ -375,5 +376,12 @@ export function mapRequestValues(map: Partial, req: Req): Req { r34.setUsernameLabel(map.externalRegistrationUserOverviewText?.usernameLabel ?? ''); req.setExternalRegistrationUserOverviewText(r34); + const r35 = new LinkingUserPromptScreenText(); + r35.setTitle(map.linkingUserPromptText?.title ?? ''); + r35.setDescription(map.linkingUserPromptText?.description ?? ''); + r35.setLinkButtonText(map.linkingUserPromptText?.linkButtonText ?? ''); + r35.setOtherButtonText(map.linkingUserPromptText?.otherButtonText ?? ''); + req.setLinkingUserPromptText(r35); + return req; } diff --git a/console/src/app/modules/policies/login-texts/login-texts.component.ts b/console/src/app/modules/policies/login-texts/login-texts.component.ts index b0a846cb65..695e757f52 100644 --- a/console/src/app/modules/policies/login-texts/login-texts.component.ts +++ b/console/src/app/modules/policies/login-texts/login-texts.component.ts @@ -41,6 +41,7 @@ const KeyNamesArray = [ 'initPasswordText', 'initializeDoneText', 'initializeUserText', + 'linkingUserPromptText', 'linkingUserDoneText', 'loginText', 'logoutText', diff --git a/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.spec.ts b/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.spec.ts new file mode 100644 index 0000000000..b2e99391d6 --- /dev/null +++ b/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider.component'; + +describe('PasswordDialogComponent', () => { + let component: DialogAddSMSProviderComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [DialogAddSMSProviderComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DialogAddSMSProviderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.ts b/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.ts index 6d9aa749fb..e766f6c360 100644 --- a/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.ts +++ b/console/src/app/modules/policies/notification-sms-provider/dialog-add-sms-provider/dialog-add-sms-provider.component.ts @@ -10,7 +10,7 @@ import { import { SMSProvider, TwilioConfig } from 'src/app/proto/generated/zitadel/settings_pb'; import { AdminService } from 'src/app/services/admin.service'; import { ToastService } from 'src/app/services/toast.service'; -import { PasswordDialogComponent } from '../password-dialog/password-dialog.component'; +import { PasswordDialogSMSProviderComponent } from '../password-dialog-sms-provider/password-dialog-sms-provider.component'; enum SMSProviderType { Twilio = 1, @@ -73,7 +73,7 @@ export class DialogAddSMSProviderComponent { } public changeToken(): void { - const dialogRef = this.dialog.open(PasswordDialogComponent, { + const dialogRef = this.dialog.open(PasswordDialogSMSProviderComponent, { width: '400px', data: { i18nTitle: 'SETTING.SMS.TWILIO.SETTOKEN', diff --git a/console/src/app/modules/policies/notification-sms-provider/notification-sms-provider.module.ts b/console/src/app/modules/policies/notification-sms-provider/notification-sms-provider.module.ts index 112fd3e6d1..f3bcf260ef 100644 --- a/console/src/app/modules/policies/notification-sms-provider/notification-sms-provider.module.ts +++ b/console/src/app/modules/policies/notification-sms-provider/notification-sms-provider.module.ts @@ -6,6 +6,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; + import { TranslateModule } from '@ngx-translate/core'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; @@ -16,10 +17,11 @@ import { InputModule } from '../../input/input.module'; import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module'; import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component'; import { NotificationSMSProviderComponent } from './notification-sms-provider.component'; +import { PasswordDialogSMSProviderComponent } from './password-dialog-sms-provider/password-dialog-sms-provider.component'; import { MatDialogModule } from '@angular/material/dialog'; @NgModule({ - declarations: [NotificationSMSProviderComponent, DialogAddSMSProviderComponent], + declarations: [NotificationSMSProviderComponent, DialogAddSMSProviderComponent, PasswordDialogSMSProviderComponent], imports: [ CommonModule, CardModule, @@ -34,9 +36,9 @@ import { MatDialogModule } from '@angular/material/dialog'; FormFieldModule, WarnDialogModule, MatSelectModule, - MatDialogModule, MatProgressSpinnerModule, MatSelectModule, + MatDialogModule, TranslateModule, ], exports: [NotificationSMSProviderComponent], diff --git a/console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.html b/console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.html similarity index 100% rename from console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.html rename to console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.html diff --git a/console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.scss b/console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.scss similarity index 100% rename from console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.scss rename to console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.scss diff --git a/console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.spec.ts b/console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.spec.ts similarity index 88% rename from console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.spec.ts rename to console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.spec.ts index 45f7f38a66..034bbe8de0 100644 --- a/console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.spec.ts +++ b/console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { PasswordDialogComponent } from './password-dialog.component'; +import { PasswordDialogComponent } from './password-dialog-sms-provider.component'; describe('PasswordDialogComponent', () => { let component: PasswordDialogComponent; diff --git a/console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.ts b/console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.ts similarity index 52% rename from console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.ts rename to console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.ts index 1f8cbafe4c..cbd2d30b02 100644 --- a/console/src/app/modules/policies/notification-sms-provider/password-dialog/password-dialog.component.ts +++ b/console/src/app/modules/policies/notification-sms-provider/password-dialog-sms-provider/password-dialog-sms-provider.component.ts @@ -2,14 +2,14 @@ import { Component, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ - selector: 'cnsl-password-dialog', - templateUrl: './password-dialog.component.html', - styleUrls: ['./password-dialog.component.scss'], + selector: 'cnsl-password-dialog-sms-provider', + templateUrl: './password-dialog-sms-provider.component.html', + styleUrls: ['./password-dialog-sms-provider.component.scss'], }) -export class PasswordDialogComponent { +export class PasswordDialogSMSProviderComponent { public password: string = ''; constructor( - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, ) {} diff --git a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.html b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.html index 7fb2a83630..5f86d23009 100644 --- a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.html +++ b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.html @@ -1,70 +1,37 @@ -

{{ 'DESCRIPTIONS.SETTINGS.SMTP_PROVIDER.TITLE' | translate }}

-

{{ 'DESCRIPTIONS.SETTINGS.SMTP_PROVIDER.DESCRIPTION' | translate }}

+

{{ 'SMTP.LIST.TITLE' | translate }}

+

{{ 'SMTP.LIST.DESCRIPTION' | translate }}

-
- +
+
-{{ 'SETTING.SMTP.REQUIREDWARN' | translate }} +

{{ 'SMTP.CREATE.TITLE' | translate }}

+

{{ 'SMTP.CREATE.DESCRIPTION' | translate }}

-
- - {{ 'SETTING.SMTP.SENDERADDRESS' | translate }} - - - - - {{ 'SETTING.SMTP.SENDERNAME' | translate }} - - - - - {{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }} - - - - - {{ 'SETTING.SMTP.TLS' | translate }} - - - - {{ 'SETTING.SMTP.HOSTANDPORT' | translate }} - - - - - {{ 'SETTING.SMTP.USER' | translate }} - - - - - - -
+
diff --git a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.scss b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.scss index aa056af7e5..0e97db7c0f 100644 --- a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.scss +++ b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.scss @@ -1,32 +1,113 @@ -.spinner-wr { - margin: 0.5rem 0; -} +@use '@angular/material' as mat; -.smtp-form-field, -.info-section-warn { - max-width: 400px; - display: block; -} +@mixin smtp-settings-theme($theme) { + $primary: map-get($theme, primary); + $primary-color: mat.get-color-from-palette($primary, 500); + $is-dark-theme: map-get($theme, is-dark); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); -.info-section-warn { - margin-bottom: 0.5rem; -} - -.smtp-checkbox { - max-width: 400px; - display: block; - margin: 1rem 0; -} - -.set-password-btn { - margin-bottom: 1rem; -} - -.general-btn-container { - display: flex; - justify-content: flex-start; - - .save-button { + .cnsl-smtp-table-wrapper { display: block; } + + .new-smtp-wrapper { + display: grid; + row-gap: 1.5rem; + column-gap: 1.5rem; + box-sizing: border-box; + width: 100%; + grid-template-columns: 1fr; + + @media only screen and (min-width: 700px) { + grid-template-columns: 1fr 1fr; + } + + @media only screen and (min-width: 1000px) { + grid-template-columns: 1fr 1fr 1fr; + } + + @media only screen and (min-width: 1300px) { + grid-template-columns: 1fr 1fr 1fr 1fr; + } + + .item { + position: relative; + z-index: 100; + display: flex; + text-decoration: none; + cursor: pointer; + padding-top: 1rem; + padding-right: 1rem; + padding-bottom: 1rem; + padding-left: 1rem; + border-radius: 0.5rem; + box-sizing: border-box; + transition: box-shadow 0.1s ease-in; + align-items: center; + color: map-get($foreground, text); + + .coming-soon-label { + position: absolute; + top: 0; + right: 1rem; + transform: translateY(-50%); + width: fit-content; + } + + &:hover { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12); + } + + .smtp-logo { + margin-right: 1rem; + height: 36px; + width: 36px; + + &.apple { + margin-bottom: 4px; + } + + &.dark { + display: if($is-dark-theme, block, none); + } + + &.light { + display: if($is-dark-theme, none, block); + } + } + + .smtp-icon { + margin-right: 1rem; + display: flex; + justify-content: center; + align-items: center; + height: 36px; + width: 36px; + + .icon { + font-size: 2.25rem; + height: 2.25rem; + width: 2.25rem; + } + } + + .text-container { + display: flex; + flex-direction: column; + + .title { + } + } + + &.coming-soon { + filter: grayscale(1); + cursor: not-allowed; + + &:hover { + box-shadow: none; + } + } + } + } } diff --git a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.spec.ts b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.spec.ts index 65d781de8b..5a773d4b1f 100644 --- a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.spec.ts +++ b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NotificationSMTPProviderComponent } from './notification-smtp-provider.component'; -describe('NotificationSMTPProviderComponent', () => { +describe('IdpSettingsComponent', () => { let component: NotificationSMTPProviderComponent; let fixture: ComponentFixture; diff --git a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.ts b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.ts index 8d65bc634a..6af684f0f1 100644 --- a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.ts +++ b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.ts @@ -1,22 +1,9 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { MatDialog } from '@angular/material/dialog'; -import { take } from 'rxjs'; -import { - AddSMTPConfigRequest, - AddSMTPConfigResponse, - UpdateSMTPConfigPasswordRequest, - UpdateSMTPConfigRequest, - UpdateSMTPConfigResponse, -} from 'src/app/proto/generated/zitadel/admin_pb'; +import { Component, Injector, Input, OnInit, Type } from '@angular/core'; import { AdminService } from 'src/app/services/admin.service'; -import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; -import { ToastService } from 'src/app/services/toast.service'; -import { requiredValidator } from '../../form-field/validators/validators'; +import { ManagementService } from 'src/app/services/mgmt.service'; -import { InfoSectionType } from '../../info-section/info-section.component'; -import { PasswordDialogComponent } from '../notification-sms-provider/password-dialog/password-dialog.component'; import { PolicyComponentServiceType } from '../policy-component-types.enum'; +import { SMTPKnownProviders } from '../../smtp-provider/known-smtp-providers-settings'; @Component({ selector: 'cnsl-notification-smtp-provider', @@ -25,151 +12,21 @@ import { PolicyComponentServiceType } from '../policy-component-types.enum'; }) export class NotificationSMTPProviderComponent implements OnInit { @Input() public serviceType!: PolicyComponentServiceType; + public service!: ManagementService | AdminService; - public smtpLoading: boolean = false; + public PolicyComponentServiceType: any = PolicyComponentServiceType; + public providers = SMTPKnownProviders; - public form!: UntypedFormGroup; - - public InfoSectionType: any = InfoSectionType; - - public hasSMTPConfig: boolean = false; - - // show available providers - - constructor( - private service: AdminService, - private dialog: MatDialog, - private toast: ToastService, - private fb: UntypedFormBuilder, - private authService: GrpcAuthService, - ) { - this.form = this.fb.group({ - senderAddress: [{ disabled: true, value: '' }, [requiredValidator]], - senderName: [{ disabled: true, value: '' }, [requiredValidator]], - replyToAddress: [{ disabled: true, value: '' }], - tls: [{ disabled: true, value: true }, [requiredValidator]], - hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]], - user: [{ disabled: true, value: '' }, [requiredValidator]], - }); - } + constructor(private injector: Injector) {} ngOnInit(): void { - this.fetchData(); - this.authService - .isAllowed(['iam.write']) - .pipe(take(1)) - .subscribe((allowed) => { - if (allowed) { - this.form.enable(); - } - }); - } - - private fetchData(): void { - this.smtpLoading = true; - this.service - .getSMTPConfig() - .then((smtpConfig) => { - this.smtpLoading = false; - if (smtpConfig.smtpConfig) { - this.hasSMTPConfig = true; - this.form.patchValue(smtpConfig.smtpConfig); - this.form.patchValue({ ['hostAndPort']: smtpConfig.smtpConfig.host }); - } - }) - .catch((error) => { - this.smtpLoading = false; - if (error && error.code === 5) { - console.log(error); - this.hasSMTPConfig = false; - } - }); - } - - private updateData(): Promise { - if (this.hasSMTPConfig) { - const req = new UpdateSMTPConfigRequest(); - req.setHost(this.hostAndPort?.value ?? ''); - req.setSenderAddress(this.senderAddress?.value ?? ''); - req.setSenderName(this.senderName?.value ?? ''); - req.setReplyToAddress(this.replyToAddress?.value ?? ''); - req.setTls(this.tls?.value ?? false); - req.setUser(this.user?.value ?? ''); - - return this.service.updateSMTPConfig(req); - } else { - const req = new AddSMTPConfigRequest(); - req.setHost(this.hostAndPort?.value ?? ''); - req.setSenderAddress(this.senderAddress?.value ?? ''); - req.setSenderName(this.senderName?.value ?? ''); - req.setReplyToAddress(this.replyToAddress?.value ?? ''); - req.setTls(this.tls?.value ?? false); - req.setUser(this.user?.value ?? ''); - - return this.service.addSMTPConfig(req); + switch (this.serviceType) { + case PolicyComponentServiceType.MGMT: + this.service = this.injector.get(ManagementService as Type); + break; + case PolicyComponentServiceType.ADMIN: + this.service = this.injector.get(AdminService as Type); + break; } } - - public savePolicy(): void { - this.updateData() - .then(() => { - this.toast.showInfo('SETTING.SMTP.SAVED', true); - setTimeout(() => { - this.fetchData(); - }, 2000); - }) - .catch((error: unknown) => { - this.toast.showError(error); - }); - } - - public setSMTPPassword(): void { - const dialogRef = this.dialog.open(PasswordDialogComponent, { - width: '400px', - data: { - i18nTitle: 'SETTING.SMTP.SETPASSWORD', - i18nLabel: 'SETTING.SMTP.PASSWORD', - }, - }); - - dialogRef.afterClosed().subscribe((password: string) => { - if (password) { - const passwordReq = new UpdateSMTPConfigPasswordRequest(); - passwordReq.setPassword(password); - - this.service - .updateSMTPConfigPassword(passwordReq) - .then(() => { - this.toast.showInfo('SETTING.SMTP.PASSWORDSET', true); - }) - .catch((error) => { - this.toast.showError(error); - }); - } - }); - } - - public get senderAddress(): AbstractControl | null { - return this.form.get('senderAddress'); - } - - public get senderName(): AbstractControl | null { - return this.form.get('senderName'); - } - - public get replyToAddress(): AbstractControl | null { - return this.form.get('replyToAddress'); - } - - public get tls(): AbstractControl | null { - return this.form.get('tls'); - } - - public get user(): AbstractControl | null { - return this.form.get('user'); - } - - public get hostAndPort(): AbstractControl | null { - return this.form.get('hostAndPort'); - } } diff --git a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.module.ts b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.module.ts index 91e68a4e66..4dbf109639 100644 --- a/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.module.ts +++ b/console/src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.module.ts @@ -1,43 +1,32 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatSelectModule } from '@angular/material/select'; +import { RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { CardModule } from '../../card/card.module'; -import { FormFieldModule } from '../../form-field/form-field.module'; -import { InfoSectionModule } from '../../info-section/info-section.module'; -import { InputModule } from '../../input/input.module'; -import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module'; -import { PasswordDialogComponent } from '../notification-sms-provider/password-dialog/password-dialog.component'; import { NotificationSMTPProviderComponent } from './notification-smtp-provider.component'; -import { MatDialogModule } from '@angular/material/dialog'; +import { InputModule } from '../../input/input.module'; +import { FormFieldModule } from '../../form-field/form-field.module'; +import { SMTPTableModule } from '../../smtp-table/smtp-table.module'; +import { MatButtonModule } from '@angular/material/button'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @NgModule({ - declarations: [NotificationSMTPProviderComponent, PasswordDialogComponent], + declarations: [NotificationSMTPProviderComponent], imports: [ - CommonModule, - CardModule, - InfoSectionModule, - FormsModule, - ReactiveFormsModule, - HasRolePipeModule, - MatButtonModule, - MatCheckboxModule, InputModule, - MatIconModule, FormFieldModule, - WarnDialogModule, - MatSelectModule, + CommonModule, + MatButtonModule, + CardModule, + MatIconModule, + SMTPTableModule, + RouterModule, + HasRolePipeModule, MatProgressSpinnerModule, - MatSelectModule, TranslateModule, - MatDialogModule, ], exports: [NotificationSMTPProviderComponent], }) diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html index 9a515d1890..6591d4613b 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html @@ -17,17 +17,49 @@
- {{ lockoutData.maxPasswordAttempts }} -
- {{ 'POLICY.DATA.MAXATTEMPTS' | translate }} + {{ 'POLICY.DATA.MAXPASSWORDATTEMPTS' | translate }} + +
+
+
+
+ + {{ lockoutData.maxOtpAttempts }} + +
+ +
+ {{ 'POLICY.DATA.MAXOTPATTEMPTS' | translate }}
diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts index ad4ff29b4f..e56857770d 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts @@ -91,24 +91,36 @@ export class PasswordLockoutPolicyComponent implements OnInit { } } - public incrementMaxAttempts(): void { + public incrementPasswordMaxAttempts(): void { if (this.lockoutData?.maxPasswordAttempts !== undefined) { this.lockoutData.maxPasswordAttempts++; } } - public decrementMaxAttempts(): void { + public decrementPasswordMaxAttempts(): void { if (this.lockoutData?.maxPasswordAttempts && this.lockoutData?.maxPasswordAttempts > 0) { this.lockoutData.maxPasswordAttempts--; } } + public incrementOTPMaxAttempts(): void { + if (this.lockoutData?.maxOtpAttempts !== undefined) { + this.lockoutData.maxOtpAttempts++; + } + } + + public decrementOTPMaxAttempts(): void { + if (this.lockoutData?.maxOtpAttempts && this.lockoutData?.maxOtpAttempts > 0) { + this.lockoutData.maxOtpAttempts--; + } + } + public savePolicy(): void { let promise: Promise; if (this.lockoutData) { if (this.service instanceof AdminService) { promise = this.service - .updateLockoutPolicy(this.lockoutData.maxPasswordAttempts) + .updateLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); this.fetchData(); @@ -119,7 +131,7 @@ export class PasswordLockoutPolicyComponent implements OnInit { } else { if ((this.lockoutData as LockoutPolicy.AsObject).isDefault) { promise = (this.service as ManagementService) - .addCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts) + .addCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); this.fetchData(); @@ -129,7 +141,7 @@ export class PasswordLockoutPolicyComponent implements OnInit { }); } else { promise = (this.service as ManagementService) - .updateCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts) + .updateCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); this.fetchData(); diff --git a/console/src/app/modules/provider-options/provider-options.component.html b/console/src/app/modules/provider-options/provider-options.component.html index 4991eee7ae..6dc77284e9 100644 --- a/console/src/app/modules/provider-options/provider-options.component.html +++ b/console/src/app/modules/provider-options/provider-options.component.html @@ -23,4 +23,12 @@ {{ 'IDP.OPTIONS.ISLINKINGALLOWED' | translate }}
+ +

{{ 'IDP.OPTIONS.AUTOLINKING_DESC' | translate }}

+ + + {{ 'IDP.OPTIONS.AUTOLINKINGTYPE.' + linkingType | translate }} + + +
diff --git a/console/src/app/modules/provider-options/provider-options.component.ts b/console/src/app/modules/provider-options/provider-options.component.ts index 63a75a1dc5..de0d512176 100644 --- a/console/src/app/modules/provider-options/provider-options.component.ts +++ b/console/src/app/modules/provider-options/provider-options.component.ts @@ -1,7 +1,8 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Subject, takeUntil } from 'rxjs'; -import { Options } from 'src/app/proto/generated/zitadel/idp_pb'; +import { Options, AutoLinkingOption } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AccessTokenType } from '../../proto/generated/zitadel/user_pb'; @Component({ selector: 'cnsl-provider-options', @@ -17,8 +18,15 @@ export class ProviderOptionsComponent implements OnChanges, OnDestroy { isAutoUpdate: new FormControl(false, []), isCreationAllowed: new FormControl(true, []), isLinkingAllowed: new FormControl(true, []), + autoLinking: new FormControl(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED, []), }); + public linkingTypes: AutoLinkingOption[] = [ + AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED, + AutoLinkingOption.AUTO_LINKING_OPTION_USERNAME, + AutoLinkingOption.AUTO_LINKING_OPTION_EMAIL, + ]; + constructor() { this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => { if (value) { @@ -27,6 +35,7 @@ export class ProviderOptionsComponent implements OnChanges, OnDestroy { opt.setIsAutoUpdate(value.isAutoUpdate); opt.setIsCreationAllowed(value.isCreationAllowed); opt.setIsLinkingAllowed(value.isLinkingAllowed); + opt.setAutoLinking(value.autoLinking); this.optionsChanged.emit(opt); } }); diff --git a/console/src/app/modules/provider-options/provider-options.module.ts b/console/src/app/modules/provider-options/provider-options.module.ts index 38d8578417..cef6ee8c0d 100644 --- a/console/src/app/modules/provider-options/provider-options.module.ts +++ b/console/src/app/modules/provider-options/provider-options.module.ts @@ -2,13 +2,22 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatSelectModule } from '@angular/material/select'; import { TranslateModule } from '@ngx-translate/core'; import { InfoSectionModule } from '../info-section/info-section.module'; import { ProviderOptionsComponent } from './provider-options.component'; @NgModule({ declarations: [ProviderOptionsComponent], - imports: [CommonModule, MatCheckboxModule, FormsModule, InfoSectionModule, ReactiveFormsModule, TranslateModule], + imports: [ + CommonModule, + MatCheckboxModule, + MatSelectModule, + FormsModule, + InfoSectionModule, + ReactiveFormsModule, + TranslateModule, + ], exports: [ProviderOptionsComponent], }) export class ProviderOptionsModule {} diff --git a/console/src/app/modules/providers/provider-apple/provider-apple.component.ts b/console/src/app/modules/providers/provider-apple/provider-apple.component.ts index 98c568b6aa..461ab90e34 100644 --- a/console/src/app/modules/providers/provider-apple/provider-apple.component.ts +++ b/console/src/app/modules/providers/provider-apple/provider-apple.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateAppleProviderRequest as AdminUpdateAppleProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddAppleProviderRequest as MgmtAddAppleProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -34,7 +34,10 @@ const MAX_ALLOWED_SIZE = 5 * 1024; }) export class ProviderAppleComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts b/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts index cec3a15f89..230d9152e4 100644 --- a/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts +++ b/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts @@ -10,7 +10,14 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateAzureADProviderRequest as AdminUpdateAzureADProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { AzureADTenant, AzureADTenantType, IDPOwnerType, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { + AutoLinkingOption, + AzureADTenant, + AzureADTenantType, + IDPOwnerType, + Options, + Provider, +} from 'src/app/proto/generated/zitadel/idp_pb'; import { AddAzureADProviderRequest as MgmtAddAzureADProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +39,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderAzureADComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts b/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts index 30d10fa17e..6b02c9707e 100644 --- a/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts +++ b/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitHubEnterpriseServerProviderRequest as AdminUpdateGitHubEnterpriseServerProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitHubEnterpriseServerProviderRequest as MgmtAddGitHubEnterpriseServerProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGithubESComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-github/provider-github.component.ts b/console/src/app/modules/providers/provider-github/provider-github.component.ts index a9c7bc5c50..eac57d498e 100644 --- a/console/src/app/modules/providers/provider-github/provider-github.component.ts +++ b/console/src/app/modules/providers/provider-github/provider-github.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitHubProviderRequest as AdminUpdateGithubProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitHubProviderRequest as MgmtAddGithubProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGithubComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts b/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts index 1adf214a2a..76f1f706db 100644 --- a/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts +++ b/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitLabSelfHostedProviderRequest as AdminUpdateGitLabSelfHostedProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitLabSelfHostedProviderRequest as MgmtAddGitLabSelfHostedProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGitlabSelfHostedComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts b/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts index 0e7b695ed6..23d76584b6 100644 --- a/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts +++ b/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitLabProviderRequest as AdminUpdateGitLabProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitLabProviderRequest as MgmtAddGitLabProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGitlabComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-google/provider-google.component.ts b/console/src/app/modules/providers/provider-google/provider-google.component.ts index 66a812053e..1759c4b976 100644 --- a/console/src/app/modules/providers/provider-google/provider-google.component.ts +++ b/console/src/app/modules/providers/provider-google/provider-google.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGoogleProviderRequest as AdminUpdateGoogleProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGoogleProviderRequest as MgmtAddGoogleProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGoogleComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts index 4ae12d7679..8045fe8a80 100644 --- a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts +++ b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts @@ -9,7 +9,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateJWTProviderRequest as AdminUpdateJWTProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddJWTProviderRequest as MgmtAddJWTProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderJWTComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts b/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts index 2a6e0e46e5..382763a5e6 100644 --- a/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts +++ b/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts @@ -9,7 +9,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateLDAPProviderRequest as AdminUpdateLDAPProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { LDAPAttributes, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, LDAPAttributes, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddLDAPProviderRequest as MgmtAddLDAPProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; export class ProviderLDAPComponent { public updateBindPassword: boolean = false; public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); public attributes: LDAPAttributes = new LDAPAttributes(); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-next/provider-next.service.ts b/console/src/app/modules/providers/provider-next/provider-next.service.ts index d2c268353a..245571e699 100644 --- a/console/src/app/modules/providers/provider-next/provider-next.service.ts +++ b/console/src/app/modules/providers/provider-next/provider-next.service.ts @@ -8,7 +8,7 @@ import { AdminService } from '../../../services/admin.service'; import { IDPOwnerType } from '../../../proto/generated/zitadel/idp_pb'; import { ToastService } from '../../../services/toast.service'; import { Data, ParamMap } from '@angular/router'; -import { ActivateIdpService } from '../../../services/activate-idp.service'; +import { LoginPolicyService } from '../../../services/login-policy.service'; import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum'; @Injectable({ @@ -18,7 +18,7 @@ export class ProviderNextService { constructor( private env: EnvironmentService, private toast: ToastService, - private addIdpSvc: ActivateIdpService, + private loginPolicySvc: LoginPolicyService, private injector: Injector, ) {} @@ -117,7 +117,7 @@ export class ProviderNextService { if (!id) { throw new Error('No ID'); } - return this.addIdpSvc.activateIdp( + return this.loginPolicySvc.activateIdp( service, id, service instanceof AdminService ? IDPOwnerType.IDP_OWNER_TYPE_SYSTEM : IDPOwnerType.IDP_OWNER_TYPE_ORG, diff --git a/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts b/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts index cb5a2c17a2..c2e6ee602e 100644 --- a/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts +++ b/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGenericOAuthProviderRequest as AdminUpdateGenericOAuthProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGenericOAuthProviderRequest as MgmtAddGenericOAuthProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderOAuthComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts b/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts index 7a791ab381..b3658034cc 100644 --- a/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts +++ b/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGenericOIDCProviderRequest as AdminUpdateGenericOIDCProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGenericOIDCProviderRequest as MgmtAddGenericOIDCProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderOIDCComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts index 27fca823f8..4ac09149e6 100644 --- a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts +++ b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts @@ -1,6 +1,6 @@ import { Component, Injector, Type } from '@angular/core'; import { Location } from '@angular/common'; -import { Options, Provider, SAMLBinding } from '../../../proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider, SAMLBinding } from '../../../proto/generated/zitadel/idp_pb'; import { AbstractControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum'; import { ManagementService } from '../../../services/mgmt.service'; @@ -37,7 +37,10 @@ export class ProviderSamlSpComponent { public provider?: Provider.AsObject; public form!: FormGroup; public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: assert service$ instead public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; // DEPRECATED: use service$ instead diff --git a/console/src/app/modules/settings-list/settings-list.component.html b/console/src/app/modules/settings-list/settings-list.component.html index 8f98d0b287..0038737b8d 100644 --- a/console/src/app/modules/settings-list/settings-list.component.html +++ b/console/src/app/modules/settings-list/settings-list.component.html @@ -12,6 +12,9 @@ + + + diff --git a/console/src/app/modules/settings-list/settings-list.module.ts b/console/src/app/modules/settings-list/settings-list.module.ts index f84ad42dd7..5538c49bef 100644 --- a/console/src/app/modules/settings-list/settings-list.module.ts +++ b/console/src/app/modules/settings-list/settings-list.module.ts @@ -14,7 +14,6 @@ import { LoginTextsPolicyModule } from '../policies/login-texts/login-texts.modu import { MessageTextsPolicyModule } from '../policies/message-texts/message-texts.module'; import { NotificationPolicyModule } from '../policies/notification-policy/notification-policy.module'; import { NotificationSMSProviderModule } from '../policies/notification-sms-provider/notification-sms-provider.module'; -import { NotificationSMTPProviderModule } from '../policies/notification-smtp-provider/notification-smtp-provider.module'; import { OIDCConfigurationModule } from '../policies/oidc-configuration/oidc-configuration.module'; import { PasswordComplexityPolicyModule } from '../policies/password-complexity-policy/password-complexity-policy.module'; import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy/password-lockout-policy.module'; @@ -27,8 +26,10 @@ import { SettingsListComponent } from './settings-list.component'; import FailedEventsModule from '../failed-events/failed-events.module'; import IamViewsModule from '../iam-views/iam-views.module'; import EventsModule from '../events/events.module'; -import OrgListModule from 'src/app/pages/org-list/org-list.module'; import { OrgTableModule } from '../org-table/org-table.module'; +import { NotificationSMTPProviderModule } from '../policies/notification-smtp-provider/notification-smtp-provider.module'; +import { FeaturesComponent } from 'src/app/components/features/features.component'; +import OrgListModule from 'src/app/pages/org-list/org-list.module'; @NgModule({ declarations: [SettingsListComponent], @@ -44,15 +45,18 @@ import { OrgTableModule } from '../org-table/org-table.module'; LanguageSettingsModule, NotificationPolicyModule, IdpSettingsModule, + NotificationSMTPProviderModule, PrivacyPolicyModule, MessageTextsPolicyModule, SecurityPolicyModule, DomainsModule, LoginTextsPolicyModule, OrgTableModule, + OrgListModule, DomainPolicyModule, TranslateModule, HasRolePipeModule, + FeaturesComponent, NotificationSMTPProviderModule, NotificationSMSProviderModule, OIDCConfigurationModule, diff --git a/console/src/app/modules/settings-list/settings.ts b/console/src/app/modules/settings-list/settings.ts index a5c07930f2..a945a8c7c7 100644 --- a/console/src/app/modules/settings-list/settings.ts +++ b/console/src/app/modules/settings-list/settings.ts @@ -10,6 +10,15 @@ export const ORGANIZATIONS: SidenavSetting = { }, }; +export const FEATURESETTINGS: SidenavSetting = { + id: 'features', + i18nKey: 'SETTINGS.LIST.FEATURESETTINGS', + groupI18nKey: 'SETTINGS.GROUPS.GENERAL', + requiredRoles: { + [PolicyComponentServiceType.ADMIN]: ['iam.restrictions.read'], + }, +}; + export const LANGUAGES: SidenavSetting = { id: 'languages', i18nKey: 'SETTINGS.LIST.LANGUAGES', diff --git a/console/src/app/modules/smtp-provider/known-smtp-providers-settings.ts b/console/src/app/modules/smtp-provider/known-smtp-providers-settings.ts new file mode 100644 index 0000000000..ec6087ea43 --- /dev/null +++ b/console/src/app/modules/smtp-provider/known-smtp-providers-settings.ts @@ -0,0 +1,174 @@ +export interface AmazonRegionsEndpoints { + 'US East (Ohio)': string; + 'US East (N. Virginia)': string; + 'US West (N. California)': string; + 'US West (Oregon)': string; + 'Asia Pacific (Mumbai)': string; + 'Asia Pacific (Osaka)': string; + 'Asia Pacific (Seoul)': string; + 'Asia Pacific (Singapore)': string; + 'Asia Pacific (Sydney)': string; + 'Asia Pacific (Tokyo)': string; + 'Canada (Central)': string; + 'Europe (Frankfurt)': string; + 'Europe (London)': string; + 'Europe (Paris)': string; + 'Europe (Stockholm)': string; + 'South America (São Paulo)': string; +} + +const amazonEndpoints = { + 'US East (Ohio)': 'email-smtp.us-east-2.amazonaws.com', + 'US East (N. Virginia)': 'email-smtp.us-east-1.amazonaws.com', + 'US West (N. California)': 'email-smtp.us-west-1.amazonaws.com', + 'US West (Oregon)': 'email-smtp.us-west-2.amazonaws.com', + 'Asia Pacific (Mumbai)': 'email-smtp.ap-south-1.amazonaws.com', + 'Asia Pacific (Osaka)': 'email-smtp.ap-northeast-3.amazonaws.com', + 'Asia Pacific (Seoul)': 'email-smtp.ap-northeast-2.amazonaws.com', + 'Asia Pacific (Singapore)': 'email-smtp.ap-southeast-1.amazonaws.com', + 'Asia Pacific (Sydney)': 'email-smtp.ap-southeast-2.amazonaws.com', + 'Asia Pacific (Tokyo)': 'email-smtp.ap-northeast-1.amazonaws.com', + 'Canada (Central)': 'email-smtp.ca-central-1.amazonaws.com', + 'Europe (Frankfurt)': 'email-smtp.eu-central-1.amazonaws.com', + 'Europe (Ireland)': 'email-smtp.eu-west-1.amazonaws.com', + 'Europe (London)': 'email-smtp.eu-west-2.amazonaws.com', + 'Europe (Paris)': 'email-smtp.eu-west-3.amazonaws.com', + 'Europe (Stockholm)': 'email-smtp.eu-north-1.amazonaws.com', + 'South America (São Paulo)': 'email-smtp.sa-east-1.amazonaws.com', +}; + +export interface ProviderDefaultSettings { + name: string; + regions?: AmazonRegionsEndpoints; + multiHostsLabel?: string; + requiredTls: boolean; + host?: string; + unencryptedPort?: number; + encryptedPort?: number; + user: { + value: string; + placeholder: string; + }; + password: { + value: string; + placeholder: string; + }; + image?: string; + routerLinkElement: string; +} + +export const AmazonSESDefaultSettings: ProviderDefaultSettings = { + name: 'amazon SES', + regions: amazonEndpoints, + multiHostsLabel: 'Choose your region', + requiredTls: true, + encryptedPort: 587, + user: { value: '', placeholder: 'your Amazon SES credentials for this region' }, + password: { value: '', placeholder: 'your Amazon SES credentials for this region' }, + image: './assets/images/smtp/aws-ses.svg', + routerLinkElement: 'aws-ses', +}; + +export const GoogleDefaultSettings: ProviderDefaultSettings = { + name: 'google', + requiredTls: true, + host: 'smtp.gmail.com', + unencryptedPort: 587, + encryptedPort: 587, + user: { value: '', placeholder: 'your complete Google Workspace email address' }, + password: { value: '', placeholder: 'your complete Google Workspace password' }, + image: './assets/images/smtp/google.png', + routerLinkElement: 'google', +}; + +export const MailgunDefaultSettings: ProviderDefaultSettings = { + name: 'mailgun', + requiredTls: false, + host: 'smtp.mailgun.org', + unencryptedPort: 587, + encryptedPort: 465, + user: { value: '', placeholder: 'postmaster@YOURDOMAIN' }, + password: { value: '', placeholder: 'Your mailgun smtp password' }, + image: './assets/images/smtp/mailgun.svg', + routerLinkElement: 'mailgun', +}; + +export const MailjetDefaultSettings: ProviderDefaultSettings = { + name: 'mailjet', + requiredTls: false, + host: 'in-v3.mailjet.com', + unencryptedPort: 587, + encryptedPort: 465, + user: { value: '', placeholder: 'Your Mailjet API key' }, + password: { value: '', placeholder: 'Your Mailjet Secret key' }, + image: './assets/images/smtp/mailjet.svg', + routerLinkElement: 'mailjet', +}; + +export const PostmarkDefaultSettings: ProviderDefaultSettings = { + name: 'postmark', + requiredTls: false, + host: 'smtp.postmarkapp.com', + unencryptedPort: 587, + encryptedPort: 587, + user: { value: '', placeholder: 'Your Server API token' }, + password: { value: '', placeholder: 'Your Server API token' }, + image: './assets/images/smtp/postmark.png', + routerLinkElement: 'postmark', +}; + +export const SendgridDefaultSettings: ProviderDefaultSettings = { + name: 'sendgrid', + requiredTls: false, + host: 'smtp.sendgrid.net', + unencryptedPort: 587, + encryptedPort: 465, + user: { value: 'apikey', placeholder: '' }, + password: { value: '', placeholder: ' Your SendGrid API Key' }, + image: './assets/images/smtp/sendgrid.png', + routerLinkElement: 'sendgrid', +}; + +export const MailchimpDefaultSettings: ProviderDefaultSettings = { + name: 'mailchimp', + requiredTls: false, + host: 'smtp.mandrillapp.com', + unencryptedPort: 587, + encryptedPort: 465, + user: { value: '', placeholder: 'Your Mailchimp primary contact email' }, + password: { value: '', placeholder: 'Your Mailchimp Transactional API key' }, + image: './assets/images/smtp/mailchimp.svg', + routerLinkElement: 'mailchimp', +}; + +export const BrevoDefaultSettings: ProviderDefaultSettings = { + name: 'brevo', + requiredTls: false, + host: 'smtp-relay.sendinblue.com', + unencryptedPort: 587, + encryptedPort: 465, + user: { value: '', placeholder: 'Your SMTP login email address' }, + password: { value: '', placeholder: 'Your SMTP key' }, + image: './assets/images/smtp/brevo.svg', + routerLinkElement: 'brevo', +}; + +export const GenericDefaultSettings: ProviderDefaultSettings = { + name: 'generic', + requiredTls: false, + user: { value: '', placeholder: 'your SMTP user' }, + password: { value: '', placeholder: 'your SMTP password' }, + routerLinkElement: 'generic', +}; + +export const SMTPKnownProviders = [ + AmazonSESDefaultSettings, + BrevoDefaultSettings, + // GoogleDefaultSettings, + MailgunDefaultSettings, + MailchimpDefaultSettings, + MailjetDefaultSettings, + PostmarkDefaultSettings, + SendgridDefaultSettings, + GenericDefaultSettings, +]; diff --git a/console/src/app/modules/smtp-provider/smtp-provider-routing.module.ts b/console/src/app/modules/smtp-provider/smtp-provider-routing.module.ts new file mode 100644 index 0000000000..849be82a45 --- /dev/null +++ b/console/src/app/modules/smtp-provider/smtp-provider-routing.module.ts @@ -0,0 +1,39 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { SMTPProviderComponent } from './smtp-provider.component'; + +const types = [ + { path: 'aws-ses', component: SMTPProviderComponent }, + { path: 'generic', component: SMTPProviderComponent }, + { path: 'google', component: SMTPProviderComponent }, + { path: 'mailgun', component: SMTPProviderComponent }, + { path: 'postmark', component: SMTPProviderComponent }, + { path: 'sendgrid', component: SMTPProviderComponent }, + { path: 'mailjet', component: SMTPProviderComponent }, + { path: 'mailchimp', component: SMTPProviderComponent }, + { path: 'brevo', component: SMTPProviderComponent }, +]; + +const routes: Routes = types.map((value) => { + return { + path: value.path, + children: [ + { + path: 'create', + component: value.component, + }, + ], + }; +}); + +routes.push({ + path: ':id', + component: SMTPProviderComponent, +}); + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SMTPProvidersRoutingModule {} diff --git a/console/src/app/modules/smtp-provider/smtp-provider.component.html b/console/src/app/modules/smtp-provider/smtp-provider.component.html new file mode 100644 index 0000000000..c23b19f66e --- /dev/null +++ b/console/src/app/modules/smtp-provider/smtp-provider.component.html @@ -0,0 +1,144 @@ + +
+ +
+ +
+

+ {{ + !id + ? ('SMTP.CREATE.STEPS.CREATE_DESC_TITLE' | translate: { value: providerDefaultSetting.name | titlecase }) + : ('SMTP.CREATE.STEPS.CURRENT_DESC_TITLE' | translate) + }} +

+
+ + + + + {{ 'SMTP.CREATE.STEPS.PROVIDER_SETTINGS' | translate }} +
+ + {{ 'SETTING.SMTP.TLS' | translate }} + + + + {{ providerDefaultSetting.multiHostsLabel }} + + + {{ region.key }} + + + + + + {{ 'SETTING.SMTP.DESCRIPTION' | translate }} + + + + + {{ 'SETTING.SMTP.HOSTANDPORT' | translate }} + + + + + {{ 'SETTING.SMTP.USER' | translate }} + + + + + {{ 'SETTING.SMTP.PASSWORD' | translate }} + + +
+ +
+ +
+
+ + +
+ {{ 'SMTP.CREATE.STEPS.SENDER_SETTINGS' | translate }} + + + {{ 'SETTING.SMTP.SENDERADDRESS' | translate }} + + + + + {{ 'SETTING.SMTP.SENDERNAME' | translate }} + + + + + {{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }} + + +
+ +
+ + +
+
+ + + check + +
+
diff --git a/console/src/app/modules/smtp-provider/smtp-provider.component.spec.ts b/console/src/app/modules/smtp-provider/smtp-provider.component.spec.ts new file mode 100644 index 0000000000..6a82f69f6a --- /dev/null +++ b/console/src/app/modules/smtp-provider/smtp-provider.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { SMTPProviderComponent } from './smtp-provider.component'; + +describe('SMTPProviderSendgridComponent', () => { + let component: SMTPProviderComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [SMTPProviderComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SMTPProviderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/smtp-provider/smtp-provider.component.ts b/console/src/app/modules/smtp-provider/smtp-provider.component.ts new file mode 100644 index 0000000000..c3991a55c8 --- /dev/null +++ b/console/src/app/modules/smtp-provider/smtp-provider.component.ts @@ -0,0 +1,278 @@ +import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; +import { Location } from '@angular/common'; +import { Component } from '@angular/core'; +import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { Options } from 'src/app/proto/generated/zitadel/idp_pb'; +import { requiredValidator } from '../form-field/validators/validators'; + +import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; +import { + AddSMTPConfigRequest, + AddSMTPConfigResponse, + UpdateSMTPConfigRequest, + UpdateSMTPConfigResponse, +} from 'src/app/proto/generated/zitadel/admin_pb'; +import { AdminService } from 'src/app/services/admin.service'; +import { ToastService } from 'src/app/services/toast.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { + AmazonSESDefaultSettings, + BrevoDefaultSettings, + GenericDefaultSettings, + GoogleDefaultSettings, + MailchimpDefaultSettings, + MailgunDefaultSettings, + MailjetDefaultSettings, + PostmarkDefaultSettings, + ProviderDefaultSettings, + SendgridDefaultSettings, +} from './known-smtp-providers-settings'; + +@Component({ + selector: 'cnsl-smtp-provider', + templateUrl: './smtp-provider.component.html', + styleUrls: ['./smtp-provider.scss'], +}) +export class SMTPProviderComponent { + public showOptional: boolean = false; + public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public id: string = ''; + public providerDefaultSetting: ProviderDefaultSettings = GenericDefaultSettings; + public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; + + public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; + + public smtpLoading: boolean = false; + public hasSMTPConfig: boolean = false; + + public updateClientSecret: boolean = false; + + // stepper + public currentCreateStep: number = 1; + public requestRedirectValuesSubject$: Subject = new Subject(); + public firstFormGroup!: UntypedFormGroup; + public secondFormGroup!: UntypedFormGroup; + + constructor( + private service: AdminService, + private _location: Location, + private fb: UntypedFormBuilder, + private toast: ToastService, + private router: Router, + private route: ActivatedRoute, + ) { + this.route.parent?.url.subscribe((urlPath) => { + const providerName = urlPath[urlPath.length - 1].path; + switch (providerName) { + case 'aws-ses': + this.providerDefaultSetting = AmazonSESDefaultSettings; + break; + case 'google': + this.providerDefaultSetting = GoogleDefaultSettings; + break; + case 'mailgun': + this.providerDefaultSetting = MailgunDefaultSettings; + break; + case 'mailjet': + this.providerDefaultSetting = MailjetDefaultSettings; + break; + case 'postmark': + this.providerDefaultSetting = PostmarkDefaultSettings; + break; + case 'sendgrid': + this.providerDefaultSetting = SendgridDefaultSettings; + break; + case 'mailchimp': + this.providerDefaultSetting = MailchimpDefaultSettings; + break; + case 'brevo': + this.providerDefaultSetting = BrevoDefaultSettings; + break; + } + + this.firstFormGroup = this.fb.group({ + description: [this.providerDefaultSetting.name], + tls: [{ value: this.providerDefaultSetting.requiredTls, disabled: this.providerDefaultSetting.requiredTls }], + region: [''], + hostAndPort: [ + this.providerDefaultSetting?.host + ? `${this.providerDefaultSetting?.host}:${this.providerDefaultSetting?.unencryptedPort}` + : '', + ], + user: [this.providerDefaultSetting?.user.value || ''], + password: [this.providerDefaultSetting?.password.value || ''], + }); + + this.secondFormGroup = this.fb.group({ + senderAddress: ['', [requiredValidator]], + senderName: ['', [requiredValidator]], + replyToAddress: [''], + }); + + this.region?.valueChanges.subscribe((region: string) => { + this.hostAndPort?.setValue( + `${region}:${ + this.tls ? this.providerDefaultSetting?.encryptedPort : this.providerDefaultSetting?.unencryptedPort + }`, + ); + }); + + if (!this.router.url.endsWith('/create')) { + this.id = this.route.snapshot.paramMap.get('id') || ''; + if (this.id) { + this.fetchData(this.id); + } + } + }); + } + + public changeStep(event: StepperSelectionEvent): void { + this.currentCreateStep = event.selectedIndex + 1; + + if (event.selectedIndex >= 2) { + this.requestRedirectValuesSubject$.next(); + } + } + + public close(): void { + this._location.back(); + } + + public toggleTLS(event: MatCheckboxChange) { + if (this.providerDefaultSetting.host) { + this.hostAndPort?.setValue( + `${this.providerDefaultSetting?.host}:${ + event.checked ? this.providerDefaultSetting?.encryptedPort : this.providerDefaultSetting?.unencryptedPort + }`, + ); + } + } + + private fetchData(id: string): void { + this.smtpLoading = true; + this.service + .getSMTPConfigById(id) + .then((data) => { + this.smtpLoading = false; + if (data.smtpConfig) { + this.hasSMTPConfig = true; + this.firstFormGroup.patchValue({ + ['description']: data.smtpConfig.description, + ['tls']: data.smtpConfig.tls, + ['hostAndPort']: data.smtpConfig.host, + ['user']: data.smtpConfig.user, + }); + this.secondFormGroup.patchValue({ + ['senderAddress']: data.smtpConfig.senderAddress, + ['senderName']: data.smtpConfig.senderName, + ['replyToAddress']: data.smtpConfig.replyToAddress, + }); + } + }) + .catch((error) => { + this.smtpLoading = false; + if (error && error.code === 5) { + this.hasSMTPConfig = false; + } + }); + } + + private updateData(): Promise { + if (this.hasSMTPConfig) { + const req = new UpdateSMTPConfigRequest(); + req.setId(this.id); + req.setDescription(this.description?.value || ''); + req.setTls(this.tls?.value ?? false); + + if (this.hostAndPort && this.hostAndPort.value) { + req.setHost(this.hostAndPort.value); + } + if (this.user && this.user.value) { + req.setUser(this.user.value); + } + if (this.password && this.password.value) { + req.setPassword(this.password.value); + } + if (this.senderAddress && this.senderAddress.value) { + req.setSenderAddress(this.senderAddress.value); + } + if (this.senderName && this.senderName.value) { + req.setSenderName(this.senderName.value); + } + if (this.replyToAddress && this.replyToAddress.value) { + req.setReplyToAddress(this.replyToAddress.value); + } + return this.service.updateSMTPConfig(req); + } else { + const req = new AddSMTPConfigRequest(); + req.setDescription(this.description?.value ?? ''); + req.setHost(this.hostAndPort?.value ?? ''); + req.setSenderAddress(this.senderAddress?.value ?? ''); + req.setSenderName(this.senderName?.value ?? ''); + req.setReplyToAddress(this.replyToAddress?.value ?? ''); + req.setTls(this.tls?.value ?? false); + req.setUser(this.user?.value ?? ''); + req.setPassword(this.password?.value ?? ''); + return this.service.addSMTPConfig(req); + } + } + + public savePolicy(): void { + this.updateData() + .then(() => { + this.toast.showInfo('SETTING.SMTP.SAVED', true); + setTimeout(() => { + this.close(); + }, 2000); + }) + .catch((error: unknown) => { + if (`${error}`.includes('No changes')) { + this.toast.showInfo('SETTING.SMTP.NOCHANGES', true); + setTimeout(() => { + this.close(); + }, 2000); + } else { + this.toast.showError(error); + } + }); + } + + public get description(): AbstractControl | null { + return this.firstFormGroup.get('description'); + } + + public get tls(): AbstractControl | null { + return this.firstFormGroup.get('tls'); + } + + public get region(): AbstractControl | null { + return this.firstFormGroup.get('region'); + } + + public get hostAndPort(): AbstractControl | null { + return this.firstFormGroup.get('hostAndPort'); + } + + public get user(): AbstractControl | null { + return this.firstFormGroup.get('user'); + } + + public get password(): AbstractControl | null { + return this.firstFormGroup.get('password'); + } + + public get senderAddress(): AbstractControl | null { + return this.secondFormGroup.get('senderAddress'); + } + + public get senderName(): AbstractControl | null { + return this.secondFormGroup.get('senderName'); + } + + public get replyToAddress(): AbstractControl | null { + return this.secondFormGroup.get('replyToAddress'); + } +} diff --git a/console/src/app/modules/smtp-provider/smtp-provider.module.ts b/console/src/app/modules/smtp-provider/smtp-provider.module.ts new file mode 100644 index 0000000000..8d197a4f12 --- /dev/null +++ b/console/src/app/modules/smtp-provider/smtp-provider.module.ts @@ -0,0 +1,51 @@ +import { CommonModule, TitleCasePipe } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatIconModule } from '@angular/material/icon'; +import { TranslateModule } from '@ngx-translate/core'; +import { InputModule } from 'src/app/modules/input/input.module'; + +import { MatStepperModule } from '@angular/material/stepper'; +import { CardModule } from '../card/card.module'; +import { CreateLayoutModule } from '../create-layout/create-layout.module'; +import { InfoSectionModule } from '../info-section/info-section.module'; +import { ProviderOptionsModule } from '../provider-options/provider-options.module'; +import { StringListModule } from '../string-list/string-list.module'; +import { SMTPProvidersRoutingModule } from './smtp-provider-routing.module'; +import { SMTPProviderComponent } from './smtp-provider.component'; +import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; +import { MatButtonModule } from '@angular/material/button'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSelectModule } from '@angular/material/select'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + +@NgModule({ + declarations: [SMTPProviderComponent], + imports: [ + SMTPProvidersRoutingModule, + CommonModule, + FormsModule, + ReactiveFormsModule, + CreateLayoutModule, + StringListModule, + InfoSectionModule, + InputModule, + MatButtonModule, + MatProgressBarModule, + MatSelectModule, + MatIconModule, + MatChipsModule, + CardModule, + MatCheckboxModule, + MatTooltipModule, + MatStepperModule, + TranslateModule, + ProviderOptionsModule, + HasRolePipeModule, + MatProgressSpinnerModule, + ], +}) +export default class SMTPProviderModule {} diff --git a/console/src/app/modules/smtp-provider/smtp-provider.scss b/console/src/app/modules/smtp-provider/smtp-provider.scss new file mode 100644 index 0000000000..06fdcefe6e --- /dev/null +++ b/console/src/app/modules/smtp-provider/smtp-provider.scss @@ -0,0 +1,71 @@ +@use '@angular/material' as mat; + +@mixin smtp-provider-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .stepper { + background: inherit !important; + margin: 0 -1.5rem; + } + + .smtp-create-actions { + margin-top: 2rem; + display: flex; + align-items: center; + justify-content: space-between; + + .bck-button { + margin-right: 1rem; + } + + .create-button { + padding: 0.5rem 4rem; + } + } + + .smtp-form-field, + .info-section-warn { + max-width: 400px; + display: block; + } + + .wizard-header { + display: flex; + align-items: center; + + .smtp-logo { + margin-right: 1rem; + height: 48px; + width: 48px; + flex-shrink: 0; + } + + .smtp-icon { + margin-right: 1rem; + display: flex; + justify-content: center; + align-items: center; + height: 36px; + width: 36px; + + .icon { + font-size: 2.25rem; + height: 2.25rem; + width: 2.25rem; + } + } + } + + .row { + display: flex; + justify-content: space-between; + + .left, + .right { + margin-bottom: 0.5rem; + font-size: 14px; + } + } +} diff --git a/console/src/app/modules/smtp-table/smtp-table.component.html b/console/src/app/modules/smtp-table/smtp-table.component.html new file mode 100644 index 0000000000..ba8ac58518 --- /dev/null +++ b/console/src/app/modules/smtp-table/smtp-table.component.html @@ -0,0 +1,129 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'SMTP.LIST.ACTIVATED' | translate }} + + + {{ 'SETTING.SMTP.DESCRIPTION' | translate }} + {{ config?.description }} + + TLS + + + + {{ 'SETTING.SMTP.HOSTANDPORT' | translate }} + {{ config?.host }} + {{ 'SETTING.SMTP.SENDERADDRESS' | translate }} + {{ config?.senderAddress }} + + + + + + + + +
+
+ +
+ + {{ 'SMTP.LIST.EMPTY' | translate }} +
+ + +
diff --git a/console/src/app/modules/smtp-table/smtp-table.component.scss b/console/src/app/modules/smtp-table/smtp-table.component.scss new file mode 100644 index 0000000000..076614c03f --- /dev/null +++ b/console/src/app/modules/smtp-table/smtp-table.component.scss @@ -0,0 +1,81 @@ +@mixin smtp-table-theme($theme) { + $primary: map-get($theme, primary); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $accent: map-get($theme, accent); + $primary-color: map-get($primary, 500); + $warn-color: map-get($warn, 500); + $accent-color: map-get($accent, 500); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + $back: map-get($background, background); + + .smtp-table-provider-type { + display: flex; + align-items: center; + + .smtp-logo { + margin-right: 1rem; + height: 28px; + width: 28px; + flex-shrink: 0; + + &.dark { + display: if($is-dark-theme, block, none); + } + + &.light { + display: if($is-dark-theme, none, block); + } + } + + .smtp-icon { + font-size: 1.75rem; + height: 1.75rem; + width: 1.75rem; + margin-right: 1rem; + flex-shrink: 0; + } + } +} +.smtp-margin-right { + margin-right: 0.5rem; +} + +td { + outline: none; +} + +tr { + outline: none; + + &.disabled, + &.disabled * { + opacity: 0.8; + cursor: default; + } +} + +.availability { + width: 40px; +} + +.smtp-available { + color: var(--success); +} + +.smtp-not-available { + color: var(--warn); +} + +.smtp-table-actions { + width: 100px; +} + +.la-unlock { + color: orangered; +} + +.la-lock { + color: darkorange; +} diff --git a/console/src/app/modules/smtp-table/smtp-table.component.spec.ts b/console/src/app/modules/smtp-table/smtp-table.component.spec.ts new file mode 100644 index 0000000000..8095d73255 --- /dev/null +++ b/console/src/app/modules/smtp-table/smtp-table.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { IdpTableComponent } from './smtp-table.component'; + +describe('UserTableComponent', () => { + let component: IdpTableComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [IdpTableComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(IdpTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/smtp-table/smtp-table.component.ts b/console/src/app/modules/smtp-table/smtp-table.component.ts new file mode 100644 index 0000000000..ee0da1a329 --- /dev/null +++ b/console/src/app/modules/smtp-table/smtp-table.component.ts @@ -0,0 +1,194 @@ +import { SelectionModel } from '@angular/cdk/collections'; +import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; +import { Router, RouterLink } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { SMTPConfigState } from 'src/app/proto/generated/zitadel/settings_pb'; +import { ListQuery } from 'src/app/proto/generated/zitadel/object_pb'; +import { LoginPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; +import { AdminService } from 'src/app/services/admin.service'; +import { ToastService } from 'src/app/services/toast.service'; + +import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'; +import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; +import { ListSMTPConfigsRequest, ListSMTPConfigsResponse } from 'src/app/proto/generated/zitadel/admin_pb'; +import { SMTPConfig } from 'src/app/proto/generated/zitadel/settings_pb'; +import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatDialog } from '@angular/material/dialog'; + +@Component({ + selector: 'cnsl-smtp-table', + templateUrl: './smtp-table.component.html', + styleUrls: ['./smtp-table.component.scss'], +}) +export class SMTPTableComponent implements OnInit { + @ViewChild(PaginatorComponent) public paginator!: PaginatorComponent; + public dataSource: MatTableDataSource = new MatTableDataSource(); + public selection: SelectionModel = new SelectionModel(true, []); + public configsResult?: ListSMTPConfigsResponse.AsObject; + private loadingSubject: BehaviorSubject = new BehaviorSubject(false); + public loading$: Observable = this.loadingSubject.asObservable(); + public PolicyComponentServiceType: any = PolicyComponentServiceType; + public displayedColumns: string[] = ['activated', 'description', 'tls', 'host', 'senderAddress', 'actions']; + @Output() public changedSelection: EventEmitter> = new EventEmitter(); + + public loginPolicy!: LoginPolicy.AsObject; + + constructor( + private adminService: AdminService, + public translate: TranslateService, + private toast: ToastService, + private dialog: MatDialog, + private router: Router, + ) { + this.selection.changed.subscribe(() => { + this.changedSelection.emit(this.selection.selected); + }); + } + + ngOnInit(): void { + this.getData(10, 0); + } + + public isActive(state: number) { + return state === SMTPConfigState.SMTP_CONFIG_ACTIVE; + } + + public isAllSelected(): boolean { + const numSelected = this.selection.selected.length; + const numRows = this.dataSource.data.length; + return numSelected === numRows; + } + + public masterToggle(): void { + this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row)); + } + + public changePage(event: PageEvent): void { + this.getData(event.pageSize, event.pageIndex * event.pageSize); + } + + public activateSMTPConfig(id: string): void { + const dialogRef = this.dialog.open(WarnDialogComponent, { + data: { + confirmKey: 'ACTIONS.CONTINUE', + cancelKey: 'ACTIONS.CANCEL', + titleKey: 'SMTP.LIST.DIALOG.ACTIVATE_WARN_TITLE', + descriptionKey: 'SMTP.LIST.DIALOG.ACTIVATE_WARN_DESCRIPTION', + }, + width: '400px', + }); + + dialogRef.afterClosed().subscribe((resp) => { + if (resp) { + this.adminService + .activateSMTPConfig(id) + .then(() => { + this.toast.showInfo('SMTP.LIST.DIALOG.ACTIVATED', true); + this.refreshPage(); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + }); + } + + public deactivateSMTPConfig(id: string): void { + const dialogRef = this.dialog.open(WarnDialogComponent, { + data: { + confirmKey: 'ACTIONS.CONTINUE', + cancelKey: 'ACTIONS.CANCEL', + titleKey: 'SMTP.LIST.DIALOG.DEACTIVATE_WARN_TITLE', + descriptionKey: 'SMTP.LIST.DIALOG.DEACTIVATE_WARN_DESCRIPTION', + }, + width: '400px', + }); + + dialogRef.afterClosed().subscribe((resp) => { + if (resp) { + this.adminService + .deactivateSMTPConfig(id) + .then(() => { + this.toast.showInfo('SMTP.LIST.DIALOG.DEACTIVATED', true); + this.refreshPage(); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + }); + } + + public deleteSMTPConfig(id: string, senderName: string): void { + const dialogRef = this.dialog.open(WarnDialogComponent, { + data: { + confirmKey: 'ACTIONS.DELETE', + cancelKey: 'ACTIONS.CANCEL', + titleKey: 'SMTP.LIST.DIALOG.DELETE_TITLE', + descriptionKey: 'SMTP.LIST.DIALOG.DELETE_DESCRIPTION', + confirmationKey: 'SMTP.LIST.DIALOG.SENDER', + confirmation: senderName, + }, + width: '400px', + }); + + dialogRef.afterClosed().subscribe((resp) => { + if (resp) { + this.adminService + .removeSMTPConfig(id) + .then(() => { + this.toast.showInfo('SMTP.LIST.DIALOG.DELETED', true); + this.refreshPage(); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + }); + } + + private async getData(limit: number, offset: number): Promise { + this.loadingSubject.next(true); + + const req = new ListSMTPConfigsRequest(); + const lq = new ListQuery(); + lq.setOffset(offset); + lq.setLimit(limit); + req.setQuery(lq); + this.adminService + .listSMTPConfigs() + .then((resp) => { + this.configsResult = resp; + if (resp.resultList) { + this.dataSource.data = resp.resultList; + } + this.loadingSubject.next(false); + }) + .catch((error) => { + this.toast.showError(error); + this.loadingSubject.next(false); + }); + } + + public refreshPage(): void { + this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize); + } + + public get createRouterLink(): RouterLink | any { + return ['/instance', 'idp', 'create']; + } + + public routerLinkForRow(row: SMTPConfig.AsObject): any { + return ['/instance', 'smtpprovider', row.id]; + } + + public get displayedColumnsWithActions(): string[] { + return ['actions', ...this.displayedColumns]; + } + + public navigateToProvider(row: SMTPConfig.AsObject) { + this.router.navigate(this.routerLinkForRow(row)); + } +} diff --git a/console/src/app/modules/smtp-table/smtp-table.module.ts b/console/src/app/modules/smtp-table/smtp-table.module.ts new file mode 100644 index 0000000000..3ce1c3d05e --- /dev/null +++ b/console/src/app/modules/smtp-table/smtp-table.module.ts @@ -0,0 +1,46 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatIconModule } from '@angular/material/icon'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; +import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module'; +import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; +import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module'; +import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module'; +import { TruncatePipeModule } from 'src/app/pipes/truncate-pipe/truncate-pipe.module'; + +import { PaginatorModule } from '../paginator/paginator.module'; +import { TableActionsModule } from '../table-actions/table-actions.module'; +import { SMTPTableComponent } from './smtp-table.component'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTableModule } from '@angular/material/table'; + +@NgModule({ + declarations: [SMTPTableComponent], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + TableActionsModule, + MatCheckboxModule, + MatIconModule, + MatTooltipModule, + TranslateModule, + LocalizedDatePipeModule, + TimestampToDatePipeModule, + MatTableModule, + PaginatorModule, + RouterModule, + RefreshTableModule, + HasRoleModule, + HasRolePipeModule, + TruncatePipeModule, + ], + exports: [SMTPTableComponent], +}) +export class SMTPTableModule {} diff --git a/console/src/app/pages/instance/instance-routing.module.ts b/console/src/app/pages/instance/instance-routing.module.ts index c88cbafaf7..ca03086e85 100644 --- a/console/src/app/pages/instance/instance-routing.module.ts +++ b/console/src/app/pages/instance/instance-routing.module.ts @@ -32,6 +32,15 @@ const routes: Routes = [ serviceType: PolicyComponentServiceType.ADMIN, }, }, + { + path: 'smtpprovider', + canActivate: [AuthGuard, RoleGuard], + loadChildren: () => import('src/app/modules/smtp-provider/smtp-provider.module'), + data: { + roles: ['iam.idp.read'], + serviceType: PolicyComponentServiceType.ADMIN, + }, + }, ]; @NgModule({ diff --git a/console/src/app/pages/instance/instance.component.ts b/console/src/app/pages/instance/instance.component.ts index 1ab7f061ef..8798027400 100644 --- a/console/src/app/pages/instance/instance.component.ts +++ b/console/src/app/pages/instance/instance.component.ts @@ -32,6 +32,7 @@ import { FAILEDEVENTS, EVENTS, ORGANIZATIONS, + FEATURESETTINGS, } from '../../modules/settings-list/settings'; import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; @@ -53,6 +54,7 @@ export class InstanceComponent implements OnInit, OnDestroy { public id: string = ''; public defaultSettingsList: SidenavSetting[] = [ ORGANIZATIONS, + FEATURESETTINGS, // notifications // { showWarn: true, ...NOTIFICATIONS }, NOTIFICATIONS, diff --git a/console/src/app/pages/projects/apps/app-create/app-create.component.ts b/console/src/app/pages/projects/apps/app-create/app-create.component.ts index f901c9dde6..f623c0385a 100644 --- a/console/src/app/pages/projects/apps/app-create/app-create.component.ts +++ b/console/src/app/pages/projects/apps/app-create/app-create.component.ts @@ -237,7 +237,7 @@ export class AppCreateComponent implements OnInit, OnDestroy { this.samlAppRequest.setMetadataUrl(form.metadataUrl); } - if (this.samlAppRequest) { + if (this.samlAppRequest && minimalMetadata.length > 0) { const base64 = Buffer.from(minimalMetadata, 'utf-8').toString('base64'); this.samlAppRequest.setMetadataXml(base64); } diff --git a/console/src/app/pages/projects/apps/app-detail/app-detail.component.ts b/console/src/app/pages/projects/apps/app-detail/app-detail.component.ts index 90136b4d6a..5d8170c0c0 100644 --- a/console/src/app/pages/projects/apps/app-detail/app-detail.component.ts +++ b/console/src/app/pages/projects/apps/app-detail/app-detail.component.ts @@ -213,7 +213,14 @@ export class AppDetailComponent implements OnInit, OnDestroy { metadataXml: [{ value: '', disabled: true }], }); - this.samlForm.valueChanges.subscribe((form) => { + this.samlForm.valueChanges.subscribe(() => { + if (!this.app) { + this.app = new App().toObject(); + } + if (!this.app.samlConfig) { + this.app.samlConfig = new SAMLConfig().toObject(); + } + let minimalMetadata = this.entityId?.value && this.acsURL?.value ? ` @@ -224,15 +231,15 @@ export class AppDetailComponent implements OnInit, OnDestroy { ` : ''; - if (this.metadataUrl && this.metadataUrl.value.length > 0) { - if (this.app && this.app.samlConfig && this.app.samlConfig.metadataXml) { - this.app.samlConfig.metadataXml = ''; - } - } - - if (this.app && this.app.samlConfig && this.app.samlConfig.metadataXml && minimalMetadata) { + if (minimalMetadata) { const base64 = Buffer.from(minimalMetadata, 'utf-8').toString('base64'); this.app.samlConfig.metadataXml = base64; + this.app.samlConfig.metadataUrl = ''; + } + + if (this.metadataUrl?.value) { + this.app.samlConfig.metadataXml = ''; + this.app.samlConfig.metadataUrl = this.metadataUrl?.value; } }); } @@ -713,8 +720,10 @@ export class AppDetailComponent implements OnInit, OnDestroy { req.setProjectId(this.projectId); req.setAppId(this.app.id); - if (this.app.samlConfig) { + if (this.app.samlConfig?.metadataUrl.length > 0) { req.setMetadataUrl(this.app.samlConfig?.metadataUrl); + } + if (this.app.samlConfig?.metadataXml.length > 0) { req.setMetadataXml(this.app.samlConfig?.metadataXml); } diff --git a/console/src/app/services/activate-idp.service.ts b/console/src/app/services/activate-idp.service.ts deleted file mode 100644 index 8c49925db2..0000000000 --- a/console/src/app/services/activate-idp.service.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject, combineLatest, from, map, Observable, of, switchMap } from 'rxjs'; - -import { ManagementService } from './mgmt.service'; -import { AddCustomLoginPolicyRequest, AddCustomLoginPolicyResponse } from '../proto/generated/zitadel/management_pb'; -import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; -import { IDPOwnerType, Provider } from '../proto/generated/zitadel/idp_pb'; -import { AdminService } from './admin.service'; -import { catchError } from 'rxjs/operators'; -import { LoginPolicy } from '../proto/generated/zitadel/policy_pb'; - -@Injectable({ - providedIn: 'root', -}) -export class ActivateIdpService { - constructor() {} - - public activateIdp( - service: AdminService | ManagementService, - id: string, - owner?: IDPOwnerType, - policy?: LoginPolicy.AsObject, - ): Observable { - const isAdmin = service instanceof AdminService; - if (isAdmin) { - service.addIDPToLoginPolicy(id); - } - - if (!isAdmin && owner !== IDPOwnerType.IDP_OWNER_TYPE_SYSTEM && owner !== IDPOwnerType.IDP_OWNER_TYPE_ORG) { - throw new Error('Must specify owner for management service'); - } - - return from(service.addIDPToLoginPolicy(id!, owner!)).pipe( - catchError((error) => { - if (isAdmin || error.code != 5) { - throw error; - } - // No org policy was found, so we create a new one - return from(policy ? of(policy) : from(service.getLoginPolicy()).pipe(map((policy) => policy.policy))).pipe( - switchMap((policy) => { - if (!policy?.isDefault) { - // There is already an org policy - throw error; - } - return from(this.addLoginPolicy(service, policy)).pipe( - switchMap(() => from(service.addIDPToLoginPolicy(id!, owner!))), - ); - }), - ); - }), - ); - } - - public addLoginPolicy( - service: ManagementService, - policy: LoginPolicy.AsObject, - ): Promise { - const mgmtreq = new AddCustomLoginPolicyRequest(); - mgmtreq.setAllowExternalIdp(policy.allowExternalIdp); - mgmtreq.setAllowRegister(policy.allowRegister); - mgmtreq.setAllowUsernamePassword(policy.allowUsernamePassword); - mgmtreq.setForceMfa(policy.forceMfa); - mgmtreq.setPasswordlessType(policy.passwordlessType); - mgmtreq.setHidePasswordReset(policy.hidePasswordReset); - mgmtreq.setMultiFactorsList(policy.multiFactorsList); - mgmtreq.setSecondFactorsList(policy.secondFactorsList); - - const pcl = new Duration() - .setSeconds(policy.passwordCheckLifetime?.seconds ?? 0) - .setNanos(policy.passwordCheckLifetime?.nanos ?? 0); - mgmtreq.setPasswordCheckLifetime(pcl); - - const elcl = new Duration() - .setSeconds(policy.externalLoginCheckLifetime?.seconds ?? 0) - .setNanos(policy.externalLoginCheckLifetime?.nanos ?? 0); - mgmtreq.setExternalLoginCheckLifetime(elcl); - - const misl = new Duration() - .setSeconds(policy.mfaInitSkipLifetime?.seconds ?? 0) - .setNanos(policy.mfaInitSkipLifetime?.nanos ?? 0); - mgmtreq.setMfaInitSkipLifetime(misl); - - const sfcl = new Duration() - .setSeconds(policy.secondFactorCheckLifetime?.seconds ?? 0) - .setNanos(policy.secondFactorCheckLifetime?.nanos ?? 0); - mgmtreq.setSecondFactorCheckLifetime(sfcl); - - const mficl = new Duration() - .setSeconds(policy.multiFactorCheckLifetime?.seconds ?? 0) - .setNanos(policy.multiFactorCheckLifetime?.nanos ?? 0); - mgmtreq.setMultiFactorCheckLifetime(mficl); - - mgmtreq.setAllowDomainDiscovery(policy.allowDomainDiscovery); - mgmtreq.setIgnoreUnknownUsernames(policy.ignoreUnknownUsernames); - mgmtreq.setDefaultRedirectUri(policy.defaultRedirectUri); - - mgmtreq.setDisableLoginWithEmail(policy.disableLoginWithEmail); - mgmtreq.setDisableLoginWithPhone(policy.disableLoginWithPhone); - mgmtreq.setForceMfaLocalOnly(policy.forceMfaLocalOnly); - - return service.addCustomLoginPolicy(mgmtreq); - } -} diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts index 17c8f1b323..d1460b06f2 100644 --- a/console/src/app/services/admin.service.ts +++ b/console/src/app/services/admin.service.ts @@ -6,6 +6,8 @@ import { ActivateLabelPolicyResponse, ActivateSMSProviderRequest, ActivateSMSProviderResponse, + ActivateSMTPConfigRequest, + ActivateSMTPConfigResponse, AddAppleProviderRequest, AddAppleProviderResponse, AddAzureADProviderRequest, @@ -52,6 +54,8 @@ import { DeactivateIDPResponse, DeactivateSMSProviderRequest, DeactivateSMSProviderResponse, + DeactivateSMTPConfigRequest, + DeactivateSMTPConfigResponse, DeleteProviderRequest, DeleteProviderResponse, GetAllowedLanguagesRequest, @@ -135,6 +139,8 @@ import { GetSecurityPolicyResponse, GetSMSProviderRequest, GetSMSProviderResponse, + GetSMTPConfigByIdRequest, + GetSMTPConfigByIdResponse, GetSMTPConfigRequest, GetSMTPConfigResponse, GetSupportedLanguagesRequest, @@ -167,6 +173,8 @@ import { ListSecretGeneratorsResponse, ListSMSProvidersRequest, ListSMSProvidersResponse, + ListSMTPConfigsRequest, + ListSMTPConfigsResponse, ListViewsRequest, ListViewsResponse, ReactivateIDPRequest, @@ -193,6 +201,8 @@ import { RemoveSecondFactorFromLoginPolicyResponse, RemoveSMSProviderRequest, RemoveSMSProviderResponse, + RemoveSMTPConfigRequest, + RemoveSMTPConfigResponse, ResetCustomDomainPolicyToDefaultRequest, ResetCustomDomainPolicyToDefaultResponse, ResetCustomLoginTextsToDefaultRequest, @@ -894,6 +904,17 @@ export class AdminService { return this.grpcService.admin.getSMTPConfig(req, null).then((resp) => resp.toObject()); } + public getSMTPConfigById(id: string): Promise { + const req = new GetSMTPConfigByIdRequest(); + req.setId(id); + return this.grpcService.admin.getSMTPConfigById(req, null).then((resp) => resp.toObject()); + } + + public listSMTPConfigs(): Promise { + const req = new ListSMTPConfigsRequest(); + return this.grpcService.admin.listSMTPConfigs(req, null).then((resp) => resp.toObject()); + } + public addSMTPConfig(req: AddSMTPConfigRequest): Promise { return this.grpcService.admin.addSMTPConfig(req, null).then((resp) => resp.toObject()); } @@ -906,6 +927,24 @@ export class AdminService { return this.grpcService.admin.updateSMTPConfigPassword(req, null).then((resp) => resp.toObject()); } + public activateSMTPConfig(id: string): Promise { + const req = new ActivateSMTPConfigRequest(); + req.setId(id); + return this.grpcService.admin.activateSMTPConfig(req, null).then((resp) => resp.toObject()); + } + + public deactivateSMTPConfig(id: string): Promise { + const req = new DeactivateSMTPConfigRequest(); + req.setId(id); + return this.grpcService.admin.deactivateSMTPConfig(req, null).then((resp) => resp.toObject()); + } + + public removeSMTPConfig(id: string): Promise { + const req = new RemoveSMTPConfigRequest(); + req.setId(id); + return this.grpcService.admin.removeSMTPConfig(req, null).then((resp) => resp.toObject()); + } + /* sms */ public listSMSProviders(): Promise { @@ -957,9 +996,13 @@ export class AdminService { return this.grpcService.admin.getLockoutPolicy(req, null).then((resp) => resp.toObject()); } - public updateLockoutPolicy(maxAttempts: number): Promise { + public updateLockoutPolicy( + maxPasswordAttempts: number, + maxOTPAttempts: number, + ): Promise { const req = new UpdateLockoutPolicyRequest(); - req.setMaxPasswordAttempts(maxAttempts); + req.setMaxPasswordAttempts(maxPasswordAttempts); + req.setMaxOtpAttempts(maxOTPAttempts); return this.grpcService.admin.updateLockoutPolicy(req, null).then((resp) => resp.toObject()); } diff --git a/console/src/app/services/feature.service.ts b/console/src/app/services/feature.service.ts new file mode 100644 index 0000000000..b991971bba --- /dev/null +++ b/console/src/app/services/feature.service.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@angular/core'; +import { GrpcService } from './grpc.service'; + +import { + GetInstanceFeaturesRequest, + GetInstanceFeaturesResponse, + ResetInstanceFeaturesRequest, + SetInstanceFeaturesRequest, + SetInstanceFeaturesResponse, +} from '../proto/generated/zitadel/feature/v2beta/instance_pb'; +import { + GetOrganizationFeaturesRequest, + GetOrganizationFeaturesResponse, +} from '../proto/generated/zitadel/feature/v2beta/organization_pb'; +import { GetUserFeaturesRequest, GetUserFeaturesResponse } from '../proto/generated/zitadel/feature/v2beta/user_pb'; +import { GetSystemFeaturesRequest, GetSystemFeaturesResponse } from '../proto/generated/zitadel/feature/v2beta/system_pb'; + +@Injectable({ + providedIn: 'root', +}) +export class FeatureService { + constructor(private readonly grpcService: GrpcService) {} + + public getInstanceFeatures(inheritance: boolean): Promise { + const req = new GetInstanceFeaturesRequest(); + req.setInheritance(inheritance); + return this.grpcService.feature.getInstanceFeatures(req, null).then((resp) => resp); + } + + public setInstanceFeatures(req: SetInstanceFeaturesRequest): Promise { + return this.grpcService.feature.setInstanceFeatures(req, null); + } + + public resetInstanceFeatures(): Promise { + const req = new ResetInstanceFeaturesRequest(); + return this.grpcService.feature.resetInstanceFeatures(req, null); + } + + public getOrganizationFeatures(orgId: string, inheritance: boolean): Promise { + const req = new GetOrganizationFeaturesRequest(); + req.setOrganizationId(orgId); + req.setInheritance(inheritance); + return this.grpcService.feature.getOrganizationFeatures(req, null).then((resp) => resp); + } + + public getSystemFeatures(): Promise { + const req = new GetSystemFeaturesRequest(); + return this.grpcService.feature.getSystemFeatures(req, null).then((resp) => resp); + } + + public getUserFeatures(userId: string, inheritance: boolean): Promise { + const req = new GetUserFeaturesRequest(); + req.setInheritance(inheritance); + req.setUserId(userId); + return this.grpcService.feature.getUserFeatures(req, null).then((resp) => resp); + } +} diff --git a/console/src/app/services/grpc.service.ts b/console/src/app/services/grpc.service.ts index 895dae9af5..8a87f95ef0 100644 --- a/console/src/app/services/grpc.service.ts +++ b/console/src/app/services/grpc.service.ts @@ -17,6 +17,7 @@ import { ExhaustedGrpcInterceptor } from './interceptors/exhausted.grpc.intercep import { I18nInterceptor } from './interceptors/i18n.interceptor'; import { OrgInterceptor } from './interceptors/org.interceptor'; import { StorageService } from './storage.service'; +import { FeatureServiceClient } from '../proto/generated/zitadel/feature/v2beta/Feature_serviceServiceClientPb'; @Injectable({ providedIn: 'root', @@ -25,6 +26,7 @@ export class GrpcService { public auth!: AuthServiceClient; public mgmt!: ManagementServiceClient; public admin!: AdminServiceClient; + public feature!: FeatureServiceClient; constructor( private envService: EnvironmentService, @@ -76,6 +78,12 @@ export class GrpcService { // @ts-ignore interceptors, ); + this.feature = new FeatureServiceClient( + env.api, + null, + // @ts-ignore + interceptors, + ); const authConfig: AuthConfig = { scope: 'openid profile email', diff --git a/console/src/app/services/login-policy.service.ts b/console/src/app/services/login-policy.service.ts new file mode 100644 index 0000000000..333d5170f0 --- /dev/null +++ b/console/src/app/services/login-policy.service.ts @@ -0,0 +1,115 @@ +import { Injectable } from '@angular/core'; +import { from, map, Observable, of, switchMap } from 'rxjs'; + +import { ManagementService } from './mgmt.service'; +import { AddCustomLoginPolicyRequest, AddCustomLoginPolicyResponse } from '../proto/generated/zitadel/management_pb'; +import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; +import { IDPOwnerType } from '../proto/generated/zitadel/idp_pb'; +import { AdminService } from './admin.service'; +import { catchError } from 'rxjs/operators'; +import { LoginPolicy } from '../proto/generated/zitadel/policy_pb'; + +@Injectable({ + providedIn: 'root', +}) +export class LoginPolicyService { + constructor() {} + + public activateIdp( + service: AdminService | ManagementService, + id: string, + owner?: IDPOwnerType, + policy?: LoginPolicy.AsObject, + ): Observable { + const isAdmin = service instanceof AdminService; + if (isAdmin) { + service.addIDPToLoginPolicy(id); + } + + if (!isAdmin && owner !== IDPOwnerType.IDP_OWNER_TYPE_SYSTEM && owner !== IDPOwnerType.IDP_OWNER_TYPE_ORG) { + throw new Error('Must specify owner for management service'); + } + + return from(service.addIDPToLoginPolicy(id!, owner!)).pipe( + catchError((error) => { + if (isAdmin || error.code != 5) { + throw error; + } + // No org policy was found, so we create a new one + return from(policy ? of(policy) : from(service.getLoginPolicy()).pipe(map((policy) => policy.policy))).pipe( + switchMap((policy) => { + if (!policy?.isDefault) { + // There is already an org policy + throw error; + } + return from(this.createCustomLoginPolicy(service, policy, id)); + }), + ); + }), + ); + } + + public createCustomLoginPolicy( + service: ManagementService, + fromDefaultPolicy: LoginPolicy.AsObject, + activateOrgIdp?: string, + ): Promise { + const mgmtreq = new AddCustomLoginPolicyRequest(); + mgmtreq.setAllowExternalIdp(fromDefaultPolicy.allowExternalIdp); + mgmtreq.setAllowRegister(fromDefaultPolicy.allowRegister); + mgmtreq.setAllowUsernamePassword(fromDefaultPolicy.allowUsernamePassword); + mgmtreq.setForceMfa(fromDefaultPolicy.forceMfa); + mgmtreq.setPasswordlessType(fromDefaultPolicy.passwordlessType); + mgmtreq.setHidePasswordReset(fromDefaultPolicy.hidePasswordReset); + mgmtreq.setMultiFactorsList(fromDefaultPolicy.multiFactorsList); + mgmtreq.setSecondFactorsList(fromDefaultPolicy.secondFactorsList); + + const pcl = new Duration() + .setSeconds(fromDefaultPolicy.passwordCheckLifetime?.seconds ?? 0) + .setNanos(fromDefaultPolicy.passwordCheckLifetime?.nanos ?? 0); + mgmtreq.setPasswordCheckLifetime(pcl); + + const elcl = new Duration() + .setSeconds(fromDefaultPolicy.externalLoginCheckLifetime?.seconds ?? 0) + .setNanos(fromDefaultPolicy.externalLoginCheckLifetime?.nanos ?? 0); + mgmtreq.setExternalLoginCheckLifetime(elcl); + + const misl = new Duration() + .setSeconds(fromDefaultPolicy.mfaInitSkipLifetime?.seconds ?? 0) + .setNanos(fromDefaultPolicy.mfaInitSkipLifetime?.nanos ?? 0); + mgmtreq.setMfaInitSkipLifetime(misl); + + const sfcl = new Duration() + .setSeconds(fromDefaultPolicy.secondFactorCheckLifetime?.seconds ?? 0) + .setNanos(fromDefaultPolicy.secondFactorCheckLifetime?.nanos ?? 0); + mgmtreq.setSecondFactorCheckLifetime(sfcl); + + const mficl = new Duration() + .setSeconds(fromDefaultPolicy.multiFactorCheckLifetime?.seconds ?? 0) + .setNanos(fromDefaultPolicy.multiFactorCheckLifetime?.nanos ?? 0); + mgmtreq.setMultiFactorCheckLifetime(mficl); + + mgmtreq.setAllowDomainDiscovery(fromDefaultPolicy.allowDomainDiscovery); + mgmtreq.setIgnoreUnknownUsernames(fromDefaultPolicy.ignoreUnknownUsernames); + mgmtreq.setDefaultRedirectUri(fromDefaultPolicy.defaultRedirectUri); + + mgmtreq.setDisableLoginWithEmail(fromDefaultPolicy.disableLoginWithEmail); + mgmtreq.setDisableLoginWithPhone(fromDefaultPolicy.disableLoginWithPhone); + mgmtreq.setForceMfaLocalOnly(fromDefaultPolicy.forceMfaLocalOnly); + + mgmtreq.setIdpsList( + fromDefaultPolicy.idpsList.map((idp) => addIdpMessage(idp.idpId, IDPOwnerType.IDP_OWNER_TYPE_SYSTEM)), + ); + if (activateOrgIdp) { + mgmtreq.addIdps(addIdpMessage(activateOrgIdp, IDPOwnerType.IDP_OWNER_TYPE_ORG)); + } + return service.addCustomLoginPolicy(mgmtreq); + } +} + +function addIdpMessage(id: string, owner: IDPOwnerType): AddCustomLoginPolicyRequest.IDP { + const addIdp = new AddCustomLoginPolicyRequest.IDP(); + addIdp.setIdpId(id); + addIdp.setOwnertype(owner); + return addIdp; +} diff --git a/console/src/app/services/mgmt.service.ts b/console/src/app/services/mgmt.service.ts index ae975a228d..0e3c0b3062 100644 --- a/console/src/app/services/mgmt.service.ts +++ b/console/src/app/services/mgmt.service.ts @@ -1587,9 +1587,13 @@ export class ManagementService { return this.grpcService.mgmt.getLockoutPolicy(req, null).then((resp) => resp.toObject()); } - public addCustomLockoutPolicy(maxAttempts: number): Promise { + public addCustomLockoutPolicy( + maxPasswordAttempts: number, + maxOTPAttempts: number, + ): Promise { const req = new AddCustomLockoutPolicyRequest(); - req.setMaxPasswordAttempts(maxAttempts); + req.setMaxPasswordAttempts(maxPasswordAttempts); + req.setMaxOtpAttempts(maxOTPAttempts); return this.grpcService.mgmt.addCustomLockoutPolicy(req, null).then((resp) => resp.toObject()); } @@ -1599,9 +1603,13 @@ export class ManagementService { return this.grpcService.mgmt.resetLockoutPolicyToDefault(req, null).then((resp) => resp.toObject()); } - public updateCustomLockoutPolicy(maxAttempts: number): Promise { + public updateCustomLockoutPolicy( + maxPasswordAttempts: number, + maxOTPAttempts: number, + ): Promise { const req = new UpdateCustomLockoutPolicyRequest(); - req.setMaxPasswordAttempts(maxAttempts); + req.setMaxPasswordAttempts(maxPasswordAttempts); + req.setMaxOtpAttempts(maxOTPAttempts); return this.grpcService.mgmt.updateCustomLockoutPolicy(req, null).then((resp) => resp.toObject()); } diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 2ddcec25c4..d606ceb64a 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -86,6 +86,10 @@ "TITLE": "Настройки на организацията", "DESCRIPTION": "Персонализирайте настройките на вашата организация." }, + "FEATURES": { + "TITLE": "Настройки на функциите", + "DESCRIPTION": "Отключете функции за вашия екземпляр." + }, "IDPS": { "TITLE": "Доставчици на идентичност", "DESCRIPTION": "Създайте и активирайте външни доставчици на идентичност. Изберете известен доставчик или конфигурирайте друг OIDC, OAuth или SAML съвместим доставчик по ваш избор. Можете дори да използвате вашите съществуващи JWT токени като федерирани идентичности, като конфигурирате доставчик на идентичност с JWT.", @@ -1308,6 +1312,7 @@ }, "LIST": { "ORGS": "Организации", + "FEATURESETTINGS": "Настройки на функциите", "LANGUAGES": "Езици", "LOGIN": "Поведение при влизане и сигурност", "LOCKOUT": "Блокиране", @@ -1369,6 +1374,8 @@ } }, "SMTP": { + "TITLE": "SMTP настройки", + "DESCRIPTION": "Описание", "SENDERADDRESS": "Имейл адрес на изпращача", "SENDERNAME": "Име на изпращача", "REPLYTOADDRESS": "Reply-to адрес", @@ -1379,6 +1386,7 @@ "PASSWORDSET": "Паролата за SMTP бе зададена успешно.", "TLS": "Сигурност на транспортния слой (TLS)", "SAVED": "Запазено успешно!", + "NOCHANGES": "Без промени!", "REQUIREDWARN": "За да изпращате известия от вашия домейн, трябва да въведете своите SMTP данни." }, "SMS": { @@ -1438,6 +1446,27 @@ "IMPERSONATIONENABLED": "Разрешаване на имитация", "IMPERSONATIONDESCRIPTION": "Тази настройка позволява да се използва имитация по принцип. Обърнете внимание, че имитаторът също се нуждае от присвоени подходящи роли `*_IMPERSONATOR`." }, + "FEATURES": { + "LOGINDEFAULTORG": "Организация по подразбиране за влизане", + "LOGINDEFAULTORG_DESCRIPTION": "Потребителският интерфейс за влизане ще използва настройките на организацията по подразбиране (а не на инстанцията), ако не е зададен контекст на организация.", + "OIDCLEGACYINTROSPECTION": "Наследено осмисляне OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "На скоро префакторизирахме крайния пункт за осмисляне заради производителностни причини. Тази функция може да се използва за връщане към наследената реализация, ако възникнат неочаквани грешки.", + "OIDCTOKENEXCHANGE": "Обмяна на токени OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Активиране на експерименталния тип на дарение urn:ietf:params:oauth:grant-type:token-exchange за краен пункт на токен OIDC. Обменът на токени може да се използва за заявка на токени с по-малък обхват или за имперсонализиране на други потребители. Вижте политиката за сигурност, за да разрешите имперсонализацията на инстанция.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Тригери за проекции на осмисляне на OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Активиране на тригери за проекции по време на заявка за осмисляне. Това може да действа като обходен механизъм, ако има забележими проблеми с консистентността в отговора на осмислянето, но може да окаже влияние върху производителността. Планираме да премахнем тригерите за заявки за осмисляне в бъдеще.", + "USERSCHEMA": "Потребителска схема", + "USERSCHEMA_DESCRIPTION": "Потребителските схеми позволяват управление на данните за схемите на потребителите. Ако е активиран флагът, ще можете да използвате новото API и неговите функции.", + "ACTIONS": "Действия", + "ACTIONS_DESCRIPTION": "Действия v2 позволяват управление на выполнения на данни и цели. Ако флагът е активиран, ще можете да използвате новия API и неговите функции.", + "STATES": { + "INHERITED": "Наследено", + "ENABLED": "Активирано", + "DISABLED": "Деактивирано" + }, + "INHERITED_DESCRIPTION": "Тази настройка задава стойността по подразбиране на системата.", + "RESET": "Задай всички на наследено" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Нулиране на настройката", @@ -1582,6 +1611,7 @@ "initPasswordText": "Инициализиране на парола", "initializeDoneText": "Инициализирането на потребителя е готово", "initializeUserText": "Инициализирайте потребителя", + "linkingUserPromptText": "Потребителският промпт за свързване", "linkingUserDoneText": "Свързването на потребителя е готово", "loginText": "Влизам", "logoutText": "Излез от профила си", @@ -1653,7 +1683,8 @@ "HASLOWERCASE": "има малки букви", "HASUPPERCASE": "има главни букви", "SHOWLOCKOUTFAILURES": "показва грешки при блокиране", - "MAXATTEMPTS": "Максимален брой опити за парола", + "MAXPASSWORDATTEMPTS": "Максимален брой опити за парола", + "MAXOTPATTEMPTS": "Максимален брой опити за OTP", "EXPIREWARNDAYS": "Предупреждение за изтичане след ден", "MAXAGEDAYS": "Максимална възраст в дни", "USERLOGINMUSTBEDOMAIN": "Добавяне на домейн на организация като суфикс към имената за вход", @@ -2013,7 +2044,13 @@ "ISCREATIONALLOWED": "Създаването на акаунт е разрешено", "ISCREATIONALLOWED_DESC": "Определя дали могат да се създават акаунти.", "ISLINKINGALLOWED": "Свързването на акаунти е разрешено", - "ISLINKINGALLOWED_DESC": "Определя дали дадена самоличност може да бъде свързана със съществуващ акаунт." + "ISLINKINGALLOWED_DESC": "Определя дали дадена самоличност може да бъде свързана със съществуващ акаунт.", + "AUTOLINKING_DESC": "Определя дали идентичността ще бъде подканена да бъде свързана със съществуващ профил.", + "AUTOLINKINGTYPE": { + "0": "Изключено", + "1": "Проверка за съществуващо потребителско име", + "2": "Проверка за съществуващ имейл" + } }, "OWNERTYPES": { "0": "неизвестен", @@ -2174,6 +2211,48 @@ "1": "Позволен" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP доставчик", + "DESCRIPTION": "Това са SMTP доставчиците за вашето копие на Zitadel. Активирайте този, който искате да използвате, за да изпращате известия до вашите потребители.", + "EMPTY": "Няма наличен SMTP доставчик", + "ACTIVATED": "Активиран", + "ACTIVATE": "Активирайте доставчика", + "DEACTIVATE": "Деактивирайте доставчика", + "TYPE": "Тип", + "DIALOG": { + "ACTIVATED": "SMTP конфигурацията е активирана", + "ACTIVATE_WARN_TITLE": "Активирайте SMTP конфигурацията", + "ACTIVATE_WARN_DESCRIPTION": "На път сте да активирате SMTP конфигурация. Първо ще деактивираме текущия активен доставчик и след това ще активираме тази конфигурация. Сигурен ли си?", + "DEACTIVATE_WARN_TITLE": "Деактивирайте SMTP конфигурацията", + "DEACTIVATE_WARN_DESCRIPTION": "На път сте да деактивирате SMTP конфигурация. Сигурен ли си?", + "DEACTIVATED": "SMTP конфигурацията е деактивирана", + "DELETE_TITLE": "Изтриване на SMTP конфигурация", + "DELETE_DESCRIPTION": "На път сте да изтриете конфигурация. Потвърдете това действие, като въведете името на подателя", + "DELETED": "SMTP конфигурацията е изтрита", + "SENDER": "Въведете {{ value }}, за да изтриете тази SMTP конфигурация." + } + }, + "CREATE": { + "TITLE": "Добавете SMTP доставчик", + "DESCRIPTION": "Изберете един или повече от следните доставчици.", + "STEPS": { + "TITLE": "Добавете {{ value }} SMTP доставчик", + "CREATE_DESC_TITLE": "Въведете своите {{ value }} SMTP настройки стъпка по стъпка", + "CURRENT_DESC_TITLE": "Това са вашите SMTP настройки", + "PROVIDER_SETTINGS": "Настройки на SMTP доставчик", + "SENDER_SETTINGS": "Настройки на изпращача", + "TEST_SETTINGS": "Тествайте настройките на SMTP" + } + }, + "DETAIL": { + "TITLE": "Настройки на SMTP доставчик" + }, + "EMPTY": "Няма наличен SMTP доставчик", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Приложения", "COMPLIANCE": "Съответствие с OIDC", diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json index c893c5d8c3..c9a01468ac 100644 --- a/console/src/assets/i18n/cs.json +++ b/console/src/assets/i18n/cs.json @@ -86,6 +86,10 @@ "TITLE": "Nastavení organizace", "DESCRIPTION": "Přizpůsobte nastavení vaší organizace." }, + "FEATURES": { + "TITLE": "Nastavení funkcí", + "DESCRIPTION": "Odemkněte funkce pro vaši instanci." + }, "IDPS": { "TITLE": "Poskytovatelé identity", "DESCRIPTION": "Vytvořte a aktivujte externí poskytovatele identity. Vyberte známého poskytovatele nebo nakonfigurujte jakýkoliv jiný OIDC, OAuth nebo SAML kompatibilní poskytovatel podle vašeho výběru. Můžete dokonce použít vaše stávající JWT tokeny jako federované identity konfigurací JWT poskytovatele identity.", @@ -1315,6 +1319,7 @@ }, "LIST": { "ORGS": "Organizace", + "FEATURESETTINGS": "Nastavení funkcí", "LANGUAGES": "Jazyky", "LOGIN": "Chování při přihlášení a bezpečnost", "LOCKOUT": "Blokování", @@ -1376,6 +1381,8 @@ } }, "SMTP": { + "TITLE": "Nastavení SMTP", + "DESCRIPTION": "Popis", "SENDERADDRESS": "E-mailová adresa odesílatele", "SENDERNAME": "Jméno odesílatele", "REPLYTOADDRESS": "Adresa pro odpovědi", @@ -1386,6 +1393,7 @@ "PASSWORDSET": "SMTP heslo bylo úspěšně nastaveno.", "TLS": "Zabezpečení transportní vrstvy (TLS)", "SAVED": "Úspěšně uloženo!", + "NOCHANGES": "Žádné změny!", "REQUIREDWARN": "Pro odesílání oznámení z vaší domény musíte zadat vaše SMTP údaje." }, "SMS": { @@ -1445,6 +1453,27 @@ "IMPERSONATIONENABLED": "Povolit předstírání jiné identity", "IMPERSONATIONDESCRIPTION": "Toto nastavení v zásadě umožňuje používat zosobnění. Všimněte si, že imitátor potřebuje také přiřazené příslušné role `*_IMPERSONATOR`." }, + "FEATURES": { + "LOGINDEFAULTORG": "Výchozí organizace pro přihlášení", + "LOGINDEFAULTORG_DESCRIPTION": "Přihlašovací rozhraní použije nastavení výchozí organizace (a ne z instance), pokud není nastaven žádný kontext organizace.", + "OIDCLEGACYINTROSPECTION": "Dědictví OIDC introspekce", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Nedávno jsme přepracovali bod introspekce z výkonnostních důvodů. Tato funkce lze použít k rollbacku na dědickou implementaci, pokud se objeví neočekávané chyby.", + "OIDCTOKENEXCHANGE": "Výměna tokenů OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Povolit experimentální typ udělení urn:ietf:params:oauth:grant-type:token-exchange pro bod tokenového bodu OIDC. Výměna tokenů lze použít k žádosti o tokeny s menším rozsahem nebo k impersonaci jiných uživatelů. Podívejte se na bezpečnostní politiku, abyste umožnili impersonaci na instanci.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Spouštěče projekcí introspekce OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Povolit spouštěče projekcí během požadavku na introspekci. To může sloužit jako obcházení, pokud existují zjevné problémy s konzistencí v odpovědi na introspekci, ale může to mít vliv na výkon. Plánujeme odstranit spouštěče pro požadavky na introspekci v budoucnosti.", + "USERSCHEMA": "Schéma uživatele", + "USERSCHEMA_DESCRIPTION": "Schémata uživatelů umožňují spravovat datová schémata uživatelů. Pokud je příznak povolen, budete moci používat nové API a jeho funkce.", + "ACTIONS": "Akce", + "ACTIONS_DESCRIPTION": "Akce v2 umožňují správu datových provedení a cílů. Pokud je tento příznak povolen, budete moci používat nové API a jeho funkce.", + "STATES": { + "INHERITED": "Děděno", + "ENABLED": "Povoleno", + "DISABLED": "Zakázáno" + }, + "INHERITED_DESCRIPTION": "Toto nastavení nastaví hodnotu na výchozí hodnotu systému.", + "RESET": "Nastavit vše na děděné" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Resetovat nastavení", @@ -1589,6 +1618,7 @@ "initPasswordText": "Inicializace hesla", "initializeDoneText": "Inicializace uživatele dokončena", "initializeUserText": "Inicializace uživatele", + "linkingUserPromptText": "Uživatelský propojovací text", "linkingUserDoneText": "Propojení uživatele dokončeno", "loginText": "Přihlášení", "logoutText": "Odhlášení", @@ -1660,7 +1690,8 @@ "HASLOWERCASE": "obsahuje malá písmena", "HASUPPERCASE": "obsahuje velká písmena", "SHOWLOCKOUTFAILURES": "zobrazit neúspěšné pokusy o uzamčení", - "MAXATTEMPTS": "Maximální počet pokusů o heslo", + "MAXPASSWORDATTEMPTS": "Maximální počet pokusů o heslo", + "MAXOTPATTEMPTS": "Maximální počet pokusů o OTP", "EXPIREWARNDAYS": "Upozornění na expiraci po dni", "MAXAGEDAYS": "Maximální stáří v dnech", "USERLOGINMUSTBEDOMAIN": "Přidat doménu organizace jako příponu k přihlašovacím jménům", @@ -2024,7 +2055,13 @@ "ISCREATIONALLOWED": "Je povoleno vytváření účtu", "ISCREATIONALLOWED_DESC": "Určuje, zda lze vytvářet účty.", "ISLINKINGALLOWED": "Je povoleno propojení účtů", - "ISLINKINGALLOWED_DESC": "Určuje, zda lze identitu propojit s existujícím účtem." + "ISLINKINGALLOWED_DESC": "Určuje, zda lze identitu propojit s existujícím účtem.", + "AUTOLINKING_DESC": "Určuje, zda se bude identita vyzývat k propojení se stávajícím účtem.", + "AUTOLINKINGTYPE": { + "0": "Vypnuto", + "1": "Kontrola existence uživatelského jména", + "2": "Kontrola existence e-mailu" + } }, "OWNERTYPES": { "0": "neznámý", @@ -2193,6 +2230,48 @@ "1": "Povoleno" } }, + "SMTP": { + "LIST": { + "TITLE": "Poskytovatel SMTP", + "DESCRIPTION": "Toto jsou poskytovatelé SMTP pro vaši instanci Zitadel. Aktivujte ten, který chcete používat k odesílání upozornění svým uživatelům.", + "EMPTY": "Není k dispozici žádný poskytovatel SMTP", + "ACTIVATED": "Aktivováno", + "ACTIVATE": "Aktivujte poskytovatele", + "DEACTIVATE": "Deaktivovat poskytovatele", + "TYPE": "Typ", + "DIALOG": { + "ACTIVATED": "Konfigurace SMTP byla aktivována", + "ACTIVATE_WARN_TITLE": "Aktivujte konfiguraci SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Chystáte se aktivovat konfiguraci SMTP. Nejprve deaktivujeme aktuálního aktivního poskytovatele a poté aktivujeme tuto konfiguraci. Jsi si jistá?", + "DEACTIVATE_WARN_TITLE": "Deaktivujte konfiguraci SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Chystáte se deaktivovat konfiguraci SMTP. Jsi si jistá?", + "DEACTIVATED": "Konfigurace SMTP byla deaktivována", + "DELETE_TITLE": "Smazat konfiguraci SMTP", + "DELETE_DESCRIPTION": "Chystáte se smazat konfiguraci. Potvrďte tuto akci zadáním jména odesílatele", + "DELETED": "Konfigurace SMTP byla smazána", + "SENDER": "Chcete-li smazat tuto konfiguraci SMTP, zadejte {{ value }}." + } + }, + "CREATE": { + "TITLE": "Přidat poskytovatele SMTP", + "DESCRIPTION": "Vyberte jednoho nebo více z následujících poskytovatelů.", + "STEPS": { + "TITLE": "Přidejte {{ value }} poskytovatele SMTP", + "CREATE_DESC_TITLE": "Krok za krokem zadejte nastavení SMTP {{ value }}", + "CURRENT_DESC_TITLE": "Toto jsou vaše nastavení SMTP", + "PROVIDER_SETTINGS": "Nastavení poskytovatele SMTP", + "SENDER_SETTINGS": "Nastavení odesílatele", + "TEST_SETTINGS": "Otestujte nastavení SMTP" + } + }, + "DETAIL": { + "TITLE": "Nastavení poskytovatele SMTP" + }, + "EMPTY": "Není k dispozici žádný poskytovatel SMTP", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Aplikace", "COMPLIANCE": "OIDC Kompatibilita", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index e0f403b07b..7ea9423b05 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -86,6 +86,10 @@ "TITLE": "Organisationseinstellungen", "DESCRIPTION": "Passe die Einstellungen deiner Organisation an." }, + "FEATURES": { + "TITLE": "Funktionseinstellungen", + "DESCRIPTION": "Schalten Sie Funktionen für Ihre Instanz frei." + }, "IDPS": { "TITLE": "Identitätsanbieter", "DESCRIPTION": "Erstelle und aktiviere externe Identitätsanbieter. Wähle einen bekannten Anbieter aus oder konfiguriere einen anderen OIDC-, OAuth- oder SAML-kompatiblen Anbieter deiner Wahl. Du kannst sogar deine vorhandenen JWT-Tokens als föderierte Identitäten verwenden, indem du einen JWT-Identitätsanbieter konfigurierst.", @@ -1314,6 +1318,7 @@ }, "LIST": { "ORGS": "Organisationen", + "FEATURESETTINGS": "Features", "LANGUAGES": "Sprachen", "LOGIN": "Loginverhalten und Sicherheit", "LOCKOUT": "Sperrmechanismen", @@ -1375,6 +1380,8 @@ } }, "SMTP": { + "TITLE": "SMTP Einstellungen", + "DESCRIPTION": "Beschreibung", "SENDERADDRESS": "Sender Email-Adresse", "SENDERNAME": "Sender Name", "REPLYTOADDRESS": "Reply-to-Adresse", @@ -1385,6 +1392,7 @@ "PASSWORDSET": "SMTP Passwort erfolgreich gesetzt.", "TLS": "Transport Layer Security (TLS)", "SAVED": "Erfolgreich gespeichert.", + "NOCHANGES": "Keine Änderungen!", "REQUIREDWARN": "Damit Mails von Ihrer Domain verschickt werden können, müssen Sie Ihre SMTP Einstellungen konfigurieren." }, "SMS": { @@ -1444,6 +1452,27 @@ "IMPERSONATIONENABLED": "Identitätswechsel zulassen", "IMPERSONATIONDESCRIPTION": "Diese Einstellung ermöglicht grundsätzlich die Verwendung von Identitätswechseln. Beachten Sie, dass dem Imitator auch die entsprechenden `*_IMPERSONATOR`-Rollen zugewiesen werden müssen." }, + "FEATURES": { + "LOGINDEFAULTORG": "Standardorganisation für die Anmeldung", + "LOGINDEFAULTORG_DESCRIPTION": "Die Anmelde-Benutzeroberfläche verwendet die Einstellungen der Standardorganisation (und nicht von der Instanz), wenn kein Organisationskontext festgelegt ist.", + "OIDCLEGACYINTROSPECTION": "OIDC Legacy-Introspektion", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Wir haben kürzlich den Introspektionsendpunkt aus Leistungsgründen neu strukturiert. Mit diesem Feature können Sie zur alten Implementierung zurückkehren, falls unerwartete Fehler auftreten.", + "OIDCTOKENEXCHANGE": "OIDC Token-Austausch", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Aktivieren Sie den experimentellen urn:ietf:params:oauth:grant-type:token-exchange-Grant-Typ für den OIDC-Token-Endpunkt. Der Token-Austausch kann verwendet werden, um Token mit einem geringeren Umfang anzufordern oder andere Benutzer zu impersonieren. Siehe die Sicherheitsrichtlinie, um die Impersonation auf einer Instanz zu erlauben.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "OIDC Trigger-Introspektionsprojektionen", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Aktivieren Sie Projektionstrigger während einer Introspektionsanfrage. Dies kann als Workaround fungieren, wenn bemerkbare Konsistenzprobleme in der Introspektionsantwort auftreten, kann sich jedoch auf die Leistung auswirken. Wir planen, Trigger für Introspektionsanfragen in Zukunft zu entfernen.", + "USERSCHEMA": "Benutzerschema", + "USERSCHEMA_DESCRIPTION": "Benutzerschemata ermöglichen das Verwalten von Datenschemata von Benutzern. Wenn die Flagge aktiviert ist, können Sie die neue API und ihre Funktionen verwenden.", + "ACTIONS": "Aktionen", + "ACTIONS_DESCRIPTION": "Aktionen v2 ermöglichen die Verwaltung von Datenausführungen und Zielen. Wenn das Flag aktiviert ist, können Sie die neue API und ihre Funktionen verwenden.", + "STATES": { + "INHERITED": "Erben", + "ENABLED": "Aktiviert", + "DISABLED": "Deaktiviert" + }, + "INHERITED_DESCRIPTION": "Diese Einstellung setzt den Wert auf den Standardwert des Systems.", + "RESET": "Alle auf Erben setzen" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Einstellungen zurücksetzen", @@ -1588,6 +1617,7 @@ "initPasswordText": "Passwort Initialisierung", "initializeDoneText": "Benutzereinrichtung erfolgreich", "initializeUserText": "Benutzereinrichtung", + "linkingUserPromptText": "Aufforderung zur Benutzerverlinkung", "linkingUserDoneText": "Benutzerverlinkung erfolgreich", "loginText": "Anmelden", "logoutText": "Abmelden", @@ -1659,7 +1689,8 @@ "HASLOWERCASE": "erfordert Kleinbuchstaben", "HASUPPERCASE": "erfordert Grossbuchstaben", "SHOWLOCKOUTFAILURES": "Zeige Anzahl Anmeldeversuche", - "MAXATTEMPTS": "Maximale Anzahl an Versuchen", + "MAXPASSWORDATTEMPTS": "Maximale Anzahl an Passwort Versuchen", + "MAXOTPATTEMPTS": "Maximale Anzahl an OTP Versuchen", "EXPIREWARNDAYS": "Ablauf Warnung nach Tagen", "MAXAGEDAYS": "Maximale Gültigkeit in Tagen", "USERLOGINMUSTBEDOMAIN": "Organisationsdomain dem Loginname hinzufügen", @@ -2014,7 +2045,13 @@ "ISCREATIONALLOWED": "Account erstellen erlaubt", "ISCREATIONALLOWED_DESC": "Legt fest, ob Konten erstellt werden können.", "ISLINKINGALLOWED": "Account linking erlaubt", - "ISLINKINGALLOWED_DESC": "Legt fest, ob eine Identität mit einem bestehenden Konto verknüpft werden kann." + "ISLINKINGALLOWED_DESC": "Legt fest, ob eine Identität mit einem bestehenden Konto verknüpft werden kann.", + "AUTOLINKING_DESC": "Legt fest, ob eine Identität aufgefordert wird, mit einem vorhandenen Konto verknüpft zu werden.", + "AUTOLINKINGTYPE": { + "0": "Deaktiviert", + "1": "Überprüfung auf vorhandenen Benutzernamen", + "2": "Überprüfung auf vorhandene E-Mail" + } }, "OWNERTYPES": { "0": "unknown", @@ -2183,6 +2220,48 @@ "1": "Erlaubt" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP-Anbieter", + "DESCRIPTION": "Dies sind die SMTP-Anbieter für Ihre Zitadel-Instanz. Aktivieren Sie diejenige, die Sie zum Senden von Benachrichtigungen an Ihre Benutzer verwenden möchten.", + "EMPTY": "Kein SMTP-Anbieter verfügbar", + "ACTIVATED": "Aktiviert", + "ACTIVATE": "Anbieter aktivieren", + "DEACTIVATE": "Anbieter deaktivieren", + "TYPE": "Typ", + "DIALOG": { + "ACTIVATED": "Die SMTP-Konfiguration wurde aktiviert", + "ACTIVATE_WARN_TITLE": "Aktivieren Sie die SMTP-Konfiguration", + "ACTIVATE_WARN_DESCRIPTION": "Sie sind dabei, eine SMTP-Konfiguration zu aktivieren. Zuerst deaktivieren wir den aktuell aktiven Anbieter und aktivieren dann diese Konfiguration. Bist du sicher?", + "DEACTIVATE_WARN_TITLE": "Deaktivieren Sie die SMTP-Konfiguration", + "DEACTIVATE_WARN_DESCRIPTION": "Sie sind dabei, eine SMTP-Konfiguration zu deaktivieren. Bist du sicher?", + "DEACTIVATED": "Die SMTP-Konfiguration wurde deaktiviert", + "DELETE_TITLE": "SMTP-Konfiguration löschen", + "DELETE_DESCRIPTION": "Sie sind dabei, eine Konfiguration zu löschen. Bestätigen Sie diese Aktion, indem Sie den Absendernamen eingeben", + "DELETED": "SMTP-Konfiguration wurde gelöscht", + "SENDER": "Geben Sie {{ value }} ein, um diese SMTP-Konfiguration zu löschen." + } + }, + "CREATE": { + "TITLE": "SMTP-Anbieter hinzufügen", + "DESCRIPTION": "Wählen Sie einen oder mehrere der folgenden Anbieter aus.", + "STEPS": { + "TITLE": "Fügen Sie {{ value }} SMTP-Anbieter hinzu", + "CREATE_DESC_TITLE": "Geben Sie Schritt für Schritt Ihre {{ value }} SMTP-Einstellungen ein", + "CURRENT_DESC_TITLE": "Dies sind Ihre SMTP-Einstellungen", + "PROVIDER_SETTINGS": "SMTP-Anbietereinstellungen", + "SENDER_SETTINGS": "Absendereinstellungen", + "TEST_SETTINGS": "Testen Sie die SMTP-Einstellungen" + } + }, + "DETAIL": { + "TITLE": "SMTP-Anbietereinstellungen" + }, + "EMPTY": "Kein SMTP-Anbieter verfügbar", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Apps", "COMPLIANCE": "OIDC Einhaltung", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 72cee6c0f3..658f59ba9e 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -86,6 +86,10 @@ "TITLE": "Organization Settings", "DESCRIPTION": "Customize the settings of your organization." }, + "FEATURES": { + "TITLE": "Feature Settings", + "DESCRIPTION": "Unlock features for your instance." + }, "IDPS": { "TITLE": "Identity Providers", "DESCRIPTION": "Create and activate external identity providers. Choose a well-known provider or configure any other OIDC, OAuth or SAML compatible provider of your choice. You can even use your existing JWT tokens as federated identities by configuring a JWT identity provider.", @@ -1315,6 +1319,7 @@ }, "LIST": { "ORGS": "Organizations", + "FEATURESETTINGS": "Features", "LANGUAGES": "Languages", "LOGIN": "Login Behavior and Security", "LOCKOUT": "Lockout", @@ -1376,6 +1381,8 @@ } }, "SMTP": { + "TITLE": "SMTP Provider", + "DESCRIPTION": "Description", "SENDERADDRESS": "Sender Email Address", "SENDERNAME": "Sender Name", "REPLYTOADDRESS": "Reply-to Address", @@ -1386,6 +1393,7 @@ "PASSWORDSET": "SMTP Password was set successfully.", "TLS": "Transport Layer Security (TLS)", "SAVED": "Saved successfully!", + "NOCHANGES": "No changes!", "REQUIREDWARN": "To send notifications from your domain, you have to enter your SMTP data." }, "SMS": { @@ -1445,6 +1453,27 @@ "IMPERSONATIONENABLED": "Allow Impersonation", "IMPERSONATIONDESCRIPTION": "This setting allows to use impersonation in principle. Note that the impersonator needs the appropriate `*_IMPERSONATOR` roles assigned as well." }, + "FEATURES": { + "LOGINDEFAULTORG": "Login Default Org", + "LOGINDEFAULTORG_DESCRIPTION": "The login UI will use the settings of the default org (and not from the instance) if no organization context is set", + "OIDCLEGACYINTROSPECTION": "OIDC Legacy introspection", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "We have recently refactored the introspection endpoint for performance reasons. This feature can be used to rollback to the legacy implementation if unexpected bugs arise.", + "OIDCTOKENEXCHANGE": "OIDC Token Exchange", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Enable the experimental urn:ietf:params:oauth:grant-type:token-exchange grant type for the OIDC token endpoint. Token exchange can be used to request tokens with a lesser scope or impersonate other users. See the security policy to allow impersonation on an instance.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "OIDC Trigger introspection Projections", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Enable projection triggers during an introspection request. This can act as workaround if there are noticeable consistency issues in the introspection response but can have an impact on performance. We are planning to remove triggers for introspection requests in the future.", + "USERSCHEMA": "User Schema", + "USERSCHEMA_DESCRIPTION": "User Schemas allow to manage data schemas of user. If the flag is enabled, you'll be able to use the new API and its features.", + "ACTIONS": "Actions", + "ACTIONS_DESCRIPTION": "Actions v2 allow to manage data executions and targets. If the flag is enabled, you'll be able to use the new API and its features.", + "STATES": { + "INHERITED": "Inherit", + "ENABLED": "Enabled", + "DISABLED": "Disabled" + }, + "INHERITED_DESCRIPTION": "This sets the value to the default value of the system.", + "RESET": "Set all to inherit" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Reset Setting", @@ -1589,6 +1618,7 @@ "initPasswordText": "Initialize password", "initializeDoneText": "Initialize user done", "initializeUserText": "Initialize user", + "linkingUserPromptText": "Linking user prompt", "linkingUserDoneText": "Linking user done", "loginText": "Login", "logoutText": "Logout", @@ -1660,7 +1690,8 @@ "HASLOWERCASE": "must include a lowercase letter", "HASUPPERCASE": "must include an uppercase letter", "SHOWLOCKOUTFAILURES": "show lockout failures", - "MAXATTEMPTS": "Password maximum Attempts", + "MAXPASSWORDATTEMPTS": "Password maximum attempts", + "MAXOTPATTEMPTS": "OTP maximum attempts", "EXPIREWARNDAYS": "Expiration Warning after day", "MAXAGEDAYS": "Max Age in days", "USERLOGINMUSTBEDOMAIN": "Add organization domain as suffix to loginnames", @@ -2027,7 +2058,13 @@ "ISCREATIONALLOWED": "Account creation allowed", "ISCREATIONALLOWED_DESC": "Determines whether accounts can be created.", "ISLINKINGALLOWED": "Account linking allowed", - "ISLINKINGALLOWED_DESC": "Determines whether an identity can be linked to an existing account." + "ISLINKINGALLOWED_DESC": "Determines whether an identity can be linked to an existing account.", + "AUTOLINKING_DESC": "Determines whether an identity will be prompted to be linked to an existing account.", + "AUTOLINKINGTYPE": { + "0": "Disabled", + "1": "Check for existing Username", + "2": "Check for existing Email" + } }, "OWNERTYPES": { "0": "unknown", @@ -2202,6 +2239,48 @@ "1": "Allowed" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP Provider", + "DESCRIPTION": "These are the SMTP providers for your Zitadel instance. Activate the one you want to use to send notifications to your users.", + "EMPTY": "No SMTP Provider available", + "ACTIVATED": "Activated", + "ACTIVATE": "Activate provider", + "DEACTIVATE": "Deactivate provider", + "TYPE": "Type", + "DIALOG": { + "ACTIVATED": "SMTP config has been activated", + "ACTIVATE_WARN_TITLE": "Activate SMTP config", + "ACTIVATE_WARN_DESCRIPTION": "You are about to activate an SMTP configuration. First we'll deactivate the current active provider and then activate this configuration. Are you sure?", + "DEACTIVATE_WARN_TITLE": "Deactivate SMTP config", + "DEACTIVATE_WARN_DESCRIPTION": "You are about to deactivate an SMTP configuration. Are you sure?", + "DEACTIVATED": "SMTP config has been deactivated", + "DELETE_TITLE": "Delete SMTP config", + "DELETE_DESCRIPTION": "You are about to delete a configuration. Confirm this action typing the sender name", + "DELETED": "SMTP config has been deleted", + "SENDER": "Type {{value}}, to delete this SMTP configuration." + } + }, + "CREATE": { + "TITLE": "Add SMTP provider", + "DESCRIPTION": "Select one ore more of the following providers.", + "STEPS": { + "TITLE": "Add {{ value }} SMTP Provider", + "CREATE_DESC_TITLE": "Enter your {{ value }} SMTP settings step by step", + "CURRENT_DESC_TITLE": "These are your SMTP settings", + "PROVIDER_SETTINGS": "SMTP Provider Settings", + "SENDER_SETTINGS": "Sender Settings", + "TEST_SETTINGS": "Test SMTP Settings" + } + }, + "DETAIL": { + "TITLE": "SMTP Provider Settings" + }, + "EMPTY": "No SMTP provider available", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Applications", "COMPLIANCE": "OIDC Compliance", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 819c078905..ce50afa77d 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -86,6 +86,10 @@ "TITLE": "Configuración de la Organización", "DESCRIPTION": "Personaliza la configuración de tu organización." }, + "FEATURES": { + "TITLE": "Configuración de funciones", + "DESCRIPTION": "Desbloquee funciones para su instancia." + }, "IDPS": { "TITLE": "Proveedores de Identidad", "DESCRIPTION": "Crea y activa proveedores de identidad externos. Elige un proveedor conocido o configura cualquier otro proveedor compatible con OIDC, OAuth o SAML de tu elección. Incluso puedes usar tus tokens JWT existentes como identidades federadas configurando un proveedor de identidad JWT.", @@ -1316,6 +1320,7 @@ }, "LIST": { "ORGS": "Organizaciones", + "FEATURESETTINGS": "Ajustes de funcionalidades", "LANGUAGES": "Idiomas", "LOGIN": "Comportamiento del inicio de sesión y de la seguridad", "LOCKOUT": "Bloqueo", @@ -1377,6 +1382,8 @@ } }, "SMTP": { + "TITLE": "Ajustes SMTP", + "DESCRIPTION": "Descripción", "SENDERADDRESS": "Dirección email del emisor", "SENDERNAME": "Nombre del emisor", "REPLYTOADDRESS": "Dirección Reply-To", @@ -1387,6 +1394,7 @@ "PASSWORDSET": "La contraseña SMTP se estableció con éxito.", "TLS": "Transport Layer Security (TLS)", "SAVED": "¡Se guardó con éxito!", + "NOCHANGES": "¡Sin cambios!", "REQUIREDWARN": "Para enviar notificaciones para tu dominio, debes introducir tus datos SMTP." }, "SMS": { @@ -1446,6 +1454,27 @@ "IMPERSONATIONENABLED": "Permitir suplantación", "IMPERSONATIONDESCRIPTION": "Esta configuración permite utilizar la suplantación en principio. Tenga en cuenta que el imitador también necesita que se le asignen los roles `*_IMPERSONATOR` apropiados." }, + "FEATURES": { + "LOGINDEFAULTORG": "Organización predeterminada de inicio de sesión", + "LOGINDEFAULTORG_DESCRIPTION": "La interfaz de inicio de sesión utilizará la configuración de la organización predeterminada (y no de la instancia) si no se establece ningún contexto de organización.", + "OIDCLEGACYINTROSPECTION": "Introspección heredada OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Recientemente hemos refactorizado el punto de introspección por razones de rendimiento. Esta función se puede utilizar para volver a la implementación heredada si surgen errores inesperados.", + "OIDCTOKENEXCHANGE": "Intercambio de tokens OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Habilita el tipo de concesión experimental urn:ietf:params:oauth:grant-type:token-exchange para el punto de extremo de token OIDC. El intercambio de tokens se puede utilizar para solicitar tokens con un alcance menor o suplantar a otros usuarios. Consulta la política de seguridad para permitir la suplantación en una instancia.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Desencadenadores de proyecciones de introspección OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Habilita los desencadenadores de proyección durante una solicitud de introspección. Esto puede actuar como un mecanismo alternativo si hay problemas de coherencia perceptibles en la respuesta a la introspección, pero puede afectar al rendimiento. Estamos planeando eliminar los desencadenadores para las solicitudes de introspección en el futuro.", + "USERSCHEMA": "Esquema de usuario", + "USERSCHEMA_DESCRIPTION": "Los esquemas de usuario permiten gestionar los esquemas de datos de los usuarios. Si se activa la bandera, podrás utilizar la nueva API y sus funciones.", + "ACTIONS": "Acciones", + "ACTIONS_DESCRIPTION": "Acciones v2 permite administrar las ejecuciones y objetivos de datos. Si la bandera está habilitada, podrá utilizar la nueva API y sus funciones.", + "STATES": { + "INHERITED": "Heredado", + "ENABLED": "Habilitado", + "DISABLED": "Deshabilitado" + }, + "INHERITED_DESCRIPTION": "Esta configuración establece el valor al valor predeterminado del sistema.", + "RESET": "Establecer todo a heredado" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Restablecer ajuste", @@ -1590,6 +1619,7 @@ "initPasswordText": "Inicializar contraseña", "initializeDoneText": "Inicializar usuario, hecho", "initializeUserText": "Inicializar usuario", + "linkingUserPromptText": "Mensaje de enlace de usuario", "linkingUserDoneText": "Vinculación de usuario, hecho", "loginText": "Iniciar sesión", "logoutText": "Cerrar sesión", @@ -1661,7 +1691,8 @@ "HASLOWERCASE": "tiene minúsculas", "HASUPPERCASE": "tiene mayúsculas", "SHOWLOCKOUTFAILURES": "mostrar fallos de bloqueo", - "MAXATTEMPTS": "Intentos máximos", + "MAXPASSWORDATTEMPTS": "Intentos máximos de contraseña", + "MAXOTPATTEMPTS": "Intentos máximos de OTP", "EXPIREWARNDAYS": "Aviso de expiración después de estos días: ", "MAXAGEDAYS": "Antigüedad máxima en días", "USERLOGINMUSTBEDOMAIN": "Añadir el dominio de la organización como sufijo de los nombres de inicio de sesión", @@ -2020,7 +2051,13 @@ "ISCREATIONALLOWED": "Creación de cuentas permitida", "ISCREATIONALLOWED_DESC": "Determina si se pueden crear cuentas.", "ISLINKINGALLOWED": "Permitida la vinculación de cuentas", - "ISLINKINGALLOWED_DESC": "Determina si una identidad puede vincularse a una cuenta existente." + "ISLINKINGALLOWED_DESC": "Determina si una identidad puede vincularse a una cuenta existente.", + "AUTOLINKING_DESC": "Determina si se pedirá a una identidad que se vincule a una cuenta existente.", + "AUTOLINKINGTYPE": { + "0": "Desactivado", + "1": "Comprobar nombre de usuario existente", + "2": "Comprobar correo electrónico existente" + } }, "OWNERTYPES": { "0": "desconocido", @@ -2181,6 +2218,48 @@ "1": "Permitido" } }, + "SMTP": { + "LIST": { + "TITLE": "Proveedor SMTP", + "DESCRIPTION": "Estos son los proveedores SMTP para tu instancia de Zitadel. Activa el que quieras utilizar para enviar notificaciones a tus usuarios.", + "EMPTY": "No hay ningún proveedor SMTP disponible", + "ACTIVATED": "Activado", + "ACTIVATE": "Activar proveedor", + "DEACTIVATE": "Desactivar proveedor", + "TYPE": "Tipo", + "DIALOG": { + "ACTIVATED": "Tu configuración SMTP ha sido activada", + "ACTIVATE_WARN_TITLE": "Activar configuración SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Estás a punto de activar una configuración SMTP. Primero desactivaremos la configuración de tu proveedor actual y después activaremos esta configuración. ¿Estás seguro?", + "DEACTIVATE_WARN_TITLE": "Desactivar configuración SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Estás a punto de desactivar una configuración SMTP. ¿Está seguro?", + "DEACTIVATED": "La configuración SMTP ha sido desactivada", + "DELETE_TITLE": "Eliminar configuración SMTP", + "DELETE_DESCRIPTION": "Estás a punto de eliminar una configuración. Confirma esta acción escribiendo el nombre del remitente", + "DELETED": "La configuración SMTP ha sido eliminada", + "SENDER": "Escribe {{ value }} para eliminar esta configuración SMTP." + } + }, + "CREATE": { + "TITLE": "Agrega un proveedor SMTP", + "DESCRIPTION": "Selecciona uno o más de los siguientes proveedores.", + "STEPS": { + "TITLE": "Agrega un proveedor SMTP {{ value }} ", + "CREATE_DESC_TITLE": "Introduce tu configuración SMTP de {{ value }} paso a paso", + "CURRENT_DESC_TITLE": "Estas son tus configuraciones SMTP", + "PROVIDER_SETTINGS": "Configuración del proveedor SMTP", + "SENDER_SETTINGS": "Configuración del remitente", + "TEST_SETTINGS": "Probar la configuración SMTP" + } + }, + "DETAIL": { + "TITLE": "Configuración del proveedor SMTP" + }, + "EMPTY": "No hay ningún proveedor SMTP disponible", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Aplicaciones", "COMPLIANCE": "Cumplimiento OIDC", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index b88bed51b4..33a9bc11bb 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -86,6 +86,10 @@ "TITLE": "Paramètres de l'Organisation", "DESCRIPTION": "Personnalise les paramètres de ton organisation." }, + "FEATURES": { + "TITLE": "Paramètres des fonctionnalités", + "DESCRIPTION": "Débloquez des fonctionnalités pour votre instance." + }, "IDPS": { "TITLE": "Fournisseurs d'Identité", "DESCRIPTION": "Crée et active des fournisseurs d'identité externes. Choisis un fournisseur bien connu ou configure tout autre fournisseur compatible OIDC, OAuth ou SAML de ton choix. Tu peux même utiliser tes tokens JWT existants comme identités fédérées en configurant un fournisseur d'identité JWT.", @@ -1314,6 +1318,7 @@ }, "LIST": { "ORGS": "Organisations", + "FEATURESETTINGS": "Paramètres de fonctionnalité", "LANGUAGES": "Langues", "LOGIN": "Comportement de connexion et sécurité", "LOCKOUT": "Verrouillage", @@ -1375,6 +1380,8 @@ } }, "SMTP": { + "TITLE": "Paramètres SMTP", + "DESCRIPTION": "Description", "SENDERADDRESS": "Adresse e-mail de l'expéditeur", "SENDERNAME": "Nom de l'expéditeur", "REPLYTOADDRESS": "Adresse Reply-to", @@ -1385,6 +1392,7 @@ "PASSWORDSET": "Le mot de passe SMTP a été défini avec succès.", "TLS": "Sécurité de la couche de transport (TLS)", "SAVED": "Enregistré avec succès!", + "NOCHANGES": "Aucun changement!", "REQUIREDWARN": "Pour envoyer des notifications depuis votre domaine, vous devez entrer vos données SMTP." }, "SMS": { @@ -1444,6 +1452,27 @@ "IMPERSONATIONENABLED": "Autoriser l'usurpation d'identité", "IMPERSONATIONDESCRIPTION": "Ce paramètre permet en principe d'utiliser l'usurpation d'identité. Notez que l'usurpateur d'identité doit également recevoir les rôles `*_IMPERSONATOR` appropriés." }, + "FEATURES": { + "LOGINDEFAULTORG": "Organisation par défaut de connexion", + "LOGINDEFAULTORG_DESCRIPTION": "L'interface de connexion utilisera les paramètres de l'organisation par défaut (et non de l'instance) si aucun contexte d'organisation n'est défini.", + "OIDCLEGACYINTROSPECTION": "Introspection héritée OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Nous avons récemment refondu le point d'introspection pour des raisons de performances. Cette fonctionnalité peut être utilisée pour revenir à l'implémentation héritée en cas de bogues inattendus.", + "OIDCTOKENEXCHANGE": "Échange de jetons OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Activez le type de subvention expérimentale urn:ietf:params:oauth:grant-type:token-exchange pour le point de terminaison de jeton OIDC. L'échange de jetons peut être utilisé pour demander des jetons avec une portée moindre ou pour usurper d'autres utilisateurs. Consultez la politique de sécurité pour autoriser l'usurpation sur une instance.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Déclencheurs de projections d'introspection OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Activez les déclencheurs de projection lors d'une demande d'introspection. Cela peut agir comme un contournement s'il existe des problèmes de cohérence perceptibles dans la réponse à l'introspection, mais cela peut avoir un impact sur les performances. Nous prévoyons de supprimer les déclencheurs pour les demandes d'introspection à l'avenir.", + "USERSCHEMA": "Schéma utilisateur", + "USERSCHEMA_DESCRIPTION": "Les schémas utilisateur permettent de gérer les schémas de données des utilisateurs. Si le drapeau est activé, vous pourrez utiliser la nouvelle API et ses fonctionnalités.", + "ACTIONS": "Actions", + "ACTIONS_DESCRIPTION": "Les actions v2 permettent de gérer les exécutions et les cibles de données. Si l'indicateur est activé, vous pourrez utiliser la nouvelle API et ses fonctionnalités.", + "STATES": { + "INHERITED": "Hérité", + "ENABLED": "Activé", + "DISABLED": "Désactivé" + }, + "INHERITED_DESCRIPTION": "Ce paramètre définit la valeur par défaut du système.", + "RESET": "Réinitialiser tout sur hérité" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Réinitialiser les paramètres", @@ -1588,6 +1617,7 @@ "initPasswordText": "Initialiser le mot de passe", "initializeDoneText": "Initialiser l'utilisateur terminé", "initializeUserText": "Initialiser l'utilisateur", + "linkingUserPromptText": "Message de liaison utilisateur", "linkingUserDoneText": "Lier l'utilisateur fait", "loginText": "Connexion", "logoutText": "Déconnexion", @@ -1659,7 +1689,8 @@ "HASLOWERCASE": "a minuscule", "HASUPPERCASE": "a majuscule", "SHOWLOCKOUTFAILURES": "montrer les échecs de verrouillage", - "MAXATTEMPTS": "Mot de passe maximum Tentatives", + "MAXPASSWORDATTEMPTS": "Mot de passe maximum tentatives", + "MAXOTPATTEMPTS": "Maximal de tentatives OTP", "EXPIREWARNDAYS": "Expiration Avertissement après le jour", "MAXAGEDAYS": "Âge maximum en jours", "USERLOGINMUSTBEDOMAIN": "Le nom de connexion de l'utilisateur doit contenir le nom de domaine de l'organisation", @@ -2018,7 +2049,13 @@ "ISCREATIONALLOWED": "la création est-elle autorisée", "ISCREATIONALLOWED_DESC": "Détermine si des comptes peuvent être créés.", "ISLINKINGALLOWED": "la liaison est-elle autorisée", - "ISLINKINGALLOWED_DESC": "Détermine si une identité peut être liée à un compte existant." + "ISLINKINGALLOWED_DESC": "Détermine si une identité peut être liée à un compte existant.", + "AUTOLINKING_DESC": "Détermine si une identité sera invitée à être liée à un compte existant.", + "AUTOLINKINGTYPE": { + "0": "Désactivé", + "1": "Vérification de l'existence du nom d'utilisateur", + "2": "Vérification de l'existence de l'e-mail" + } }, "OWNERTYPES": { "0": "inconnu", @@ -2184,6 +2221,48 @@ "1": "Autorisée" } }, + "SMTP": { + "LIST": { + "TITLE": "Fournisseur SMTP", + "DESCRIPTION": "Ce sont les fournisseurs SMTP de votre instance Zitadel. Activez celui que vous souhaitez utiliser pour envoyer des notifications à vos utilisateurs.", + "EMPTY": "Aucun fournisseur SMTP disponible", + "ACTIVATED": "Activé", + "ACTIVATE": "Activer le fournisseur", + "DEACTIVATE": "Désactiver le fournisseur", + "TYPE": "Taper", + "DIALOG": { + "ACTIVATED": "La configuration SMTP a été activée", + "ACTIVATE_WARN_TITLE": "Activer la configuration SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Vous êtes sur le point d'activer une configuration SMTP. Nous allons d’abord désactiver le fournisseur actif actuel, puis activer cette configuration. Es-tu sûr?", + "DEACTIVATE_WARN_TITLE": "Désactiver la configuration SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Vous êtes sur le point de désactiver une configuration SMTP. Es-tu sûr?", + "DEACTIVATED": "La configuration SMTP a été désactivée", + "DELETE_TITLE": "Supprimer la configuration SMTP", + "DELETE_DESCRIPTION": "Vous êtes sur le point de supprimer une configuration. Confirmez cette action en tapant le nom de l'expéditeur", + "DELETED": "La configuration SMTP a été supprimée", + "SENDER": "Tapez {{ value }} pour supprimer cette configuration SMTP." + } + }, + "CREATE": { + "TITLE": "Ajouter un fournisseur SMTP", + "DESCRIPTION": "Sélectionnez un ou plusieurs des fournisseurs suivants.", + "STEPS": { + "TITLE": "Ajouter {{ value }} fournisseur SMTP", + "CREATE_DESC_TITLE": "Entrez vos paramètres SMTP {{ value }} étape par étape", + "CURRENT_DESC_TITLE": "Ce sont vos paramètres SMTP", + "PROVIDER_SETTINGS": "Paramètres du fournisseur SMTP", + "SENDER_SETTINGS": "Paramètres de l'expéditeur", + "TEST_SETTINGS": "Tester les paramètres SMTP" + } + }, + "DETAIL": { + "TITLE": "Paramètres du fournisseur SMTP" + }, + "EMPTY": "Aucun fournisseur SMTP disponible", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Applications", "COMPLIANCE": "Conformité à l'OIDC", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 5d1cc463ea..3a1e32a0d0 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -86,6 +86,10 @@ "TITLE": "Impostazioni dell'Organizzazione", "DESCRIPTION": "Personalizza le impostazioni della tua organizzazione." }, + "FEATURES": { + "TITLE": "Impostazioni delle funzionalità", + "DESCRIPTION": "Sblocca funzionalità per la tua istanza." + }, "IDPS": { "TITLE": "Fornitori di Identità", "DESCRIPTION": "Crea e attiva fornitori di identità esterni. Scegli un fornitore ben noto o configura qualsiasi altro fornitore compatibile con OIDC, OAuth o SAML della tua scelta. Puoi anche utilizzare i tuoi token JWT esistenti come identità federate configurando un fornitore di identità JWT.", @@ -1314,6 +1318,7 @@ }, "LIST": { "ORGS": "Organizzazioni", + "FEATURESETTINGS": "Impostazioni delle funzionalità", "LANGUAGES": "Lingue", "LOGIN": "Comportamento login e sicurezza", "LOCKOUT": "Meccanismi di bloccaggio", @@ -1375,6 +1380,8 @@ } }, "SMTP": { + "TITLE": "Impostazioni SMTP", + "DESCRIPTION": "Descrizione", "SENDERADDRESS": "Indirizzo email del mittente", "SENDERNAME": "Nome del mittente", "REPLYTOADDRESS": "Indirizzo Reply-to", @@ -1385,6 +1392,7 @@ "PASSWORDSET": "SMTP Password impostata con successo.", "TLS": "Transport Layer Security (TLS)", "SAVED": "Salvato con successo!", + "NOCHANGES": "Nessun cambiamento!", "REQUIREDWARN": "Per inviare notifiche dal tuo dominio, devi inserire i tuoi dati SMTP." }, "SMS": { @@ -1444,6 +1452,27 @@ "IMPERSONATIONENABLED": "Consenti la rappresentazione", "IMPERSONATIONDESCRIPTION": "Questa impostazione consente in linea di principio di utilizzare la rappresentazione. Tieni presente che il sosia ha bisogno anche dei ruoli `*_IMPERSONATOR` appropriati assegnati." }, + "FEATURES": { + "LOGINDEFAULTORG": "Organizzazione predefinita per l'accesso", + "LOGINDEFAULTORG_DESCRIPTION": "L'interfaccia di accesso utilizzerà le impostazioni dell'organizzazione predefinita (e non dell'istanza) se non è impostato alcun contesto organizzativo.", + "OIDCLEGACYINTROSPECTION": "Introspezione legacy OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Abbiamo recentemente ristrutturato il punto di introspezione per motivi di prestazioni. Questa funzionalità può essere utilizzata per tornare alla vecchia implementazione in caso di bug imprevisti.", + "OIDCTOKENEXCHANGE": "Scambio token OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Abilita il tipo di concessione sperimentale urn:ietf:params:oauth:grant-type:token-exchange per il punto finale del token OIDC. Lo scambio di token può essere utilizzato per richiedere token con uno scopo inferiore o impersonare altri utenti. Consultare la policy di sicurezza per consentire l'impersonificazione su un'istanza.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Proiezioni trigger OIDC per l'introspezione", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Abilita i trigger di proiezione durante una richiesta di introspezione. Questo può agire come soluzione alternativa se ci sono problemi di coerenza evidenti nella risposta all'introspezione, ma può influire sulle prestazioni. Stiamo pianificando di rimuovere i trigger per le richieste di introspezione in futuro.", + "USERSCHEMA": "Schema utente", + "USERSCHEMA_DESCRIPTION": "Gli schemi utente consentono di gestire gli schemi di dati degli utenti. Se la flag è attivata, sarà possibile utilizzare la nuova API e le sue funzionalità.", + "ACTIONS": "Azioni", + "ACTIONS_DESCRIPTION": "Le azioni v2 consentono di gestire le esecuzioni e gli obiettivi dei dati. Se l'indicatore è abilitato, potrai utilizzare la nuova API e le sue funzionalità.", + "STATES": { + "INHERITED": "Predefinito", + "ENABLED": "Abilitato", + "DISABLED": "Disabilitato" + }, + "INHERITED_DESCRIPTION": "Questa impostazione imposta il valore predefinito del sistema.", + "RESET": "Imposta tutto su predefinito" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Ripristina impostazioni", @@ -1588,6 +1617,7 @@ "initPasswordText": "Inizializzazione della password", "initializeDoneText": "Inizializzazione utente finita", "initializeUserText": "Inizializzazione utente", + "linkingUserPromptText": "Testo di promemoria per collegare l'utente", "linkingUserDoneText": "Collegamento dell'utente finito", "loginText": "Accesso", "logoutText": "Logout", @@ -1659,7 +1689,8 @@ "HASLOWERCASE": "ha la minuscola", "HASUPPERCASE": "ha la maiuscola", "SHOWLOCKOUTFAILURES": "mostra i fallimenti del blocco", - "MAXATTEMPTS": "Massimo numero di tentativi di password", + "MAXPASSWORDATTEMPTS": "Massimo numero di tentativi di password", + "MAXOTPATTEMPTS": "Massimo numero di tentativi di OTP", "EXPIREWARNDAYS": "Avviso scadenza dopo il giorno", "MAXAGEDAYS": "Lunghezza massima in giorni", "USERLOGINMUSTBEDOMAIN": "Nome utente deve contenere il dominio dell' organizzazione", @@ -2018,7 +2049,13 @@ "ISCREATIONALLOWED": "Creazione consentita", "ISCREATIONALLOWED_DESC": "Determina se i conti possono essere creati.", "ISLINKINGALLOWED": "Collegamento consentito", - "ISLINKINGALLOWED_DESC": "Determina se un'identità può essere collegata a un account esistente." + "ISLINKINGALLOWED_DESC": "Determina se un'identità può essere collegata a un account esistente.", + "AUTOLINKING_DESC": "Determina se un'identità verrà invitata a essere collegata a un account esistente.", + "AUTOLINKINGTYPE": { + "0": "Disabilitato", + "1": "Verifica dell'esistenza del nome utente", + "2": "Verifica dell'esistenza dell'email" + } }, "OWNERTYPES": { "0": "sconosciuto", @@ -2184,6 +2221,48 @@ "1": "Consentito" } }, + "SMTP": { + "LIST": { + "TITLE": "Fornitore SMTP", + "DESCRIPTION": "Questi sono i provider SMTP per la tua istanza Zitadel. Attiva quello che desideri utilizzare per inviare notifiche ai tuoi utenti.", + "EMPTY": "Nessun provider SMTP disponibile", + "ACTIVATED": "Attivato", + "ACTIVATE": "Attiva fornitore", + "DEACTIVATE": "Disattiva fornitore", + "TYPE": "Tipo", + "DIALOG": { + "ACTIVATED": "La configurazione SMTP è stata attivata", + "ACTIVATE_WARN_TITLE": "Attiva la configurazione SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Stai per attivare una configurazione SMTP. Per prima cosa disattiveremo il provider attualmente attivo e poi attiveremo questa configurazione. Sei sicuro?", + "DEACTIVATE_WARN_TITLE": "Disattiva la configurazione SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Stai per disattivare una configurazione SMTP. Sei sicuro?", + "DEACTIVATED": "La configurazione SMTP è stata disattivata", + "DELETE_TITLE": "Elimina configurazione SMTP", + "DELETE_DESCRIPTION": "Stai per eliminare una configurazione. Conferma questa azione digitando il nome del mittente", + "DELETED": "La configurazione SMTP è stata eliminata", + "SENDER": "Digita {{ value }} per eliminare questa configurazione SMTP." + } + }, + "CREATE": { + "TITLE": "Aggiungi provider SMTP", + "DESCRIPTION": "Seleziona uno o più dei seguenti fornitori.", + "STEPS": { + "TITLE": "Aggiungi {{ value }} provider SMTP", + "CREATE_DESC_TITLE": "Inserisci le tue impostazioni SMTP {{ value }} passo dopo passo", + "CURRENT_DESC_TITLE": "Queste sono le tue impostazioni SMTP", + "PROVIDER_SETTINGS": "Impostazioni del provider SMTP", + "SENDER_SETTINGS": "Impostazioni mittente", + "TEST_SETTINGS": "Testare le impostazioni SMTP" + } + }, + "DETAIL": { + "TITLE": "Impostazioni del provider SMTP" + }, + "EMPTY": "Nessun provider SMTP disponibile", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Applicazioni", "COMPLIANCE": "Conformità con OIDC", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 6036a62fe6..35b93bbb6b 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -86,10 +86,13 @@ "TITLE": "組織設定", "DESCRIPTION": "組織の設定をカスタマイズします。" }, + "FEATURES": { + "TITLE": "機能設定", + "DESCRIPTION": "インスタンスの機能のロックを解除します。" + }, "IDPS": { "TITLE": "IDプロバイダー", "DESCRIPTION": "外部IDプロバイダーを作成してアクティブにします。よく知られたプロバイダーを選択するか、他のOIDC、OAuth、SAML互換プロバイダーを設定します。既存のJWTトークンを連合アイデンティティとして使用するために、JWT IDプロバイダーを設定することもできます。", - "DESCRIPTION": "外部IDプロバイダーを作成して有効化します。よく知られているプロバイダーを選択するか、あるいはOIDC、OAuth、SAML互換の他のプロバイダーを自由に設定してください。既存のJWTトークンを、JWT IDプロバイダーを設定することで、フェデレーテッドアイデンティティとして使用することもできます。", "NEXT": "次は?", "SAML": { "TITLE": "SAML IDプロバイダーを設定する", @@ -1316,6 +1319,7 @@ }, "LIST": { "ORGS": "組織", + "FEATURESETTINGS": "機能設定", "LANGUAGES": "一般設定", "LOGIN": "ログイン動作とセキュリティ", "LOCKOUT": "ロックアウト", @@ -1377,6 +1381,8 @@ } }, "SMTP": { + "TITLE": "SMTP設定", + "DESCRIPTION": "説明", "SENDERADDRESS": "送信者のメールアドレス", "SENDERNAME": "送信者名", "REPLYTOADDRESS": "返信先アドレス", @@ -1387,6 +1393,7 @@ "PASSWORDSET": "SMTPパスワードは正常に設定されました。", "TLS": "Transport Layer Security (TLS)", "SAVED": "正常に保存されました!", + "NOCHANGES": "変更はありません!", "REQUIREDWARN": "ドメインから通知を送信するには、SMTP情報を入力する必要があります。" }, "SMS": { @@ -1446,6 +1453,27 @@ "IMPERSONATIONENABLED": "偽装を許可します", "IMPERSONATIONDESCRIPTION": "この設定では、原則として偽装を使用できます。偽装者には、適切な `*_IMPERSONATOR` ロールも割り当てられている必要があることに注意してください。" }, + "FEATURES": { + "LOGINDEFAULTORG": "ログイン時の既定組織", + "LOGINDEFAULTORG_DESCRIPTION": "組織コンテキストが設定されていない場合、ログイン UI は既定の組織の設定を使用します (インスタンスの設定ではなく)", + "OIDCLEGACYINTROSPECTION": "OIDC レガシーイントロスペクション", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "パフォーマンス上の理由から最近イントロスペクション エンドポイントをリファクタリングしました。この機能は、予期しないバグが発生した場合にレガシー実装にロールバックするために使用できます。", + "OIDCTOKENEXCHANGE": "OIDC トークン交換", + "OIDCTOKENEXCHANGE_DESCRIPTION": "OIDC トークン エンドポイント用に実験的な urn:ietf:params:oauth:grant-type:token-exchange 付与タイプを有効にします。トークン交換は、より少ないスコープを持つトークンを要求するか、他のユーザーになりすますために使用できます。インスタンスでのなりすましを許可するには、セキュリティポリシーを参照してください。", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "OIDC トリガーイントロスペクションプロジェクション", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "イントロスペクション要求中にプロジェクショントリガーを有効にします。これは、イントロスペクションレスポンスに顕著な整合性問題がある場合の回避策として機能しますが、パフォーマンスに影響を与える可能性があります。今後、イントロスペクション要求のトリガーを削除する予定です。", + "USERSCHEMA": "ユーザー スキーマ", + "USERSCHEMA_DESCRIPTION": "ユーザー スキーマを使用すると、ユーザーのデータスキーマを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。", + "ACTIONS": "アクション", + "ACTIONS_DESCRIPTION": "Actions v2は、データの実行とターゲットを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。", + "STATES": { + "INHERITED": "継承", + "ENABLED": "有効", + "DISABLED": "無効" + }, + "INHERITED_DESCRIPTION": "この設定は、値をシステムのデフォルト値に設定します。", + "RESET": "すべて継承に設定" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "設定のリセット", @@ -1585,6 +1613,7 @@ "initPasswordText": "パスワードを初期化する", "initializeDoneText": "ユーザーの初期化が完了しました", "initializeUserText": "ユーザーを初期化する", + "linkingUserPromptText": "ユーザーのリンクプロンプト", "linkingUserDoneText": "ユーザーのリンクが完了しました", "loginText": "ログイン", "logoutText": "ログアウト", @@ -1656,7 +1685,8 @@ "HASLOWERCASE": "小文字を含める", "HASUPPERCASE": "大文字を含める", "SHOWLOCKOUTFAILURES": "ロックアウトの失敗を表示する", - "MAXATTEMPTS": "パスワードの最大試行", + "MAXPASSWORDATTEMPTS": "パスワードの最大試行", + "MAXOTPATTEMPTS": "最大OTP試行回数", "EXPIREWARNDAYS": "有効期限の翌日以降の警告", "MAXAGEDAYS": "最大有効期限", "USERLOGINMUSTBEDOMAIN": "ログイン名の接尾辞として組織ドメインを追加する", @@ -2015,7 +2045,13 @@ "ISCREATIONALLOWED": "アカウント作成を許可", "ISCREATIONALLOWED_DESC": "アカウントを作成できるかどうかを決めます。", "ISLINKINGALLOWED": "アカウントリンクを許可", - "ISLINKINGALLOWED_DESC": "IDを既存のアカウントにリンクできるかどうかを決めます。" + "ISLINKINGALLOWED_DESC": "IDを既存のアカウントにリンクできるかどうかを決めます。", + "AUTOLINKING_DESC": "アイデンティティが既存のアカウントにリンクされるように促されるかどうかを決定します。", + "AUTOLINKINGTYPE": { + "0": "無効", + "1": "既存のユーザー名のチェック", + "2": "既存のメールアドレスのチェック" + } }, "OWNERTYPES": { "0": "不明", @@ -2176,6 +2212,48 @@ "1": "有効" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTPプロバイダー", + "DESCRIPTION": "これらは、Zitadel インスタンスの SMTP プロバイダーです。ユーザーに通知を送信するために使用するものをアクティブにします。", + "EMPTY": "使用可能な SMTP プロバイダーがありません", + "ACTIVATED": "アクティブ化された", + "ACTIVATE": "プロバイダーをアクティブ化する", + "DEACTIVATE": "プロバイダーを非アクティブ化する", + "TYPE": "タイプ", + "DIALOG": { + "ACTIVATED": "SMTP設定が有効化されました", + "ACTIVATE_WARN_TITLE": "SMTP設定を有効にする", + "ACTIVATE_WARN_DESCRIPTION": "SMTP 構成をアクティブにしようとしています。まず、現在アクティブなプロバイダーを非アクティブ化してから、この構成をアクティブ化します。本気ですか?", + "DEACTIVATE_WARN_TITLE": "SMTP設定を無効にする", + "DEACTIVATE_WARN_DESCRIPTION": "SMTP 構成を非アクティブ化しようとしています。本気ですか?", + "DEACTIVATED": "SMTP設定が無効化されました", + "DELETE_TITLE": "SMTP設定を削除する", + "DELETE_DESCRIPTION": "構成を削除しようとしています。送信者名を入力してこのアクションを確認します", + "DELETED": "SMTP設定が削除されました", + "SENDER": "この SMTP 構成を削除するには、「{{ value }}」と入力します。" + } + }, + "CREATE": { + "TITLE": "SMTPプロバイダーの追加", + "DESCRIPTION": "次のプロバイダーから 1 つ以上を選択します。", + "STEPS": { + "TITLE": "{{ value }} SMTP プロバイダーを追加", + "CREATE_DESC_TITLE": "{{ value }} SMTP 設定をステップごとに入力します", + "CURRENT_DESC_TITLE": "これらは SMTP 設定です", + "PROVIDER_SETTINGS": "SMTPプロバイダーの設定", + "SENDER_SETTINGS": "送信者の設定", + "TEST_SETTINGS": "SMTP設定をテストする" + } + }, + "DETAIL": { + "TITLE": "SMTPプロバイダーの設定" + }, + "EMPTY": "使用可能な SMTP プロバイダーがありません", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "アプリケーション", "COMPLIANCE": "OIDCコンプライアンス", diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index c7e68f3dad..03447afd33 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -86,6 +86,10 @@ "TITLE": "Поставки за организацијата", "DESCRIPTION": "Прилагодете ги поставките за вашата организација." }, + "FEATURES": { + "TITLE": "Поставки за функции", + "DESCRIPTION": "Отклучете ги функциите на вашиот пример." + }, "IDPS": { "TITLE": "Провајдери на идентитет", "DESCRIPTION": "Креирајте и активирајте надворешни провајдери на идентитет. Изберете познат провајдер или конфигурирајте било кој друг OIDC, OAuth или SAML компатибилен провајдер по ваш избор. Можете дури и да ги користите вашите постоечки JWT токени како федеративни идентитети со конфигурирање на JWT провајдер на идентитет.", @@ -1316,6 +1320,7 @@ }, "LIST": { "ORGS": "Организации", + "FEATURESETTINGS": "Подесувања на функцијата", "LANGUAGES": "Општо", "LOGIN": "Правила и безбедност при најава", "LOCKOUT": "Забрана на пристап", @@ -1377,6 +1382,8 @@ } }, "SMTP": { + "TITLE": "SMTP подесувања", + "DESCRIPTION": "Опис", "SENDERADDRESS": "Адреса на испраќачот", "SENDERNAME": "Име на испраќачот", "REPLYTOADDRESS": "Reply-to адреса", @@ -1387,6 +1394,7 @@ "PASSWORDSET": "SMTP лозинката е успешно поставена.", "TLS": "Transport Layer Security (TLS)", "SAVED": "Успешно зачувано!", + "NOCHANGES": "Нема промени!", "REQUIREDWARN": "За да испраќате известувања од вашиот домен, мора да ги внесете вашите SMTP податоци." }, "SMS": { @@ -1446,6 +1454,27 @@ "IMPERSONATIONENABLED": "Дозволи имитирање", "IMPERSONATIONDESCRIPTION": "Оваа поставка овозможува да се користи имитирање во принцип. Имајте предвид дека на имитаторот му требаат доделени соодветни улоги `*_IMPERSONATOR`" }, + "FEATURES": { + "LOGINDEFAULTORG": "Најава Стандардна организација", + "LOGINDEFAULTORG_DESCRIPTION": "Интерфејсот за најавување ќе ги користи поставките на стандардната организација (а не од примерот) ако не е поставен контекст на организацијата", + "OIDCLEGACYINTROSPECTION": "Интроспекција на наследството на OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Неодамна ја рефакториравме крајната точка на интроспекција поради перформанси. Оваа функција може да се користи за враќање на наследната имплементација доколку се појават неочекувани грешки.", + "OIDCTOKENEXCHANGE": "Размена на токени OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Овозможете го експерименталниот тип на грант urn:ietf:params:oauth:grant-type:token-exchange за крајната точка на токенот OIDC. Размената на токени може да се користи за барање токени со помал опсег или имитирање на други корисници. Погледнете ја безбедносната политика за да дозволите имитирање на пример.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Проекции за интроспекција на активирањето на OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Овозможете предизвикувачи за проекција за време на барање за интроспекција. Ова може да дејствува како заобиколување ако има забележителни проблеми со конзистентноста во одговорот на интроспекцијата, но може да има влијание врз перформансите. Планираме да ги отстраниме предизвикувачите за барањата за интроспекција во иднина.", + "USERSCHEMA": "Корисничка шема", + "USERSCHEMA_DESCRIPTION": "Корисничките шеми овозможуваат управување со податоци шеми на корисникот. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.", + "ACTIONS": "Акции", + "ACTIONS_DESCRIPTION": "Акциите v2 овозможуваат управување со извршување на податоци и цели. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.", + "STATES": { + "INHERITED": "Наследи", + "ENABLED": "Овозможено", + "DISABLED": "Оневозможено" + }, + "INHERITED_DESCRIPTION": "Оваа поставка ја постави вредноста на стандардната вредност на системот.", + "RESET": "Поставете ги сите да наследат" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Ресетирање на подесувањата", @@ -1590,6 +1619,7 @@ "initPasswordText": "Иницијализација на лозинка", "initializeDoneText": "Иницијализацијата на корисникот е завршена", "initializeUserText": "Иницијализација на корисник", + "linkingUserPromptText": "Поврзување на кориснички промпт", "linkingUserDoneText": "Поврзувањето на корисникот е завршено", "loginText": "Најава", "logoutText": "Одјава", @@ -1661,7 +1691,8 @@ "HASLOWERCASE": "има мали букви", "HASUPPERCASE": "има големи букви", "SHOWLOCKOUTFAILURES": "прикажи неуспешни заклучувања", - "MAXATTEMPTS": "Максимален број на обиди за лозинка", + "MAXPASSWORDATTEMPTS": "Максимален број на обиди за лозинка", + "MAXOTPATTEMPTS": "Максимални обиди за OTP", "EXPIREWARNDAYS": "Предупредување за истекување по ден", "MAXAGEDAYS": "Максимална возраст во денови", "USERLOGINMUSTBEDOMAIN": "Додади организациски домен како суфикс на корисничките имиња", @@ -2022,7 +2053,13 @@ "ISCREATIONALLOWED": "Дозволено креирање на кориснички сметки", "ISCREATIONALLOWED_DESC": "Одредува дали може да се креираат кориснички сметки.", "ISLINKINGALLOWED": "Дозволено поврзување на кориснички сметки", - "ISLINKINGALLOWED_DESC": "Одредува дали може да се поврзе идентитет со постоечка корисничка сметка." + "ISLINKINGALLOWED_DESC": "Одредува дали може да се поврзе идентитет со постоечка корисничка сметка.", + "AUTOLINKING_DESC": "Одредува дали ќе се бара идентитетот да биде поврзан со постоечки профил.", + "AUTOLINKINGTYPE": { + "0": "Исклучено", + "1": "Проверка за постоечко корисничко име", + "2": "Проверка за постоечка е-пошта" + } }, "OWNERTYPES": { "0": "непознато", @@ -2181,6 +2218,48 @@ "1": "Дозволено" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP провајдер", + "DESCRIPTION": "Ова се давателите на SMTP за вашиот пример Zitadel. Активирајте го оној што сакате да го користите за испраќање известувања до вашите корисници.", + "EMPTY": "Нема достапен SMTP провајдер", + "ACTIVATED": "Активиран", + "ACTIVATE": "Активирајте го провајдерот", + "DEACTIVATE": "Деактивирајте го провајдерот", + "TYPE": "Тип", + "DIALOG": { + "ACTIVATED": "SMTP конфигурацијата е активирана", + "ACTIVATE_WARN_TITLE": "Активирајте SMTP конфигурација", + "ACTIVATE_WARN_DESCRIPTION": "Ќе активирате SMTP конфигурација. Прво ќе го деактивираме тековниот активен провајдер, а потоа ќе ја активираме оваа конфигурација. Дали си сигурен?", + "DEACTIVATE_WARN_TITLE": "Деактивирајте ја конфигурацијата SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Ќе деактивирате SMTP конфигурација. Дали си сигурен?", + "DEACTIVATED": "SMTP конфигурацијата е деактивирана", + "DELETE_TITLE": "Избришете ја конфигурацијата SMTP", + "DELETE_DESCRIPTION": "Ќе избришете конфигурација. Потврдете го ова дејство со внесување на името на испраќачот", + "DELETED": "SMTP конфигурацијата е избришана", + "SENDER": "Внесете {{ value }}, за да ја избришете оваа SMTP конфигурација." + } + }, + "CREATE": { + "TITLE": "Додадете SMTP провајдер", + "DESCRIPTION": "Изберете еден или повеќе од следните провајдери.", + "STEPS": { + "TITLE": "Додадете {{ value }} SMTP провајдер", + "CREATE_DESC_TITLE": "Внесете ги вашите поставки за {{ value }} SMTP чекор по чекор", + "CURRENT_DESC_TITLE": "Ова се вашите поставки за SMTP", + "PROVIDER_SETTINGS": "Поставки на провајдерот SMTP", + "SENDER_SETTINGS": "Поставки на испраќачот", + "TEST_SETTINGS": "Тестирајте ги поставките за SMTP" + } + }, + "DETAIL": { + "TITLE": "Поставки на провајдерот SMTP" + }, + "EMPTY": "Нема достапен SMTP провајдер", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Апликации", "COMPLIANCE": "OIDC Соодветност", diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json index 1bfe7214b2..767db991fd 100644 --- a/console/src/assets/i18n/nl.json +++ b/console/src/assets/i18n/nl.json @@ -86,6 +86,10 @@ "TITLE": "Organisatie-instellingen", "DESCRIPTION": "Pas de instellingen van je organisatie aan." }, + "FEATURES": { + "TITLE": "Functie-instellingen", + "DESCRIPTION": "Ontgrendel functies voor uw exemplaar." + }, "IDPS": { "TITLE": "Identiteitsproviders", "DESCRIPTION": "Maak en activeer externe identiteitsproviders. Kies een bekende provider of configureer een andere OIDC, OAuth of SAML compatibele provider naar keuze. Je kunt zelfs je bestaande JWT-tokens gebruiken als gefedereerde identiteiten door een JWT-identiteitsprovider te configureren.", @@ -1315,6 +1319,7 @@ }, "LIST": { "ORGS": "Organisaties", + "FEATURESETTINGS": "Functie-instellingen", "LANGUAGES": "Talen", "LOGIN": "Login Gedrag en Beveiliging", "LOCKOUT": "Lockout", @@ -1376,6 +1381,8 @@ } }, "SMTP": { + "TITLE": "SMTP Instellingen", + "DESCRIPTION": "Beschrijving", "SENDERADDRESS": "Afzender Email Adres", "SENDERNAME": "Afzender Naam", "REPLYTOADDRESS": "Antwoord-naar Adres", @@ -1386,6 +1393,7 @@ "PASSWORDSET": "SMTP Wachtwoord is succesvol ingesteld.", "TLS": "Transport Layer Security (TLS)", "SAVED": "Succesvol opgeslagen!", + "NOCHANGES": "Geen veranderingen!", "REQUIREDWARN": "Om notificaties te kunnen versturen vanaf uw domein, moet u uw SMTP-gegevens invoeren." }, "SMS": { @@ -1445,6 +1453,27 @@ "IMPERSONATIONENABLED": "Imitatie toestaan", "IMPERSONATIONDESCRIPTION": "Deze instelling maakt het in principe mogelijk om imitatie te gebruiken. Houd er rekening mee dat aan de imitator ook de juiste `*_IMPERSONATOR` rollen moeten worden toegewezen." }, + "FEATURES": { + "LOGINDEFAULTORG": "Standaard inlogorganisatie", + "LOGINDEFAULTORG_DESCRIPTION": "Als er geen organisatiecontext is ingesteld, gebruikt de inlog-UI de instellingen van de standaardorganisatie (en niet van de instantie)", + "OIDCLEGACYINTROSPECTION": "Oude OIDC-introspectie", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "We hebben onlangs het introspectie-endpoint opnieuw gefactoreerd omwille van de prestaties. Deze functie kan worden gebruikt om terug te keren naar de oude implementatie als er onverwachte bugs optreden.", + "OIDCTOKENEXCHANGE": "OIDC-tokenuitwisseling", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Schakel het experimentele type verlening urn:ietf:params:oauth:grant-type:token-exchange in voor het OIDC-tokenendpoint. Tokenuitwisseling kan worden gebruikt om tokens met een kleinere scope op te vragen of om zich voor te doen als andere gebruikers. Raadpleeg het beveiligingsbeleid om impersonation op een instantie toe te staan.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "OIDC-triggers voor introspectieprojecties", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Schakel projectietriggers in tijdens een introspectieverzoek. Dit kan dienen als een tijdelijke oplossing als er merkbare consistentieproblemen optreden in het introspectieantwoord, maar het kan wel prestaties beïnvloeden. We zijn van plan om triggers voor introspectieverzoeken in de toekomst te verwijderen.", + "USERSCHEMA": "Gebruikerschema", + "USERSCHEMA_DESCRIPTION": "Met gebruikerschema's kunt u de dataschema's van gebruikers beheren. Als de vlag is ingeschakeld, kunt u de nieuwe API en zijn functies gebruiken.", + "ACTIONS": "Acties", + "ACTIONS_DESCRIPTION": "Actions v2 maken het mogelijk om data-uitvoeringen en doelen te beheren. Als de vlag is ingeschakeld, kunt u de nieuwe API en zijn functies gebruiken.", + "STATES": { + "INHERITED": "Overgenomen", + "ENABLED": "Ingeschakeld", + "DISABLED": "Uitgeschakeld" + }, + "INHERITED_DESCRIPTION": "Deze instelling stelt de waarde in op de standaardwaarde van het systeem.", + "RESET": "Alles instellen op overgenomen" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Reset Instelling", @@ -1589,6 +1618,7 @@ "initPasswordText": "Initialiseer wachtwoord", "initializeDoneText": "Gebruiker initialisatie voltooid", "initializeUserText": "Initialiseer gebruiker", + "linkingUserPromptText": "Gebruiker koppelingsprompt", "linkingUserDoneText": "Gebruiker koppeling voltooid", "loginText": "Login", "logoutText": "Uitloggen", @@ -1660,7 +1690,8 @@ "HASLOWERCASE": "heeft kleine letters", "HASUPPERCASE": "heeft hoofdletters", "SHOWLOCKOUTFAILURES": "toon lockout mislukkingen", - "MAXATTEMPTS": "Maximum pogingen voor wachtwoord", + "MAXPASSWORDATTEMPTS": "Maximum pogingen voor wachtwoord", + "MAXOTPATTEMPTS": "Maximale OTP-pogingen", "EXPIREWARNDAYS": "Vervaldatum Waarschuwing na dag", "MAXAGEDAYS": "Maximale Leeftijd in dagen", "USERLOGINMUSTBEDOMAIN": "Voeg organisatie domein toe als achtervoegsel aan inlognamen", @@ -2027,7 +2058,13 @@ "ISCREATIONALLOWED": "Account creatie toegestaan", "ISCREATIONALLOWED_DESC": "Bepaalt of accounts kunnen worden aangemaakt.", "ISLINKINGALLOWED": "Account koppeling toegestaan", - "ISLINKINGALLOWED_DESC": "Bepaalt of een identiteit kan worden gekoppeld aan een bestaand account." + "ISLINKINGALLOWED_DESC": "Bepaalt of een identiteit kan worden gekoppeld aan een bestaand account.", + "AUTOLINKING_DESC": "Bepaalt of een identiteit wordt gevraagd om te worden gekoppeld aan een bestaand account.", + "AUTOLINKINGTYPE": { + "0": "Uitgeschakeld", + "1": "Controleren op bestaande gebruikersnaam", + "2": "Controleren op bestaand e-mailadres" + } }, "OWNERTYPES": { "0": "onbekend", @@ -2202,6 +2239,48 @@ "1": "Toegestaan" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP-provider", + "DESCRIPTION": "Dit zijn de SMTP-providers voor uw Zitadel-instantie. Activeer degene die u wilt gebruiken om meldingen naar uw gebruikers te sturen.", + "EMPTY": "Geen SMTP-provider beschikbaar", + "ACTIVATED": "Geactiveerd", + "ACTIVATE": "Aanbieder activeren", + "DEACTIVATE": "Aanbieder deactiveren", + "TYPE": "Type", + "DIALOG": { + "ACTIVATED": "SMTP-configuratie is geactiveerd", + "ACTIVATE_WARN_TITLE": "Activeer SMTP-configuratie", + "ACTIVATE_WARN_DESCRIPTION": "U staat op het punt een SMTP-configuratie te activeren. Eerst deactiveren we de huidige actieve provider en activeren vervolgens deze configuratie. Weet je het zeker?", + "DEACTIVATE_WARN_TITLE": "Deactiveer SMTP-configuratie", + "DEACTIVATE_WARN_DESCRIPTION": "U staat op het punt een SMTP-configuratie te deactiveren. Weet je het zeker?", + "DEACTIVATED": "SMTP-configuratie is gedeactiveerd", + "DELETE_TITLE": "SMTP-configuratie verwijderen", + "DELETE_DESCRIPTION": "U staat op het punt een configuratie te verwijderen. Bevestig deze actie door de naam van de afzender te typen", + "DELETED": "SMTP-configuratie is verwijderd", + "SENDER": "Typ {{ value }} om deze SMTP-configuratie te verwijderen." + } + }, + "CREATE": { + "TITLE": "SMTP-provider toevoegen", + "DESCRIPTION": "Selecteer één of meer van de volgende aanbieders.", + "STEPS": { + "TITLE": "Voeg {{ value }} SMTP-provider toe", + "CREATE_DESC_TITLE": "Voer stap voor stap uw {{ value }} SMTP-instellingen in", + "CURRENT_DESC_TITLE": "Dit zijn uw SMTP-instellingen", + "PROVIDER_SETTINGS": "SMTP-providerinstellingen", + "SENDER_SETTINGS": "Afzenderinstellingen", + "TEST_SETTINGS": "SMTP-instellingen testen" + } + }, + "DETAIL": { + "TITLE": "SMTP-providerinstellingen" + }, + "EMPTY": "Geen SMTP-provider beschikbaar", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Toepassingen", "COMPLIANCE": "OIDC Conformiteit", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index e1b632cd3e..c4f9591f3d 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -86,6 +86,10 @@ "TITLE": "Ustawienia organizacji", "DESCRIPTION": "Dostosuj ustawienia swojej organizacji." }, + "FEATURES": { + "TITLE": "Ustawienia funkcji", + "DESCRIPTION": "Odblokuj funkcje swojej instancji." + }, "IDPS": { "TITLE": "Dostawcy tożsamości", "DESCRIPTION": "Utwórz i aktywuj zewnętrznych dostawców tożsamości. Wybierz znanego dostawcę lub skonfiguruj dowolnego innego dostawcę zgodnego z OIDC, OAuth lub SAML według własnego wyboru. Możesz nawet użyć istniejących tokenów JWT jako tożsamości federacyjnych, konfigurując dostawcę tożsamości JWT.", @@ -1314,6 +1318,7 @@ }, "LIST": { "ORGS": "Organizacje", + "FEATURESETTINGS": "Ustawienia funkcji", "LANGUAGES": "Języki", "LOGIN": "Zachowanie logowania i bezpieczeństwo", "LOCKOUT": "Blokada", @@ -1375,6 +1380,8 @@ } }, "SMTP": { + "TITLE": "Ustawienia SMTP", + "DESCRIPTION": "Opis", "SENDERADDRESS": "Adres e-mail nadawcy", "SENDERNAME": "Nazwa nadawcy", "REPLYTOADDRESS": "Adres Reply-to", @@ -1385,6 +1392,7 @@ "PASSWORDSET": "Hasło SMTP zostało pomyślnie ustawione.", "TLS": "Bezpieczeństwo warstwy transportu (TLS)", "SAVED": "Pomyślnie zapisano!", + "NOCHANGES": "Bez zmian!", "REQUIREDWARN": "Aby wysyłać powiadomienia z twojej domeny, musisz podać dane SMTP." }, "SMS": { @@ -1444,6 +1452,27 @@ "IMPERSONATIONENABLED": "Zezwalaj na podszywanie się", "IMPERSONATIONDESCRIPTION": "To ustawienie pozwala w zasadzie na użycie personifikacji. Należy pamiętać, że osoba personifikująca potrzebuje również przypisanych odpowiednich ról `*_IMPERSONATOR`." }, + "FEATURES": { + "LOGINDEFAULTORG": "Domyślna Organizacja Logowania", + "LOGINDEFAULTORG_DESCRIPTION": "Jeśli nie ustawiono kontekstu organizacji, interfejs logowania będzie używać ustawień domyślnej organizacji (a nie instancji)", + "OIDCLEGACYINTROSPECTION": "Starsza Introspekcja OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Ostatnio przeprojektowaliśmy punkt końcowy introspekcji ze względów wydajnościowych. Ta funkcja może być używana do cofnięcia do starszej implementacji, jeśli wystąpią nieoczekiwane błędy.", + "OIDCTOKENEXCHANGE": "Wymiana Tokenów OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Włącz eksperymentalny typ grantu urn:ietf:params:oauth:grant-type:token-exchange dla punktu końcowego tokena OIDC. Wymiana tokenów może być używana do żądania tokenów o mniejszym zakresie lub podszywania się za innych użytkowników. Aby zezwolić na podszywanie się na instancji, zapoznaj się z polityką bezpieczeństwa.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Projekcje Introspekcji Wyzwalane przez OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Włącz wyzwalacze projekcji podczas żądania introspekcji. Może to stanowić obejście, jeśli w odpowiedzi introspekcji występują zauważalne problemy z spójnością, ale może mieć wpływ na wydajność. Planujemy w przyszłości usunąć wyzwalacze dla żądań introspekcji.", + "USERSCHEMA": "Schemat Użytkownika", + "USERSCHEMA_DESCRIPTION": "Schematy użytkowników umożliwiają zarządzanie schematami danych użytkowników. Jeśli flaga jest włączona, będziesz mógł korzystać z nowego interfejsu API i jego funkcji.", + "ACTIONS": "Akcje", + "ACTIONS_DESCRIPTION": "Akcje v2 umożliwiają zarządzanie wykonaniami danych i celami. Jeżeli flaga jest włączona, będziesz mógł korzystać z nowego interfejsu API i jego funkcji.", + "STATES": { + "INHERITED": "Dziedziczony", + "ENABLED": "Włączony", + "DISABLED": "Wyłączony" + }, + "INHERITED_DESCRIPTION": "To ustawienie przypisuje wartość do wartości domyślnej systemu.", + "RESET": "Ustaw wszystko na dziedziczone" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Resetuj ustawienia", @@ -1588,6 +1617,7 @@ "initPasswordText": "Inicjalizacja hasła", "initializeDoneText": "Inicjalizacja użytkownika zakończona", "initializeUserText": "Inicjalizacja użytkownika", + "linkingUserPromptText": "Komunikat o łączeniu użytkowników", "linkingUserDoneText": "Łączenie użytkownika zakończone", "loginText": "Zaloguj się", "logoutText": "Wyloguj się", @@ -1659,7 +1689,8 @@ "HASLOWERCASE": "zawiera małe litery", "HASUPPERCASE": "zawiera duże litery", "SHOWLOCKOUTFAILURES": "pokaż blokady nieudanych prób", - "MAXATTEMPTS": "Maksymalna liczba prób wprowadzenia hasła", + "MAXPASSWORDATTEMPTS": "Maksymalna liczba prób wprowadzenia hasła", + "MAXOTPATTEMPTS": "Maksymalna liczba prób OTP", "EXPIREWARNDAYS": "Ostrzeżenie o wygaśnięciu po dniu", "MAXAGEDAYS": "Maksymalny wiek w dniach", "USERLOGINMUSTBEDOMAIN": "Dodaj domenę organizacji jako przyrostek do nazw logowania", @@ -2018,7 +2049,13 @@ "ISCREATIONALLOWED": "tworzenie dozwolone", "ISCREATIONALLOWED_DESC": "Określa, czy można tworzyć konta.", "ISLINKINGALLOWED": "dozwolone łączenie rachunków", - "ISLINKINGALLOWED_DESC": "Określa, czy tożsamość może być powiązana z istniejącym kontem." + "ISLINKINGALLOWED_DESC": "Określa, czy tożsamość może być powiązana z istniejącym kontem.", + "AUTOLINKING_DESC": "Określa, czy tożsamość będzie proszona o połączenie z istniejącym kontem.", + "AUTOLINKINGTYPE": { + "0": "Wyłączone", + "1": "Sprawdź istniejącą nazwę użytkownika", + "2": "Sprawdź istniejący adres e-mail" + } }, "OWNERTYPES": { "0": "nieznany", @@ -2184,6 +2221,48 @@ "1": "Dozwolone" } }, + "SMTP": { + "LIST": { + "TITLE": "Dostawca SMTP", + "DESCRIPTION": "To są dostawcy SMTP dla Twojej instancji Zitadel. Aktywuj ten, którego chcesz używać do wysyłania powiadomień do użytkowników.", + "EMPTY": "Brak dostępnego dostawcy SMTP", + "ACTIVATED": "Aktywowany", + "ACTIVATE": "Aktywuj dostawcę", + "DEACTIVATE": "Dezaktywuj dostawcę", + "TYPE": "Typ", + "DIALOG": { + "ACTIVATED": "Konfiguracja SMTP została aktywowana", + "ACTIVATE_WARN_TITLE": "Aktywuj konfigurację SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Zamierzasz aktywować konfigurację SMTP. Najpierw dezaktywujemy bieżącego aktywnego dostawcę, a następnie aktywujemy tę konfigurację. Jesteś pewny?", + "DEACTIVATE_WARN_TITLE": "Dezaktywuj konfigurację SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Zamierzasz dezaktywować konfigurację SMTP. Jesteś pewny?", + "DEACTIVATED": "Konfiguracja SMTP została dezaktywowana", + "DELETE_TITLE": "Usuń konfigurację SMTP", + "DELETE_DESCRIPTION": "Zamierzasz usunąć konfigurację. Potwierdź tę czynność, wpisując nazwę nadawcy", + "DELETED": "Konfiguracja SMTP została usunięta", + "SENDER": "Wpisz {{ value }}, aby usunąć tę konfigurację SMTP." + } + }, + "CREATE": { + "TITLE": "Dodaj dostawcę SMTP", + "DESCRIPTION": "Wybierz jednego lub więcej z poniższych dostawców.", + "STEPS": { + "TITLE": "Dodaj {{ value }} dostawcę SMTP", + "CREATE_DESC_TITLE": "Wprowadź krok po kroku ustawienia SMTP {{ value }}", + "CURRENT_DESC_TITLE": "To są Twoje ustawienia SMTP", + "PROVIDER_SETTINGS": "Ustawienia dostawcy SMTP", + "SENDER_SETTINGS": "Ustawienia nadawcy", + "TEST_SETTINGS": "Przetestuj ustawienia SMTP" + } + }, + "DETAIL": { + "TITLE": "Ustawienia dostawcy SMTP" + }, + "EMPTY": "Brak dostępnego dostawcy SMTP", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Aplikacje", "COMPLIANCE": "Zgodność OIDC", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 4cbcfe4e16..930fa9795d 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -86,6 +86,10 @@ "TITLE": "Configurações da Organização", "DESCRIPTION": "Personalize as configurações da sua organização." }, + "FEATURES": { + "TITLE": "Configurações de recursos", + "DESCRIPTION": "Desbloqueie recursos para sua instância." + }, "IDPS": { "TITLE": "Provedores de Identidade", "DESCRIPTION": "Crie e ative provedores de identidade externos. Escolha um provedor conhecido ou configure qualquer outro provedor compatível com OIDC, OAuth ou SAML de sua escolha. Você pode até usar seus tokens JWT existentes como identidades federadas configurando um provedor de identidade JWT.", @@ -1316,6 +1320,7 @@ }, "LIST": { "ORGS": "Organizações", + "FEATURESETTINGS": "Configurações de Recursos", "LANGUAGES": "Idiomas", "LOGIN": "Comportamento de Login e Segurança", "LOCKOUT": "Bloqueio", @@ -1377,6 +1382,8 @@ } }, "SMTP": { + "TITLE": "Configurações SMTP", + "DESCRIPTION": "Descrição", "SENDERADDRESS": "Endereço de e-mail do remetente", "SENDERNAME": "Nome do remetente", "REPLYTOADDRESS": "Endereço Reply-To", @@ -1387,6 +1394,7 @@ "PASSWORDSET": "Senha do SMTP definida com sucesso.", "TLS": "Transport Layer Security (TLS)", "SAVED": "Salvo com sucesso!", + "NOCHANGES": "Sem alterações!", "REQUIREDWARN": "Para enviar notificações do seu domínio, você precisa inserir seus dados SMTP." }, "SMS": { @@ -1446,6 +1454,27 @@ "IMPERSONATIONENABLED": "Permitir representação", "IMPERSONATIONDESCRIPTION": "Esta configuração permite usar a representação em princípio. Observe que o imitador também precisa das funções `*_IMPERSONATOR` apropriadas atribuídas." }, + "FEATURES": { + "LOGINDEFAULTORG": "Organização Padrão de Login", + "LOGINDEFAULTORG_DESCRIPTION": "A interface de login utilizará as configurações da organização padrão (e não da instância) se nenhum contexto de organização estiver definido", + "OIDCLEGACYINTROSPECTION": "Introspecção Legada OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Recentemente refatoramos o endpoint de introspecção por motivos de performance. Esse recurso pode ser usado para reverter para a implementação legada caso surjam bugs inesperados.", + "OIDCTOKENEXCHANGE": "Troca de Token OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Habilita o tipo de concessão experimental urn:ietf:params:oauth:grant-type:token-exchange para o endpoint de token OIDC. A troca de token pode ser usada para solicitar tokens com escopo menor ou personificar outros usuários. Consulte a política de segurança para permitir a personificação em uma instância.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Projeções de Introspecção com Gatilho OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Habilita gatilhos de projeção durante uma solicitação de introspecção. Isso pode funcionar como uma solução alternativa se houver problemas de consistência perceptíveis na resposta de introspecção, mas pode impactar o desempenho. Planejamos remover gatilhos para solicitações de introspecção no futuro.", + "USERSCHEMA": "Esquema de Usuário", + "USERSCHEMAS_DESCRIPTION": "Esquemas de Usuário permitem gerenciar esquemas de dados do usuário. Se o sinalizador estiver ativado, você poderá usar a nova API e seus recursos.", + "ACTIONS": "Ações", + "ACTIONS_DESCRIPTION": "Actions v2 permitem gerenciar execuções e destinos de dados. Se a flag estiver habilitada, você poderá usar a nova API e seus recursos.", + "STATES": { + "INHERITED": "Herdade", + "ENABLED": "Habilitado", + "DISABLED": "Desabilitado" + }, + "INHERITED_DESCRIPTION": "Essa configuração define o valor para o padrão do sistema.", + "RESET": "Definir tudo para herdar" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Redefinir configuração", @@ -1590,6 +1619,7 @@ "initPasswordText": "Inicialização de senha", "initializeDoneText": "Inicialização de usuário concluída", "initializeUserText": "Inicializaçãode usuário", + "linkingUserPromptText": "Prompt de usuário para vinculação", "linkingUserDoneText": "Vinculação de usuário concluída", "loginText": "Login", "logoutText": "Logout", @@ -1661,7 +1691,8 @@ "HASLOWERCASE": "tem letra minúscula", "HASUPPERCASE": "tem letra maiúscula", "SHOWLOCKOUTFAILURES": "mostrar falhas de bloqueio", - "MAXATTEMPTS": "Máximo de tentativas de senha", + "MAXPASSWORDATTEMPTS": "Máximo de tentativas de senha", + "MAXOTPATTEMPTS": "Máximo de tentativas de OTP", "EXPIREWARNDAYS": "Aviso de expiração após dias", "MAXAGEDAYS": "Idade máxima em dias", "USERLOGINMUSTBEDOMAIN": "Adicionar domínio da organização como sufixo aos nomes de login", @@ -2020,7 +2051,13 @@ "ISCREATIONALLOWED": "Criação de Conta Permitida", "ISCREATIONALLOWED_DESC": "Determina se as contas podem ser criadas.", "ISLINKINGALLOWED": "Vinculação de Conta Permitida", - "ISLINKINGALLOWED_DESC": "Determina se uma identidade pode ser vinculada a uma conta existente." + "ISLINKINGALLOWED_DESC": "Determina se uma identidade pode ser vinculada a uma conta existente.", + "AUTOLINKING_DESC": "Determina se uma identidade será solicitada a ser vinculada a uma conta existente.", + "AUTOLINKINGTYPE": { + "0": "Desativado", + "1": "Verificar nome de usuário existente", + "2": "Verificar e-mail existente" + } }, "OWNERTYPES": { "0": "desconhecido", @@ -2179,6 +2216,48 @@ "1": "Permitido" } }, + "SMTP": { + "LIST": { + "TITLE": "Provedor SMTP", + "DESCRIPTION": "Estes são os provedores SMTP para sua instância Zitadel. Ative aquele que deseja usar para enviar notificações aos seus usuários.", + "EMPTY": "Nenhum provedor SMTP disponível", + "ACTIVATED": "Ativado", + "ACTIVATE": "Ativar provedor", + "DEACTIVATE": "Desativar provedor", + "TYPE": "Tipo", + "DIALOG": { + "ACTIVATED": "A configuração SMTP foi ativada", + "ACTIVATE_WARN_TITLE": "Ativar configuração SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Você está prestes a ativar uma configuração SMTP. Primeiro, desativaremos o provedor ativo atual e depois ativaremos esta configuração. Tem certeza?", + "DEACTIVATE_WARN_TITLE": "Desativar configuração SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Você está prestes a desativar uma configuração SMTP. Tem certeza?", + "DEACTIVATED": "A configuração SMTP foi desativada", + "DELETE_TITLE": "Excluir configuração SMTP", + "DELETE_DESCRIPTION": "Você está prestes a excluir uma configuração. Confirme esta ação digitando o nome do remetente", + "DELETED": "A configuração SMTP foi excluída", + "SENDER": "Digite {{ value }} para excluir esta configuração SMTP." + } + }, + "CREATE": { + "TITLE": "Adicionar provedor SMTP", + "DESCRIPTION": "Selecione um ou mais dos seguintes fornecedores.", + "STEPS": { + "TITLE": "Adicionar {{ value }} provedor SMTP", + "CREATE_DESC_TITLE": "Insira suas configurações SMTP de {{ value }} passo a passo", + "CURRENT_DESC_TITLE": "Estas são suas configurações de SMTP", + "PROVIDER_SETTINGS": "Configurações do provedor SMTP", + "SENDER_SETTINGS": "Configurações do remetente", + "TEST_SETTINGS": "Testar configurações de SMTP" + } + }, + "DETAIL": { + "TITLE": "Configurações do provedor SMTP" + }, + "EMPTY": "Nenhum provedor SMTP disponível", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Aplicações", "COMPLIANCE": "Conformidade OIDC", diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json index 2f2c63e0e5..491cbf120c 100644 --- a/console/src/assets/i18n/ru.json +++ b/console/src/assets/i18n/ru.json @@ -86,6 +86,10 @@ "TITLE": "Настройки организации", "DESCRIPTION": "Настройте параметры вашей организации." }, + "FEATURES": { + "TITLE": "Настройки функции", + "DESCRIPTION": "Разблокируйте функции для вашего экземпляра." + }, "IDPS": { "TITLE": "Поставщики идентификации", "DESCRIPTION": "Создайте и активируйте внешних поставщиков идентификации. Выберите известного поставщика или настройте любого другого совместимого с OIDC, OAuth или SAML поставщика по вашему выбору. Вы даже можете использовать ваши существующие JWT токены как федеративные идентификаторы, настроив поставщика идентификации JWT.", @@ -1361,8 +1365,8 @@ "DESCRIPTION": "Данные настройки расширяют и перезаписывают настройки вашего экземпляра." }, "LIST": { - "GENERAL": "Общее", "ORGS": "Организации", + "FEATURESETTINGS": "Настройки функций", "LANGUAGES": "Языки", "LOGIN": "Действия при входе и безопасность", "LOCKOUT": "Блокировка", @@ -1421,6 +1425,7 @@ }, "SMTP": { "TITLE": "Настройки SMTP", + "DESCRIPTION": "Описание", "SENDERADDRESS": "Адрес электронной почты отправителя", "SENDERNAME": "Имя отправителя", "HOSTANDPORT": "Хост и порт", @@ -1430,6 +1435,7 @@ "PASSWORDSET": "Пароль SMTP был успешно установлен.", "TLS": "Безопасность транспортного уровня (TLS)", "SAVED": "Успешно сохранено!", + "NOCHANGES": "Без изменений!", "REQUIREDWARN": "Для того, чтобы отправлять уведомления с вашего домена, вам необходимо ввести свои данные SMTP." }, "SMS": { @@ -1498,6 +1504,27 @@ "IMPERSONATIONENABLED": "Разрешить олицетворение", "IMPERSONATIONDESCRIPTION": "Этот параметр позволяет в принципе использовать олицетворение. Обратите внимание, что имитатору также необходимо назначить соответствующие роли `*_IMPERSONATOR`." }, + "FEATURES": { + "LOGINDEFAULTORG": "Организация по умолчанию для входа", + "LOGINDEFAULTORG_DESCRIPTION": "Если контекст организации не установлен, пользовательский интерфейс входа будет использовать настройки организации по умолчанию (а не экземпляра)", + "OIDCLEGACYINTROSPECTION": "Устаревшая интроспекция OIDC", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "Недавно мы переработали конечную точку интроспекции для повышения производительности. Эта функция может использоваться для отката к устаревшей реализации, если возникнут непредвиденные ошибки.", + "OIDCTOKENEXCHANGE": "Обмен токенами OIDC", + "OIDCTOKENEXCHANGE_DESCRIPTION": "Включите экспериментальный тип гранта urn:ietf:params:oauth:grant-type:token-exchange для конечной точки токена OIDC. Обмен токенами можно использовать для запроса токенов с меньшей областью действия или для impersonation (выдачи себя за) других пользователей. Информацию о разрешении impersonation на экземпляре см. в политике безопасности.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "Проекции интроспекции с триггером OIDC", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "Включите триггеры проекций во время запроса интроспекции. Это может служить обходным путем, если в ответе интроспекции наблюдаются заметные проблемы согласованности, но может повлиять на производительность. В будущем мы планируем удалить триггеры для запросов интроспекции.", + "USERSCHEMA": "Схема пользователя", + "USERSCHEMA_DESCRIPTION": "Схемы пользователей позволяют управлять схемами данных пользователей. Если флаг включен, вы сможете использовать новый API и его функции.", + "ACTIONS": "Действия", + "ACTIONS_DESCRIPTION": "Actions v2 позволяют управлять выполнением данных и целевыми объектами. Если флаг включен, вы сможете использовать новый API и его функции.", + "STATES": { + "INHERITED": "Наследовать", + "ENABLED": "Включено", + "DISABLED": "Выключено" + }, + "INHERITED_DESCRIPTION": "Эта настройка устанавливает значение по умолчанию для системы.", + "RESET": "Установить все по умолчанию" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "Сбросить настройки", @@ -1656,6 +1683,7 @@ "initPasswordText": "Инициализировать пароль", "initializeDoneText": "Инициализация пользователя выполнена", "initializeUserText": "Инициализировать пользователя", + "linkingUserPromptText": "Текст приглашения к привязке пользователя", "linkingUserDoneText": "Привязка пользователя выполнена", "loginText": "Вход", "logoutText": "Выход", @@ -1729,7 +1757,8 @@ "HASLOWERCASE": "Содержит нижний регистр", "HASUPPERCASE": "Содержит верхний регистр", "SHOWLOCKOUTFAILURES": "Показать ошибки блокировки", - "MAXATTEMPTS": "Максимальное количество попыток пароля", + "MAXPASSWORDATTEMPTS": "Максимальное количество попыток пароля", + "MAXOTPATTEMPTS": "Максимальное количество попыток OTP", "EXPIREWARNDAYS": "Предупреждение об истечении срока действия после дня", "MAXAGEDAYS": "Максимальный возраст в днях", "USERLOGINMUSTBEDOMAIN": "Добавить домен организации в качестве суффикса к именам логина", @@ -2113,7 +2142,13 @@ "ISCREATIONALLOWED": "Создание учетной записи разрешено", "ISCREATIONALLOWED_DESC": "Определяет, можно ли создавать учетные записи.", "ISLINKINGALLOWED": "Привязка аккаунтов разрешена", - "ISLINKINGALLOWED_DESC": "Определяет, можно ли связать личность с существующей учетной записью." + "ISLINKINGALLOWED_DESC": "Определяет, можно ли связать личность с существующей учетной записью.", + "AUTOLINKING_DESC": "Определяет, будет ли запрошено связать идентификацию с существующим аккаунтом.", + "AUTOLINKINGTYPE": { + "0": "Отключено", + "1": "Проверка существующего имени пользователя", + "2": "Проверка существующего адреса электронной почты" + } }, "OWNERTYPES": { "0": "неизвестен", @@ -2298,6 +2333,48 @@ "1": "Разрешён" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP-провайдер", + "DESCRIPTION": "Это поставщики SMTP для вашего экземпляра Zitadel. Активируйте тот, который вы хотите использовать для отправки уведомлений вашим пользователям.", + "EMPTY": "SMTP-провайдер не доступен", + "ACTIVATED": "Активировано", + "ACTIVATE": "Активировать провайдера", + "DEACTIVATE": "Деактивировать провайдера", + "TYPE": "Тип", + "DIALOG": { + "ACTIVATED": "Конфигурация SMTP активирована", + "ACTIVATE_WARN_TITLE": "Активируйте конфигурацию SMTP", + "ACTIVATE_WARN_DESCRIPTION": "Вы собираетесь активировать конфигурацию SMTP. Сначала мы деактивируем текущего активного провайдера, а затем активируем эту конфигурацию. Вы уверены?", + "DEACTIVATE_WARN_TITLE": "Деактивировать конфигурацию SMTP", + "DEACTIVATE_WARN_DESCRIPTION": "Вы собираетесь деактивировать конфигурацию SMTP. Вы уверены?", + "DEACTIVATED": "Конфигурация SMTP деактивирована", + "DELETE_TITLE": "Удалить конфигурацию SMTP", + "DELETE_DESCRIPTION": "Вы собираетесь удалить конфигурацию. Подтвердите это действие, введя имя отправителя.", + "DELETED": "Конфигурация SMTP удалена.", + "SENDER": "Введите {{ value }}, чтобы удалить эту конфигурацию SMTP." + } + }, + "CREATE": { + "TITLE": "Добавить SMTP-провайдера", + "DESCRIPTION": "Выберите одного или нескольких из следующих поставщиков.", + "STEPS": { + "TITLE": "Добавить {{ value }} SMTP-провайдера", + "CREATE_DESC_TITLE": "Введите {{ value }} настройки SMTP шаг за шагом.", + "CURRENT_DESC_TITLE": "Это ваши настройки SMTP", + "PROVIDER_SETTINGS": "Настройки SMTP-провайдера", + "SENDER_SETTINGS": "Настройки отправителя", + "TEST_SETTINGS": "Проверка настроек SMTP" + } + }, + "DETAIL": { + "TITLE": "Настройки SMTP-провайдера" + }, + "EMPTY": "Нет доступного поставщика SMTP", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "Приложения", "COMPLIANCE": "Соответствие OIDC", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index d4a8c7ead4..659a25907e 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -86,6 +86,10 @@ "TITLE": "组织设置", "DESCRIPTION": "自定义您的组织设置。" }, + "FEATURES": { + "TITLE": "功能设置", + "DESCRIPTION": "解锁您的实例的功能。" + }, "IDPS": { "TITLE": "身份提供商", "DESCRIPTION": "创建并激活外部身份提供商。选择一个知名提供商或根据您的选择配置任何其他兼容OIDC、OAuth或SAML的提供商。您甚至可以通过配置JWT身份提供商,使用您现有的JWT令牌作为联合身份。", @@ -1314,6 +1318,7 @@ }, "LIST": { "ORGS": "组织", + "FEATURESETTINGS": "功能设置", "LANGUAGES": "语言", "LOGIN": "登录行为和安全", "LOCKOUT": "安全锁策略", @@ -1375,6 +1380,8 @@ } }, "SMTP": { + "TITLE": "SMTP 设置", + "DESCRIPTION": "描述", "SENDERADDRESS": "发件人地址", "SENDERNAME": "发件人名称", "REPLYTOADDRESS": "Reply-to 地址", @@ -1385,6 +1392,7 @@ "PASSWORDSET": "SMTP 密码设置成功。", "TLS": "使用安全传输层 (TLS)", "SAVED": "保存成功!", + "NOCHANGES": "没有变化!", "REQUIREDWARN": "要从您的域发送通知,您必须输入您的 SMTP 数据。" }, "SMS": { @@ -1444,6 +1452,27 @@ "IMPERSONATIONENABLED": "允许模拟", "IMPERSONATIONDESCRIPTION": "此设置原则上允许使用模拟。请注意,模拟者还需要分配适当的 `*_IMPERSONATOR` 角色。" }, + "FEATURES": { + "LOGINDEFAULTORG": "登录默认组织", + "LOGINDEFAULTORG_DESCRIPTION": "如果没有设置组织上下文,登录界面将使用默认组织的设置(而不是实例的设置)", + "OIDCLEGACYINTROSPECTION": "OIDC 传统内省", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "我们最近出于性能原因重构了内省端点。如果出现意外错误,可以使用此功能回滚到传统实现。", + "OIDCTOKENEXCHANGE": "OIDC 令牌交换", + "OIDCTOKENEXCHANGE_DESCRIPTION": "启用 OIDC 令牌端点的实验性 urn:ietf:params:oauth:grant-type:token-exchange 授权类型。令牌交换可用于请求具有较少范围的令牌或模拟其他用户。请参阅安全策略以允许在实例上模拟。", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "OIDC 触发内省投影", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "在内省请求期间启用投影触发器。如果内省响应中存在明显的一致性问题,这可以作为一个解决方法,但可能会影响性能。我们计划在未来删除内省请求的触发器。", + "USERSCHEMA": "用户架构", + "USERSCHEMA_DESCRIPTION": "用户架构允许管理用户的数据架构。如果启用此标志,您将可以使用新的 API 及其功能。", + "ACTIONS": "操作", + "ACTIONS_DESCRIPTION": "Actions v2 可以管理数据执行和目标。如果启用此标志,您将可以使用新的 API 及其功能。", + "STATES": { + "INHERITED": "继承", + "ENABLED": "已启用", + "DISABLED": "已禁用" + }, + "INHERITED_DESCRIPTION": "此设置将值设置为系统默认值。", + "RESET": "全部设置为继承" + }, "DIALOG": { "RESET": { "DEFAULTTITLE": "重置设置", @@ -1587,6 +1616,7 @@ "initPasswordText": "初始化密码", "initializeDoneText": "初始化用户完成", "initializeUserText": "初始化用户", + "linkingUserPromptText": "用户链接提示", "linkingUserDoneText": "链接用户完成", "loginText": "登录", "logoutText": "登出", @@ -1658,7 +1688,8 @@ "HASLOWERCASE": "包含小写字母", "HASUPPERCASE": "包含大写字母", "SHOWLOCKOUTFAILURES": "显示锁定失败", - "MAXATTEMPTS": "密码最大尝试次数", + "MAXPASSWORDATTEMPTS": "密码最大尝试次数", + "MAXOTPATTEMPTS": "最多尝试 OTP 次数", "EXPIREWARNDAYS": "密码过期警告", "MAXAGEDAYS": "Max Age in days", "USERLOGINMUSTBEDOMAIN": "用户名必须包含组织域名", @@ -2017,7 +2048,13 @@ "ISCREATIONALLOWED": "是否允许创作", "ISCREATIONALLOWED_DESC": "确定是否可以创建账户。", "ISLINKINGALLOWED": "是否允许连接", - "ISLINKINGALLOWED_DESC": "确定一个身份是否可以与一个现有的账户相联系。" + "ISLINKINGALLOWED_DESC": "确定一个身份是否可以与一个现有的账户相联系。", + "AUTOLINKING_DESC": "确定是否提示将身份链接到现有帐户。", + "AUTOLINKINGTYPE": { + "0": "已禁用", + "1": "检查现有用户名", + "2": "检查现有电子邮件" + } }, "OWNERTYPES": { "0": "未知", @@ -2183,6 +2220,48 @@ "1": "允许" } }, + "SMTP": { + "LIST": { + "TITLE": "SMTP 提供商", + "DESCRIPTION": "这些是您的 Zitadel 实例的 SMTP 提供商。激活您想要用来向用户发送通知的通知。", + "EMPTY": "没有可用的 SMTP 提供商", + "ACTIVATED": "活性", + "ACTIVATE": "激活提供商", + "DEACTIVATE": "停用提供商", + "TYPE": "类型", + "DIALOG": { + "ACTIVATED": "SMTP 配置已激活", + "ACTIVATE_WARN_TITLE": "激活 SMTP 配置", + "ACTIVATE_WARN_DESCRIPTION": "您即将激活 SMTP 配置。首先,我们将停用当前活动的提供程序,然后激活此配置。你确定吗?", + "DEACTIVATE_WARN_TITLE": "停用 SMTP 配置", + "DEACTIVATE_WARN_DESCRIPTION": "您即将停用 SMTP 配置。你确定吗?", + "DEACTIVATED": "SMTP 配置已停用", + "DELETE_TITLE": "删除 SMTP 配置", + "DELETE_DESCRIPTION": "您将要删除一个配置。输入发件人姓名确认此操作", + "DELETED": "SMTP 配置已被删除", + "SENDER": "输入 {{ value }},删除此 SMTP 配置。" + } + }, + "CREATE": { + "TITLE": "添加 SMTP 提供商", + "DESCRIPTION": "选择以下一个或多个提供商。", + "STEPS": { + "TITLE": "添加 {{ value }} SMTP 提供商", + "CREATE_DESC_TITLE": "逐步输入您的 {{ value }} SMTP 设置", + "CURRENT_DESC_TITLE": "这些是您的 SMTP 设置", + "PROVIDER_SETTINGS": "SMTP 提供商设置", + "SENDER_SETTINGS": "发件人设置", + "TEST_SETTINGS": "测试 SMTP 设置" + } + }, + "DETAIL": { + "TITLE": "SMTP 提供商设置" + }, + "EMPTY": "没有可用的 SMTP 提供商", + "STEPS": { + "SENDGRID": {} + } + }, "APP": { "LIST": "应用", "COMPLIANCE": "OIDC 兼容性", diff --git a/console/src/assets/images/smtp/aws-ses.svg b/console/src/assets/images/smtp/aws-ses.svg new file mode 100644 index 0000000000..c3b9b61984 --- /dev/null +++ b/console/src/assets/images/smtp/aws-ses.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/console/src/assets/images/smtp/brevo.svg b/console/src/assets/images/smtp/brevo.svg new file mode 100644 index 0000000000..a86cb89a46 --- /dev/null +++ b/console/src/assets/images/smtp/brevo.svg @@ -0,0 +1,3 @@ + + + diff --git a/console/src/assets/images/smtp/google.png b/console/src/assets/images/smtp/google.png new file mode 100644 index 0000000000..94e00f90b3 Binary files /dev/null and b/console/src/assets/images/smtp/google.png differ diff --git a/console/src/assets/images/smtp/mailchimp.svg b/console/src/assets/images/smtp/mailchimp.svg new file mode 100644 index 0000000000..1738bf2754 --- /dev/null +++ b/console/src/assets/images/smtp/mailchimp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/src/assets/images/smtp/mailgun.svg b/console/src/assets/images/smtp/mailgun.svg new file mode 100644 index 0000000000..8cfc319bad --- /dev/null +++ b/console/src/assets/images/smtp/mailgun.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/console/src/assets/images/smtp/mailjet.svg b/console/src/assets/images/smtp/mailjet.svg new file mode 100644 index 0000000000..44edfb093b --- /dev/null +++ b/console/src/assets/images/smtp/mailjet.svg @@ -0,0 +1,7 @@ + + + Mailjet + + + + diff --git a/console/src/assets/images/smtp/postmark.png b/console/src/assets/images/smtp/postmark.png new file mode 100644 index 0000000000..42e51675c0 Binary files /dev/null and b/console/src/assets/images/smtp/postmark.png differ diff --git a/console/src/assets/images/smtp/sendgrid.png b/console/src/assets/images/smtp/sendgrid.png new file mode 100644 index 0000000000..235630f073 Binary files /dev/null and b/console/src/assets/images/smtp/sendgrid.png differ diff --git a/console/src/assets/mdi/mail.svg b/console/src/assets/mdi/mail.svg new file mode 100644 index 0000000000..15e1d12d4e --- /dev/null +++ b/console/src/assets/mdi/mail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/src/component-themes.scss b/console/src/component-themes.scss index f495704c81..d695affe9d 100644 --- a/console/src/component-themes.scss +++ b/console/src/component-themes.scss @@ -72,6 +72,9 @@ @import 'src/app/modules/info-overlay/info-overlay.component.scss'; @import 'src/app/modules/create-layout/create-layout.component.scss'; @import 'src/app/modules/domains/domain-verification/domain-verification.component.scss'; +@import 'src/app/modules/smtp-table/smtp-table.component.scss'; +@import 'src/app/modules/smtp-provider/smtp-provider.scss'; +@import 'src/app/modules/policies/notification-smtp-provider/notification-smtp-provider.component.scss'; @import './styles/codemirror.scss'; @import 'src/app/components/copy-row/copy-row.component.scss'; @import 'src/app/modules/providers/provider-next/provider-next.component.scss'; @@ -85,6 +88,8 @@ @include header-theme($theme); @include app-type-radio-theme($theme); @include idp-table-theme($theme); + @include smtp-table-theme($theme); + @include smtp-provider-theme($theme); @include events-theme($theme); @include projects-theme($theme); @include grants-theme($theme); @@ -153,4 +158,5 @@ @include domain-verification-theme($theme); @include copy-row-theme($theme); @include provider-next-theme($theme); + @include smtp-settings-theme($theme); } diff --git a/console/yarn.lock b/console/yarn.lock index 7b6584a842..de6a28e268 100644 --- a/console/yarn.lock +++ b/console/yarn.lock @@ -2103,18 +2103,6 @@ protobufjs "^7.2.4" yargs "^17.7.2" -"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" - integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== - -"@hapi/topo@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@humanwhocodes/config-array@^0.11.11": version "0.11.11" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" @@ -3193,23 +3181,6 @@ "@angular-devkit/schematics" "16.2.2" jsonc-parser "3.2.0" -"@sideway/address@^4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" - integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" resolved "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz" @@ -4246,30 +4217,11 @@ big.js@^5.2.2: resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bin-build@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bin-build/-/bin-build-3.0.0.tgz#c5780a25a8a9f966d8244217e6c1f5082a143861" - integrity sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA== - dependencies: - decompress "^4.0.0" - download "^6.2.2" - execa "^0.7.0" - p-map-series "^1.0.0" - tempfile "^2.0.0" - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bl@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" - integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -4391,35 +4343,12 @@ browserstack@^1.5.1: dependencies: https-proxy-agent "^2.2.1" -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.2.1, buffer@^5.5.0: +buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4527,16 +4456,6 @@ caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -caw@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" - integrity sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA== - dependencies: - get-proxy "^2.0.0" - isurl "^1.0.0-alpha5" - tunnel-agent "^0.6.0" - url-to-options "^1.0.1" - chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" @@ -4741,7 +4660,7 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.20.0, commander@^2.8.1: +commander@^2.11.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4776,14 +4695,6 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -config-chain@^1.1.11: - version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - connect-history-api-fallback@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz" @@ -4804,7 +4715,7 @@ console-control-strings@^1.1.0: resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -content-disposition@0.5.4, content-disposition@^0.5.2: +content-disposition@0.5.4: version "0.5.4" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== @@ -4903,15 +4814,6 @@ critters@0.0.20: postcss "^8.4.23" pretty-bytes "^5.3.0" -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" @@ -5050,66 +4952,6 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decompress-response@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - dependencies: - mimic-response "^1.0.0" - -decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" - integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== - dependencies: - file-type "^5.2.0" - is-stream "^1.1.0" - tar-stream "^1.5.2" - -decompress-tarbz2@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" - integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== - dependencies: - decompress-tar "^4.1.0" - file-type "^6.1.0" - is-stream "^1.1.0" - seek-bzip "^1.0.5" - unbzip2-stream "^1.0.9" - -decompress-targz@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" - integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== - dependencies: - decompress-tar "^4.1.1" - file-type "^5.2.0" - is-stream "^1.1.0" - -decompress-unzip@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" - integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw== - dependencies: - file-type "^3.8.0" - get-stream "^2.2.0" - pify "^2.3.0" - yauzl "^2.4.2" - -decompress@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" - integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== - dependencies: - decompress-tar "^4.0.0" - decompress-tarbz2 "^4.0.0" - decompress-targz "^4.0.0" - decompress-unzip "^4.0.1" - graceful-fs "^4.1.10" - make-dir "^1.0.0" - pify "^2.3.0" - strip-dirs "^2.0.0" - deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -5275,28 +5117,6 @@ dotenv@~10.0.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -download@^6.2.2: - version "6.2.5" - resolved "https://registry.yarnpkg.com/download/-/download-6.2.5.tgz#acd6a542e4cd0bb42ca70cfc98c9e43b07039714" - integrity sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA== - dependencies: - caw "^2.0.0" - content-disposition "^0.5.2" - decompress "^4.0.0" - ext-name "^5.0.0" - file-type "5.2.0" - filenamify "^2.0.0" - get-stream "^3.0.0" - got "^7.0.0" - make-dir "^1.0.0" - p-event "^1.0.0" - pify "^3.0.0" - -duplexer3@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" - integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - duplexer@^0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" @@ -5369,7 +5189,7 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.4.1: +end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -5687,19 +5507,6 @@ events@^3.2.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw== - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^5.0.0: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -5757,21 +5564,6 @@ express@^4.17.3: utils-merge "1.0.1" vary "~1.1.2" -ext-list@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" - integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== - dependencies: - mime-db "^1.28.0" - -ext-name@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" - integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== - dependencies: - ext-list "^2.0.0" - sort-keys-length "^1.0.0" - extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" @@ -5863,13 +5655,6 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - figures@3.2.0, figures@^3.0.0: version "3.2.0" resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" @@ -5889,21 +5674,6 @@ file-saver@^2.0.5: resolved "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz" integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== -file-type@5.2.0, file-type@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" - integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ== - -file-type@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" - integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA== - -file-type@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" - integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== - filelist@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" @@ -5911,20 +5681,6 @@ filelist@^1.0.4: dependencies: minimatch "^5.0.1" -filename-reserved-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" - integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== - -filenamify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9" - integrity sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA== - dependencies: - filename-reserved-regex "^2.0.0" - strip-outer "^1.0.0" - trim-repeated "^1.0.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" @@ -6179,26 +5935,6 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proxy@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" - integrity sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw== - dependencies: - npm-conf "^1.1.0" - -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA== - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== - get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -6336,27 +6072,7 @@ google-protobuf@^3.21.2: resolved "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz" integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA== -got@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" - integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== - dependencies: - decompress-response "^3.2.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" - url-to-options "^1.0.1" - -graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6418,23 +6134,11 @@ has-proto@^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-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" @@ -6811,11 +6515,6 @@ is-docker@^2.0.0, is-docker@^2.1.1: resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -6826,13 +6525,6 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg== - dependencies: - is-extglob "^1.0.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6845,33 +6537,16 @@ is-interactive@^1.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== -is-invalid-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" - integrity sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ== - dependencies: - is-glob "^2.0.0" - is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-natural-number@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" - integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" - integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz" @@ -6896,11 +6571,6 @@ is-path-inside@^3.0.3: resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - is-plain-obj@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz" @@ -6923,16 +6593,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - -is-stream@^1.0.0, is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" @@ -6948,13 +6608,6 @@ is-unicode-supported@^0.1.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-valid-path@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" - integrity sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A== - dependencies: - is-invalid-path "^0.1.0" - is-what@^3.14.1: version "3.14.1" resolved "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz" @@ -7041,14 +6694,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - jackspeak@^2.0.3: version "2.2.0" resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz" @@ -7113,17 +6758,6 @@ jiti@^1.18.2: resolved "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz" integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== -joi@^17.4.0: - version "17.12.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.2.tgz#283a664dabb80c7e52943c557aab82faea09f521" - integrity sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw== - dependencies: - "@hapi/hoek" "^9.3.0" - "@hapi/topo" "^5.1.0" - "@sideway/address" "^4.1.5" - "@sideway/formula" "^3.0.1" - "@sideway/pinpoint" "^2.0.0" - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -7508,19 +7142,6 @@ long@^5.0.0: resolved "https://registry.npmjs.org/long/-/long-5.2.3.tgz" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -7552,13 +7173,6 @@ magic-string@0.30.1: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - make-dir@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" @@ -7667,7 +7281,7 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== @@ -7694,11 +7308,6 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - mini-css-extract-plugin@2.7.6: version "2.7.6" resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz" @@ -7933,11 +7542,6 @@ node-addon-api@^3.0.0, node-addon-api@^3.2.1: resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-downloader-helper@^2.1.6: - version "2.1.9" - resolved "https://registry.yarnpkg.com/node-downloader-helper/-/node-downloader-helper-2.1.9.tgz#a59ee7276b2bf708bbac2cc5872ad28fc7cd1b0e" - integrity sha512-FSvAol2Z8UP191sZtsUZwHIN0eGoGue3uEXGdWIH5228e9KH1YHXT7fN8Oa33UGf+FbqGTQg3sJfrRGzmVCaJA== - node-forge@^1: version "1.3.1" resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" @@ -7964,18 +7568,6 @@ node-gyp@^9.0.0: tar "^6.1.2" which "^2.0.2" -node-jq@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/node-jq/-/node-jq-4.3.1.tgz#c2a082210745fd1c54df5965b9ba5d046fce9d68" - integrity sha512-5iU9L/7j8ZNHwhxDRJXgyza6JnEKqdkNcJ9+ul5HZnhConhg/v9JdvA9agJ8XA+qBgGr1MK/MeHDrdK1tL2QAA== - dependencies: - bin-build "^3.0.0" - is-valid-path "^0.1.1" - joi "^17.4.0" - node-downloader-helper "^2.1.6" - strip-final-newline "^2.0.0" - tempfile "^3.0.0" - node-releases@^2.0.12: version "2.0.13" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz" @@ -8030,14 +7622,6 @@ npm-bundled@^3.0.0: dependencies: npm-normalize-package-bin "^3.0.0" -npm-conf@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - npm-install-checks@^6.0.0: version "6.1.1" resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz" @@ -8090,13 +7674,6 @@ npm-registry-fetch@^14.0.0: npm-package-arg "^10.0.0" proc-log "^3.0.0" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" @@ -8284,18 +7861,6 @@ os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== - -p-event@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" - integrity sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA== - dependencies: - p-timeout "^1.1.1" - p-filter@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-3.0.0.tgz#ce50e03b24b23930e11679ab8694bd09a2d7ed35" @@ -8303,11 +7868,6 @@ p-filter@^3.0.0: dependencies: p-map "^5.1.0" -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8350,13 +7910,6 @@ p-locate@^6.0.0: dependencies: p-limit "^4.0.0" -p-map-series@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" - integrity sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg== - dependencies: - p-reduce "^1.0.0" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" @@ -8371,11 +7924,6 @@ p-map@^5.1.0: dependencies: aggregate-error "^4.0.0" -p-reduce@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" - integrity sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ== - p-retry@^4.5.0: version "4.6.2" resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz" @@ -8384,13 +7932,6 @@ p-retry@^4.5.0: "@types/retry" "0.12.0" retry "^0.13.1" -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - integrity sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA== - dependencies: - p-finally "^1.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" @@ -8500,11 +8041,6 @@ path-is-inside@^1.0.1: resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== -path-key@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" @@ -8533,11 +8069,6 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" @@ -8553,16 +8084,11 @@ picomatch@2.3.1, picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0, pify@^2.3.0: +pify@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - pify@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" @@ -8685,11 +8211,6 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== - prettier-plugin-organize-imports@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz#77967f69d335e9c8e6e5d224074609309c62845e" @@ -8733,11 +8254,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== - protobufjs@^7.0.0, protobufjs@^7.2.4: version "7.2.5" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" @@ -8795,11 +8311,6 @@ prr@~1.0.1: resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.28, psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -8926,7 +8437,7 @@ read-pkg@^7.1.0: parse-json "^5.2.0" type-fest "^2.0.0" -readable-stream@^2.0.1, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -9164,7 +8675,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -9242,13 +8753,6 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -seek-bzip@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" - integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== - dependencies: - commander "^2.8.1" - select-hose@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" @@ -9385,13 +8889,6 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -9399,11 +8896,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" @@ -9423,7 +8915,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -9510,20 +9002,6 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" -sort-keys-length@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" - integrity sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw== - dependencies: - sort-keys "^1.0.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== - dependencies: - is-plain-obj "^1.0.0" - "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" @@ -9738,18 +9216,6 @@ strip-bom@^3.0.0: resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-dirs@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" - integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== - dependencies: - is-natural-number "^4.0.1" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" @@ -9760,13 +9226,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-outer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" - integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== - dependencies: - escape-string-regexp "^1.0.2" - strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" @@ -9822,19 +9281,6 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -tar-stream@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" - integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== - dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" - fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" - tar-stream@~2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" @@ -9858,32 +9304,6 @@ tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" -temp-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== - -temp-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" - integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== - -tempfile@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" - integrity sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA== - dependencies: - temp-dir "^1.0.0" - uuid "^3.0.1" - -tempfile@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-3.0.0.tgz#5376a3492de7c54150d0cc0612c3f00e2cdaf76c" - integrity sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw== - dependencies: - temp-dir "^2.0.0" - uuid "^3.3.2" - terser-webpack-plugin@^5.3.7: version "5.3.8" resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz" @@ -9929,7 +9349,7 @@ text-table@0.2.0, text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -through@X.X.X, through@^2.3.4, through@^2.3.6, through@^2.3.8: +through@X.X.X, through@^2.3.4, through@^2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -9939,11 +9359,6 @@ thunky@^1.0.2: resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== - tiny-inflate@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" @@ -9975,11 +9390,6 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -to-buffer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" @@ -10027,13 +9437,6 @@ tree-kill@1.2.2: resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -trim-repeated@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" - integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== - dependencies: - escape-string-regexp "^1.0.2" - tsconfig-paths@^4.1.2: version "4.2.0" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" @@ -10131,14 +9534,6 @@ ua-parser-js@^0.7.30: resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz" integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g== -unbzip2-stream@^1.0.9: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -10225,13 +9620,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA== - dependencies: - prepend-http "^1.0.1" - url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -10240,11 +9628,6 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -10255,7 +9638,7 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.0.1, uuid@^3.3.2: +uuid@^3.3.2: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -10544,7 +9927,7 @@ which-module@^2.0.0: resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which@^1.2.1, which@^1.2.9: +which@^1.2.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -10647,11 +10030,6 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - y18n@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" @@ -10662,11 +10040,6 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" @@ -10738,14 +10111,6 @@ yargs@^16.1.1: y18n "^5.0.5" yargs-parser "^20.2.2" -yauzl@^2.4.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" diff --git a/docs/docs/apis/actions/objects.md b/docs/docs/apis/actions/objects.md index 2f95b02efa..63f5cb5619 100644 --- a/docs/docs/apis/actions/objects.md +++ b/docs/docs/apis/actions/objects.md @@ -202,8 +202,11 @@ This object represents a list of user grant stored in ZITADEL. - `sequence` *Number* - `userId` *string* - `roles` Array of *string* - - `userResourceOwner` *string* - - `userGrantResourceOwner` *string* - - `userGrantResourceOwnerName` *string* + - `userResourceOwner` *string* + The id of the organization of the user + - `userGrantResourceOwner` *string* + The id of the organization, where the user was granted + - `userGrantResourceOwnerName` *string* + The name of the organization, where the user was granted - `projectId` *string* - `projectName` *string* diff --git a/docs/docs/concepts/architecture/secrets.md b/docs/docs/concepts/architecture/secrets.md index 4bfe0af9ec..a2b936b71e 100644 --- a/docs/docs/concepts/architecture/secrets.md +++ b/docs/docs/concepts/architecture/secrets.md @@ -64,7 +64,7 @@ ZITADEL hashes all Passwords and Client Secrets in an non reversible way to furt Passwords and secrets are always hashed with a random salt and stored as an encoded string that contains the Algorithm, its Parameters, Salt and Hash. The storage encoding used by ZITADEL is Modular Crypt Format and a full reference can be found in our [Passwap library](https://github.com/zitadel/passwap#encoding). -The following hash algorithms are supported for user passwords: +The following hash algorithms are supported: - argon2i / id[^1] - bcrypt (Default) @@ -82,8 +82,6 @@ This allows to increase cost along with growing computing power. ZITADEL allows to import user passwords from systems that use any of the above hashing algorithms. ::: -Client Secrets always use bcrypt. - ### Encrypted Secrets Some secrets cannot be hashed because they need to be used in their raw form. These include: diff --git a/docs/docs/concepts/features/external-user-grant.md b/docs/docs/concepts/features/external-user-grant.md new file mode 100644 index 0000000000..cb72f5054d --- /dev/null +++ b/docs/docs/concepts/features/external-user-grant.md @@ -0,0 +1,40 @@ +--- +title: External user grant +--- + +ZITADEL's external user grant is a feature that allows you to grant access to projects within your organization to users from other organizations. +This is useful in scenarios where you want to collaborate with external users without needing them to be part of your organization. +By using external user grants, you can streamline collaboration with external users while maintaining control over access to your projects within ZITADEL. + +![](/img/concepts/features/external-user-grant.png) + +## Where to store users + +### Consumer Identity Management (CIAM) / Business-to-Consumer (B2C) + +You might typically store all users in a single ZITADEL [organization](../structure/organizations) for managing customer accounts. +We recommend creating a second organization for your own team that also contains all the projects and applications that will be [granted](../structure/granted_projects) to the first organization with the B2C customer accounts. +Instead of duplicating user accounts for your team members in the B2C organization, you can create external user grants on the B2C organization. + +### Multitenancy / Business-to-Business (B2B) + +ZITADEL allows you to create separate [organizations](../structure/organizations) for each of your business partner or tenant. +There might be cases were users from one organization need access to projects from another organization. +You can create an external user grant, that allows the inviting organization to manage the roles for the external user. + +## Project Grants vs. User Grants + +Project grants are used to delegate access management of an entire project (or specific roles of the project) to another organization. + +User grants provide a more granular approach, allowing specific users from external organizations to access your projects. + +## Alternative to multiple user accounts + +A user account is always unique across a ZITADEL instance. +In some use cases, external user grants are a simple way to allow users access to multiple tenants. + +## References + +* [API reference for user grants](/docs/category/apis/resources/mgmt/user-grants) +* [How to manage user grants through ZITADEL's console](/docs/guides/manage/console/roles#authorizations) +* [More about multi-tenancy with ZITADEL](https://zitadel.com/blog/multi-tenancy-with-organizations) diff --git a/docs/docs/guides/integrate/login-ui/typescript-repo.mdx b/docs/docs/guides/integrate/login-ui/typescript-repo.mdx index a2d76dc3b5..a47090eb3b 100644 --- a/docs/docs/guides/integrate/login-ui/typescript-repo.mdx +++ b/docs/docs/guides/integrate/login-ui/typescript-repo.mdx @@ -60,7 +60,7 @@ The application can then request a token calling the /token endpoint of the logi - [x] Authorization Code Flow with PKCE - [x] AuthRequest `hintUserId` - [x] AuthRequest `loginHint` -- [ ] AuthRequest `prompt` +- [x] AuthRequest `prompt` - [x] Login - [x] Select Account - [x] Create @@ -71,7 +71,8 @@ The application can then request a token calling the /token endpoint of the logi - [x] `offline access` - [ ] `urn:zitadel:iam:org:idp:id:{idp_id}` - [x] `urn:zitadel:iam:org:project:id:zitadel:aud` - - [ ] `urn:zitadel:iam:org:id:{orgid}` + - [x] `urn:zitadel:iam:org:id:{orgid}` + - [x] `urn:zitadel:iam:org:domain:primary:{domain}` - [ ] AuthRequest UI locales - Multifactor - [x] Passkeys @@ -85,14 +86,13 @@ The application can then request a token calling the /token endpoint of the logi - Login - [x] Email Password - [x] Passkey - - [ ] IDPs - - [ ] Google + - [x] IDPs + - [x] Google - [ ] GitHub - [ ] GitLab - [ ] Azure - [ ] Apple - Register - - [x] Email Password - [x] Passkey diff --git a/docs/docs/guides/integrate/service-users/private-key-jwt.md b/docs/docs/guides/integrate/service-users/private-key-jwt.md index 65bd3d0f1e..da1c6de432 100644 --- a/docs/docs/guides/integrate/service-users/private-key-jwt.md +++ b/docs/docs/guides/integrate/service-users/private-key-jwt.md @@ -118,21 +118,27 @@ import datetime # Replace with your service user ID and private key service_user_id = "your_service_user_id" private_key = "-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----" +key_id = "your_key_id" # ZITADEL API URL (replace if needed) api_url = "your_custom_domain" # Generate JWT claims payload = { - "iss": "your_zitadel_instance_id", + "iss": service_user_id, "sub": service_user_id, "aud": api_url, - "exp": datetime.utcnow() + datetime.timedelta(minutes=5), - "iat": datetime.utcnow() + "exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5), + "iat": datetime.datetime.now(datetime.timezone.utc) +} + +header = { + "alg": "RS256", + "kid": key_id } # Sign the JWT using RS256 algorithm -encoded_jwt = jwt.encode(payload, private_key, algorithm="RS256") +encoded_jwt = jwt.encode(payload, private_key, algorithm="RS256", headers=header) print(f"Generated JWT: {encoded_jwt}") ``` diff --git a/docs/docs/guides/manage/console/default-settings.mdx b/docs/docs/guides/manage/console/default-settings.mdx index c29bb8d53d..c2b7773789 100644 --- a/docs/docs/guides/manage/console/default-settings.mdx +++ b/docs/docs/guides/manage/console/default-settings.mdx @@ -20,7 +20,7 @@ When you configure your default settings, you can set the following: - [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface. - [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations - [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more. -- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked. +- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked. - [**Domain settings**](#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings - [**Branding**](#branding): Appearance of the login interface. - [**Message Texts**](#message-texts): Text and internationalization for emails @@ -37,12 +37,12 @@ We recommend setting your Branding and SMTP settings initially as it will comfor In the Branding settings, you can upload you Logo for the login interface, set your own colors for buttons, background, links, and choose between multiple behavours. You don't need to be an expert as those settings can all be set without any knowledge of CSS. -| Setting | Description | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Logo | Upload your logo for the light and the dark design. This is used mainly in the login interface. | -| Icon | Upload your icon for the light and the dark design. Icons are used for smaller components. For example in console on the top left as the home button. | -| Colors | You can set four different colors to design your login page and email. (Background-, Primary-, Warn- and Font Color) | -| Font | Upload your custom font | +| Setting | Description | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Logo | Upload your logo for the light and the dark design. This is used mainly in the login interface. | +| Icon | Upload your icon for the light and the dark design. Icons are used for smaller components. For example in console on the top left as the home button. | +| Colors | You can set four different colors to design your login page and email. (Background-, Primary-, Warn- and Font Color) | +| Font | Upload your custom font | | Advanced Behavior | **Hide Loginname suffix**: If enabled, your loginname suffix (Domain) will not be shown in the login page. **Disable Watermark**: If you disable the watermark you will not see the "Powered by ZITADEL" in the login page | Make sure you click the "Apply configuration" button after you finish your configuration. This will ensure your design is visible for your customers. @@ -66,7 +66,14 @@ You can configure on which changes the users will be notified. The text of the m ### SMTP -On each instance we configure our default SMTP provider. To make sure, that you only send some E-Mails from domains you own. You need to add a custom domain on your instance. +On each instance we configure our default SMTP provider. To make sure, that you only send some E-Mails from domains you own, you need to add a custom domain on your instance. + +You can configure many SMTP providers using templates for popular providers. The templates will add known settings like host, port or default user and will suggest values for user and/or password. + +:::important +You have to activate your SMTP provider so Zitadel can use it to send your emails. Only one provider can be active. +::: + Go to the ZITADEL [customer portal](https://zitadel.cloud) to configure a custom domain. To configure your custom SMTP please fill the following fields: @@ -90,16 +97,16 @@ No default provider is configured to send some SMS to your users. If you like to The Login Policy defines how the login process should look like and which authentication options a user has to authenticate. -| Setting | Description | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Username Password allowed | Possibility to login with username and password. If this is disabled only login with external identity providers will be allowed | -| Register allowed | Enable self register possibility in the login ui, this enables username password registration as well as registration with configured external identity providers | -| External IDP allowed | Possibility to login with an external identity (e.g Google, Microsoft, Apple, etc), If you like to allow external Identity providers add them to the providers list | -| Hide password reset | Disable the self-service option for users to reset their password. | -| Domain discovery allowed | If this setting is enabled, the user does't not mandatory have to exist when entering the username. It is required to have verified domains on the organization. Example: ZITADEL is registered as organization with the domain zitadel.com and Entra ID as identity provider. A user enters john@zitadel.com in the login but the user doesn't exist. The domain can be mapped to the organization and therefore the user can be redirected to the Entra ID. -| Ignore unknown usernames | This setting can be enabled, if no error message should be shown if the user doesn't exist. Example: A user enters the login name john@zitadel.com, the user doesn't exist, but will be redirected to the password screen. After entering a password, the user will get an error that either username or password are wrong. | -| Disable login with email address | By default users can additionally [login with the email attribute](/docs/guides/solution-scenarios/configurations#use-an-email-address-as-username) of their user. Check this option to disable. | -| Disable login with phone number | By default users can additionally [login with the phonenumber attribute](/docs/guides/solution-scenarios/configurations#use-a-phone-number-as-username) of their user. Check this option to disable. | +| Setting | Description | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Username Password allowed | Possibility to login with username and password. If this is disabled only login with external identity providers will be allowed | +| Register allowed | Enable self register possibility in the login ui, this enables username password registration as well as registration with configured external identity providers | +| External IDP allowed | Possibility to login with an external identity (e.g Google, Microsoft, Apple, etc), If you like to allow external Identity providers add them to the providers list | +| Hide password reset | Disable the self-service option for users to reset their password. | +| Domain discovery allowed | If this setting is enabled, the user does't not mandatory have to exist when entering the username. It is required to have verified domains on the organization. Example: ZITADEL is registered as organization with the domain zitadel.com and Entra ID as identity provider. A user enters john@zitadel.com in the login but the user doesn't exist. The domain can be mapped to the organization and therefore the user can be redirected to the Entra ID. | +| Ignore unknown usernames | This setting can be enabled, if no error message should be shown if the user doesn't exist. Example: A user enters the login name john@zitadel.com, the user doesn't exist, but will be redirected to the password screen. After entering a password, the user will get an error that either username or password are wrong. | +| Disable login with email address | By default users can additionally [login with the email attribute](/docs/guides/solution-scenarios/configurations#use-an-email-address-as-username) of their user. Check this option to disable. | +| Disable login with phone number | By default users can additionally [login with the phonenumber attribute](/docs/guides/solution-scenarios/configurations#use-a-phone-number-as-username) of their user. Check this option to disable. | /ui/console/ Reasons why ZITADEL doesn't have a redirect URI: + - The login has not been called with an OIDC authorize request - The user landed on the login through an email link, e.g. Password Reset, Initialize User @@ -189,6 +197,7 @@ Define when an account should be locked. The following settings are available: - Maximum Password Attempts: When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger. +- Maximum OTP Attempts: When the user has reached the maximum (T)OTP attempts the account will be locked, If this is set to 0 the lockout will not trigger. If an account is locked, the administrator has to unlock it in the ZITADEL console diff --git a/docs/docs/guides/manage/console/organizations.mdx b/docs/docs/guides/manage/console/organizations.mdx index 99092c304e..6763a09670 100644 --- a/docs/docs/guides/manage/console/organizations.mdx +++ b/docs/docs/guides/manage/console/organizations.mdx @@ -108,7 +108,7 @@ Those settings are the same as on your instance. - [**Login Behavior and Access**](./default-settings#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface. - [**Identity Providers**](./default-settings#identity-providers): Define IDPs which are available for all organizations - [**Password Complexity**](./default-settings#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more. -- [**Lockout**](./default-settings#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked. +- [**Lockout**](./default-settings#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked. - [**Verified domains**](/docs/guides/manage/console/organizations#verify-your-domain-name): This is where you manage your organization specific domains which can be used to build usernames - [**Domain settings**](./default-settings#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings - [**Branding**](./default-settings#branding): Appearance of the login interface. diff --git a/docs/docs/guides/manage/console/roles.mdx b/docs/docs/guides/manage/console/roles.mdx index 5ca7831b33..d17b34629d 100644 --- a/docs/docs/guides/manage/console/roles.mdx +++ b/docs/docs/guides/manage/console/roles.mdx @@ -35,7 +35,7 @@ The **Group** is for making multiple roles selectable more easy. Now to make use of this roles, add an authorization. An authorization combines a user of your organization with one or multiple roles. -> You can also add users of other organizations, if you want to do so click on the hint below the username field. +> You can also add users of other organizations. Click on the hint below the username field to create an [external user grant](/docs/concepts/features/external-user-grant). Auth users diff --git a/docs/docs/legal/service-description/cloud-service-description.md b/docs/docs/legal/service-description/cloud-service-description.md index 900fdcb631..6fadafa85a 100644 --- a/docs/docs/legal/service-description/cloud-service-description.md +++ b/docs/docs/legal/service-description/cloud-service-description.md @@ -4,7 +4,7 @@ sidebar_label: Service description custom_edit_url: null --- -Last updated on November 15, 2023 +Last updated on April 5, 2024 This annex of the [Framework Agreement](../terms-of-service) describes the services offered by us. @@ -40,6 +40,35 @@ Operation and direct maintenance of ZITADEL will be done by you. You can freely choose the infrastructure and location to host ZITADEL. +### Responsibilities + +Your obligations while operating and using ZITADEL are detailed in our [terms of service](/docs/legal/terms-of-service#your-obligations) given the provisions in our [acceptable use policy](/docs/legal/policies/acceptable-use-policy). +When using ZITADEL Cloud, we may processing data on behalf according to the [data processing agreement](/docs/legal/data-processing-agreement). + +In a self-hosted setup, you will be responsible for the cost, operations, and availability of your infrastructure. +For DDoS, bot, and threat detection and protection we rely on external services in ZITADEL Cloud. +In a self-hosted setup, it is your responsibility to secure the infrastructure to protect confidentiality, integrity, and availability of your data. + +ZITADEL Cloud comes with pre-configured SMTP service, SMS service, and a generated domain name. +The SMTP service and SMS service are limited in use. +You should configure your own service providers for production use cases. +In a self-hosted setup, you will be responsible for SMTP / SMS services, domains and certificates. + +| Responsibility | ZITADEL Cloud | Self-Hosted | +| --- | --- | --- | +| Data / Information | Customer | Customer | +| User Access | Customer | Customer | +| SMTP Service | Customer (trial: ZITADEL) | Customer | +| SMS Service | Customer (trial: ZITADEL) | Customer | +| Custom Domain / TLS | Customer (trial: ZITADEL) | Customer | +| DDoS & Bot protection | ZITADEL | Customer | +| WAF / Threat detection | ZITADEL | Customer | +| [Backup](#backup) | ZITADEL | Customer | +| Networking | ZITADEL | Customer | +| Compute / Scaling | ZITADEL | Customer | +| Database | ZITADEL | Customer | +| Application | ZITADEL | ZITADEL | + ## Data location Data location refers to a region, consisting of one or many countries or territories, where the customer's data is being hosted. diff --git a/docs/docs/self-hosting/deploy/docker-compose.yaml b/docs/docs/self-hosting/deploy/docker-compose.yaml index 30e58f512e..e1639ec56a 100644 --- a/docs/docs/self-hosting/deploy/docker-compose.yaml +++ b/docs/docs/self-hosting/deploy/docker-compose.yaml @@ -30,10 +30,11 @@ services: environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=zitadel networks: - 'zitadel' healthcheck: - test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"] + test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres"] interval: '10s' timeout: '30s' retries: 5 diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 93f5577926..9ccd4ae0f0 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -310,9 +310,9 @@ module.exports = { groupPathsBy: "tag", }, }, - execution_v3: { - specPath: ".artifacts/openapi/zitadel/execution/v3alpha/execution_service.swagger.json", - outputDir: "docs/apis/resources/execution_service_v3", + action_v3: { + specPath: ".artifacts/openapi/zitadel/action/v3alpha/action_service.swagger.json", + outputDir: "docs/apis/resources/action_service_v3", sidebarOptions: { groupPathsBy: "tag", }, diff --git a/docs/sidebars.js b/docs/sidebars.js index 8b001971e5..ddb73badde 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -732,17 +732,17 @@ module.exports = { }, { type: "category", - label: "Execution Lifecycle (Preview)", + label: "Action Lifecycle (Preview)", link: { type: "generated-index", - title: "Execution Service API (Preview)", - slug: "/apis/resources/execution_service_v3", + title: "Action Service API (Preview)", + slug: "/apis/resources/action_service_v3", description: - "This API is intended to manage custom executions (previously known as actions) in a ZITADEL instance.\n" + + "This API is intended to manage custom executions and targets (previously known as actions) in a ZITADEL instance.\n" + "\n" + "This project is in Preview state. It can AND will continue breaking until the services provide the same functionality as the current actions.", }, - items: require("./docs/apis/resources/execution_service_v3/sidebar.js"), + items: require("./docs/apis/resources/action_service_v3/sidebar.js"), }, ], }, diff --git a/docs/static/img/concepts/features/external-user-grant.png b/docs/static/img/concepts/features/external-user-grant.png new file mode 100644 index 0000000000..16852cd393 Binary files /dev/null and b/docs/static/img/concepts/features/external-user-grant.png differ diff --git a/docs/static/img/guides/console/lockout.png b/docs/static/img/guides/console/lockout.png index 8f6c170d3b..8b87718fc9 100644 Binary files a/docs/static/img/guides/console/lockout.png and b/docs/static/img/guides/console/lockout.png differ diff --git a/docs/static/img/guides/console/smtp.png b/docs/static/img/guides/console/smtp.png index 4334264eda..900235cf77 100644 Binary files a/docs/static/img/guides/console/smtp.png and b/docs/static/img/guides/console/smtp.png differ diff --git a/docs/vercel.json b/docs/vercel.json index 8adaa184c6..df08ad33ab 100644 --- a/docs/vercel.json +++ b/docs/vercel.json @@ -38,6 +38,7 @@ { "source": "/docs/guides/integrate/access-zitadel-system-api", "destination": "/docs/guides/integrate/zitadel-apis/access-zitadel-system-api", "permanent": true }, { "source": "/docs/guides/integrate/event-api", "destination": "/docs/guides/integrate/zitadel-apis/event-api", "permanent": true }, { "source": "/docs/examples/call-zitadel-api/go", "destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-go", "permanent": true }, - { "source": "/docs/examples/call-zitadel-api/dot-net", "destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-dot-net", "permanent": true } + { "source": "/docs/examples/call-zitadel-api/dot-net", "destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-dot-net", "permanent": true }, + { "source": "/docs/guides/integrate/identity-providers", "destination": "/docs/guides/integrate/identity-providers/introduction", "permanent": true } ] } diff --git a/e2e/cypress/e2e/instance/settings/notifications.cy.ts b/e2e/cypress/e2e/instance/settings/notifications.cy.ts index 4e6856dcac..24ac3488c6 100644 --- a/e2e/cypress/e2e/instance/settings/notifications.cy.ts +++ b/e2e/cypress/e2e/instance/settings/notifications.cy.ts @@ -17,27 +17,118 @@ describe('instance notifications', () => { describe('smtp settings', () => { it(`should show SMTP provider settings`, () => { cy.visit(smtpPath); - cy.contains('SMTP Settings'); + cy.contains('SMTP Provider'); }); - it(`should add SMTP provider settings`, () => { + it(`should add Mailgun SMTP provider settings`, () => { + let rowSelector = `a:contains('Mailgun')`; cy.visit(smtpPath); - cy.get('[formcontrolname="senderAddress"]').clear().type('sender@example.com'); - cy.get('[formcontrolname="senderName"]').clear().type('Zitadel'); - cy.get('[formcontrolname="hostAndPort"]').clear().type('smtp.mailtrap.io:2525'); + cy.get(rowSelector).click(); + cy.get('[formcontrolname="hostAndPort"]').should('have.value', 'smtp.mailgun.org:587'); cy.get('[formcontrolname="user"]').clear().type('user@example.com'); - cy.get('[data-e2e="save-smtp-settings-button"]').click(); + cy.get('[formcontrolname="password"]').clear().type('password'); + cy.get('[data-e2e="continue-button"]').click(); + cy.get('[formcontrolname="senderAddress"]').clear().type('sender1@example.com'); + cy.get('[formcontrolname="senderName"]').clear().type('Test1'); + cy.get('[formcontrolname="replyToAddress"]').clear().type('replyto1@example.com'); + cy.get('[data-e2e="create-button"]').click(); cy.shouldConfirmSuccess(); - cy.get('[formcontrolname="senderAddress"]').should('have.value', 'sender@example.com'); - cy.get('[formcontrolname="senderName"]').should('have.value', 'Zitadel'); - cy.get('[formcontrolname="hostAndPort"]').should('have.value', 'smtp.mailtrap.io:2525'); - cy.get('[formcontrolname="user"]').should('have.value', 'user@example.com'); + cy.get('tr').contains('mailgun'); + cy.get('tr').contains('smtp.mailgun.org:587'); + cy.get('tr').contains('sender1@example.com'); }); - it(`should add SMTP provider password`, () => { + it(`should change Mailgun SMTP provider settings`, () => { + let rowSelector = `tr:contains('mailgun')`; cy.visit(smtpPath); - cy.get('[data-e2e="add-smtp-password-button"]').click(); - cy.get('[data-e2e="notification-setting-password"]').clear().type('dummy@example.com'); - cy.get('[data-e2e="save-notification-setting-password-button"]').click(); + cy.get(rowSelector).click(); + cy.get('[formcontrolname="hostAndPort"]').should('have.value', 'smtp.mailgun.org:587'); + cy.get('[formcontrolname="user"]').should('have.value', 'user@example.com'); + cy.get('[formcontrolname="user"]').clear().type('change@example.com'); + cy.get('[data-e2e="continue-button"]').click(); + cy.get('[formcontrolname="senderAddress"]').should('have.value', 'sender1@example.com'); + cy.get('[formcontrolname="senderName"]').should('have.value', 'Test1'); + cy.get('[formcontrolname="replyToAddress"]').should('have.value', 'replyto1@example.com'); + cy.get('[formcontrolname="senderAddress"]').clear().type('senderchange1@example.com'); + cy.get('[formcontrolname="senderName"]').clear().type('Change1'); + cy.get('[data-e2e="create-button"]').click(); cy.shouldConfirmSuccess(); + rowSelector = `tr:contains('mailgun')`; + cy.get(rowSelector).contains('mailgun'); + cy.get(rowSelector).contains('smtp.mailgun.org:587'); + cy.get(rowSelector).contains('senderchange1@example.com'); + }); + it(`should activate Mailgun SMTP provider settings`, () => { + let rowSelector = `tr:contains('smtp.mailgun.org:587')`; + cy.visit(smtpPath); + cy.get(rowSelector).find('[data-e2e="activate-provider-button"]').click({ force: true }); + cy.get('[data-e2e="confirm-dialog-button"]').click(); + cy.shouldConfirmSuccess(); + rowSelector = `tr:contains('smtp.mailgun.org:587')`; + cy.get(rowSelector).find('[data-e2e="active-provider"]'); + cy.get(rowSelector).contains('mailgun'); + cy.get(rowSelector).contains('smtp.mailgun.org:587'); + cy.get(rowSelector).contains('senderchange1@example.com'); + }); + it(`should add Mailjet SMTP provider settings`, () => { + let rowSelector = `a:contains('Mailjet')`; + cy.visit(smtpPath); + cy.get(rowSelector).click(); + cy.get('[formcontrolname="hostAndPort"]').should('have.value', 'in-v3.mailjet.com:587'); + cy.get('[formcontrolname="user"]').clear().type('user@example.com'); + cy.get('[formcontrolname="password"]').clear().type('password'); + cy.get('[data-e2e="continue-button"]').click(); + cy.get('[formcontrolname="senderAddress"]').clear().type('sender2@example.com'); + cy.get('[formcontrolname="senderName"]').clear().type('Test2'); + cy.get('[formcontrolname="replyToAddress"]').clear().type('replyto2@example.com'); + cy.get('[data-e2e="create-button"]').click(); + cy.shouldConfirmSuccess(); + rowSelector = `tr:contains('mailjet')`; + cy.get(rowSelector).contains('mailjet'); + cy.get(rowSelector).contains('in-v3.mailjet.com:587'); + cy.get(rowSelector).contains('sender2@example.com'); + }); + it(`should activate Mailjet SMTP provider settings an disable Mailgun`, () => { + let rowSelector = `tr:contains('in-v3.mailjet.com:587')`; + cy.visit(smtpPath); + cy.get(rowSelector).find('[data-e2e="activate-provider-button"]').click({ force: true }); + cy.get('[data-e2e="confirm-dialog-button"]').click(); + cy.shouldConfirmSuccess(); + cy.get(rowSelector).find('[data-e2e="active-provider"]'); + cy.get(rowSelector).contains('mailjet'); + cy.get(rowSelector).contains('in-v3.mailjet.com:587'); + cy.get(rowSelector).contains('sender2@example.com'); + rowSelector = `tr:contains('mailgun')`; + cy.get(rowSelector).find('[data-e2e="active-provider"]').should('not.exist'); + }); + it(`should deactivate Mailjet SMTP provider`, () => { + let rowSelector = `tr:contains('mailjet')`; + cy.visit(smtpPath); + cy.get(rowSelector).find('[data-e2e="deactivate-provider-button"]').click({ force: true }); + cy.get('[data-e2e="confirm-dialog-button"]').click(); + cy.shouldConfirmSuccess(); + rowSelector = `tr:contains('mailjet')`; + cy.get(rowSelector).find('[data-e2e="active-provider"]').should('not.exist'); + rowSelector = `tr:contains('mailgun')`; + cy.get(rowSelector).find('[data-e2e="active-provider"]').should('not.exist'); + }); + it(`should delete Mailjet SMTP provider`, () => { + let rowSelector = `tr:contains('mailjet')`; + cy.visit(smtpPath); + cy.get(rowSelector).find('[data-e2e="delete-provider-button"]').click({ force: true }); + cy.get('[data-e2e="confirm-dialog-input"]').focus().type('Test2'); + cy.get('[data-e2e="confirm-dialog-button"]').click(); + cy.shouldConfirmSuccess(); + rowSelector = `tr:contains('mailjet')`; + cy.get(rowSelector).should('not.exist'); + }); + it(`should delete Mailgun SMTP provider`, () => { + let rowSelector = `tr:contains('mailgun')`; + cy.visit(smtpPath); + cy.get(rowSelector).find('[data-e2e="delete-provider-button"]').click({ force: true }); + cy.get('[data-e2e="confirm-dialog-input"]').focus().type('Change1'); + cy.get('[data-e2e="confirm-dialog-button"]').click(); + cy.shouldConfirmSuccess(); + rowSelector = `tr:contains('mailgun')`; + cy.get(rowSelector).should('not.exist'); }); }); diff --git a/e2e/cypress/e2e/projects/projects.cy.ts b/e2e/cypress/e2e/projects/projects.cy.ts index 1016082894..36af2cd22e 100644 --- a/e2e/cypress/e2e/projects/projects.cy.ts +++ b/e2e/cypress/e2e/projects/projects.cy.ts @@ -46,7 +46,7 @@ describe('projects', () => { it('should add a role', () => { cy.get('[data-e2e="sidenav-element-roles"]').click(); cy.get('[data-e2e="add-new-role"]').click(); - cy.get('[formcontrolname="key"]').type(testRoleName); + cy.get('[formcontrolname="key"]').should('be.enabled').type(testRoleName); cy.get('[formcontrolname="displayName"]').type('e2eroleundertestdisplay'); cy.get('[formcontrolname="group"]').type('e2eroleundertestgroup'); cy.get('[data-e2e="save-button"]').click(); diff --git a/e2e/package-lock.json b/e2e/package-lock.json deleted file mode 100644 index 886191596a..0000000000 --- a/e2e/package-lock.json +++ /dev/null @@ -1,5366 +0,0 @@ -{ - "name": "zitadel-e2e", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "zitadel-e2e", - "version": "0.0.0", - "dependencies": { - "@types/pg": "^8.6.6", - "cypress-wait-until": "^1.7.2", - "jsonwebtoken": "^8.5.1", - "mochawesome": "^7.1.3", - "pg": "^8.8.0", - "prettier": "^2.7.1", - "typescript": "^4.8.4", - "uuid": "^9.0.0", - "wait-on": "^7.2.0" - }, - "devDependencies": { - "@types/node": "^18.8.3", - "cypress": "^13.3.1" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.10.4", - "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/request/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, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@types/node": { - "version": "18.18.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.4.tgz", - "integrity": "sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==" - }, - "node_modules/@types/pg": { - "version": "8.6.6", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", - "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "peer": true - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "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==", - "peer": true - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, - "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/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==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/axios/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==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "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==", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "peer": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "peer": true - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "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==" - }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/chalk/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "dev": true - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-table3": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", - "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cypress": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.1.tgz", - "integrity": "sha512-g4mJLZxYN+UAF2LMy3Znd4LBnUmS59Vynd81VES59RdW48Yt+QtR2cush3melOoVNz0PPbADpWr8DcUx6mif8Q==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.6.0", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.0", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - } - }, - "node_modules/cypress-wait-until": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz", - "integrity": "sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q==" - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "engines": { - "node": "*" - } - }, - "node_modules/dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "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==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "peer": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "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==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "dependencies": { - "async": "^3.2.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "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==", - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "peer": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "peer": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "node_modules/joi": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", - "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "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==", - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "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==" - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "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==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, - "engines": { - "node": "> 0.8" - } - }, - "node_modules/listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", - "peer": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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==", - "peer": true - }, - "node_modules/mochawesome": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-7.1.3.tgz", - "integrity": "sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==", - "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/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==", - "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/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==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mochawesome-report-generator/node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "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==", - "engines": { - "node": ">=12" - } - }, - "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==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "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==" - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "peer": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "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==", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "node_modules/pg": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", - "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.5.0", - "pg-pool": "^3.5.2", - "pg-protocol": "^1.5.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", - "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "peer": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/tcomb": { - "version": "3.2.29", - "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", - "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==" - }, - "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==", - "dependencies": { - "tcomb": "^3.0.0" - } - }, - "node_modules/throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/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==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/wait-on": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", - "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", - "dependencies": { - "axios": "^1.6.1", - "joi": "^17.11.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "rxjs": "^7.8.1" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "peer": true - }, - "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==", - "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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "peer": true, - "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/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "peer": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true - }, - "@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.10.4", - "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "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 - } - } - }, - "@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "@types/node": { - "version": "18.18.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.4.tgz", - "integrity": "sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==" - }, - "@types/pg": { - "version": "8.6.6", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", - "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", - "requires": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "peer": true - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "peer": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, - "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" - }, - "dependencies": { - "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" - } - }, - "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==" - } - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "peer": true - }, - "blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "peer": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "peer": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "peer": true - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "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==" - }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" - }, - "cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "peer": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "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==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "peer": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-table3": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", - "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "string-width": "^4.2.0" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "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" - } - }, - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true - }, - "common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cypress": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.1.tgz", - "integrity": "sha512-g4mJLZxYN+UAF2LMy3Znd4LBnUmS59Vynd81VES59RdW48Yt+QtR2cush3melOoVNz0PPbADpWr8DcUx6mif8Q==", - "dev": true, - "requires": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.6.0", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.0", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - } - }, - "cypress-wait-until": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz", - "integrity": "sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q==" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" - }, - "dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", - "dev": true - }, - "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" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "peer": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "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==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "requires": { - "pify": "^2.2.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "peer": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "peer": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "peer": true - }, - "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true, - "peer": true - }, - "fsu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", - "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "requires": { - "async": "^3.2.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "peer": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "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==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "peer": true - }, - "http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "peer": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "peer": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "peer": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "peer": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "peer": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "joi": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", - "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "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==" - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "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": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "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" - } - }, - "lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true - }, - "listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "peer": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "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.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" - }, - "lodash.isfunction": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" - }, - "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.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==" - }, - "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==" - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "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" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "peer": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", - "peer": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "peer": true - }, - "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==", - "peer": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "peer": true - } - } - }, - "mochawesome": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-7.1.3.tgz", - "integrity": "sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==", - "requires": { - "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" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } - }, - "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==", - "requires": { - "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" - }, - "dependencies": { - "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==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "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==" - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "peer": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "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.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" - }, - "ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "peer": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "peer": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "peer": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "pg": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", - "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.5.0", - "pg-pool": "^3.5.2", - "pg-protocol": "^1.5.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - } - }, - "pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" - }, - "pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" - }, - "pg-pool": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", - "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", - "requires": {} - }, - "pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" - }, - "pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - } - }, - "pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "requires": { - "split2": "^4.1.0" - } - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "peer": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" - }, - "postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" - }, - "postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "requires": { - "xtend": "^4.0.0" - } - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - }, - "qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "peer": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "peer": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "requires": { - "tslib": "^2.1.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "peer": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "peer": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "tcomb": { - "version": "3.2.29", - "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", - "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==" - }, - "tcomb-validation": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", - "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", - "requires": { - "tcomb": "^3.0.0" - } - }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "peer": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - }, - "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "wait-on": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", - "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", - "requires": { - "axios": "^1.6.1", - "joi": "^17.11.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "rxjs": "^7.8.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "peer": true - }, - "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==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "peer": true, - "requires": { - "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" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "peer": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "peer": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "peer": true - } - } -} diff --git a/e2e/yarn.lock b/e2e/yarn.lock new file mode 100644 index 0000000000..84669a7fe1 --- /dev/null +++ b/e2e/yarn.lock @@ -0,0 +1,1729 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cypress/request@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" + integrity sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + http-signature "~1.3.6" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "6.10.4" + safe-buffer "^5.1.2" + tough-cookie "^4.1.3" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + +"@cypress/xvfb@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@types/node@*": + version "20.7.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.0.tgz#c03de4572f114a940bc2ca909a33ddb2b925e470" + integrity sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg== + +"@types/node@^18.8.3": + version "18.18.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.0.tgz#bd19d5133a6e5e2d0152ec079ac27c120e7f1763" + integrity sha512-3xA4X31gHT1F1l38ATDIL9GpRLdwVhnEFC8Uikv5ZLlXATwrCYyPq7ZWHxzxc3J/30SUiwiYT+bQe0/XvKlWbw== + +"@types/pg@^8.6.6": + version "8.10.3" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.10.3.tgz#39b3acba4f313a65c8fbb4b241fcb21cc1ba4126" + integrity sha512-BACzsw64lCZesclRpZGu55tnqgFAYcrCBP92xLh1KLypZLCOsvJTSTgaoFVTy3lCys/aZTQzfeDxtjwrvdzL2g== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== + +"@types/sizzle@^2.3.2": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.4.tgz#cd6531924f60834fa4a1b8081f9eecf9bb1117f0" + integrity sha512-jA2llq2zNkg8HrALI7DtWzhALcVH0l7i89yhY3iBdOz6cBPeACoFq+fkQrjHA39t1hnSFOboZ7A/AY5MMZSlag== + +"@types/yauzl@^2.9.1": + version "2.10.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.1.tgz#4e8f299f0934d60f36c74f59cb5a8483fd786691" + integrity sha512-CHzgNU3qYBnp/O4S3yv2tXPlvMTq0YWSTVg2/JYLqWZGHwwgJGAwd00poay/11asPq8wLFwHzubyInqHIFmmiw== + dependencies: + "@types/node" "*" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== + +axios@^1.6.1: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +blob-util@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + +buffer@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +cachedir@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" + integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +check-more-types@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== + +ci-info@^3.2.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@~0.6.1: + version "0.6.3" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.16: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cypress-wait-until@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz#7f534dd5a11c89b65359e7a0210f20d3dfc22107" + integrity sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q== + +cypress@^13.3.1: + version "13.7.2" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.7.2.tgz#61e841382abb20e0a9a063086ee0d850af3ef6bc" + integrity sha512-FF5hFI5wlRIHY8urLZjJjj/YvfCBrRpglbZCLr/cYcL9MdDe0+5usa8kTIrDHthlEc9lwihbkb5dmwqBDNS2yw== + dependencies: + "@cypress/request" "^3.0.0" + "@cypress/xvfb" "^1.2.4" + "@types/sinonjs__fake-timers" "8.1.1" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + buffer "^5.7.1" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" + commander "^6.2.1" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.4" + enquirer "^2.3.6" + eventemitter2 "6.4.7" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-ci "^3.0.1" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.8" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + process "^0.11.10" + proxy-from-env "1.0.0" + request-progress "^3.0.0" + semver "^7.5.3" + supports-color "^8.1.1" + tmp "~0.2.1" + untildify "^4.0.0" + yauzl "^2.10.0" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + +dateformat@^4.5.1: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +dayjs@^1.10.4: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diff@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@^2.3.6: + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eventemitter2@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" + integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== + +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +executable@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsu@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834" + integrity sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== + dependencies: + async "^3.2.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + 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" + +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== + dependencies: + assert-plus "^1.0.0" + jsprim "^2.0.2" + sshpk "^1.14.1" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-installed-globally@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + +joi@^17.11.0: + version "17.12.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d" + integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lazy-ass@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== + +listr2@^3.8.3: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.1" + through "^2.3.8" + wrap-ansi "^7.0.0" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg== + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0, lodash.once@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mochawesome-report-generator@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz#65a30a11235ba7a68e1cf0ca1df80d764b93ae78" + integrity sha512-Ghw8JhQFizF0Vjbtp9B0i//+BOkV5OWcQCPpbO0NGOoxV33o+gKDYU0Pr2pGxkIHnqZ+g5mYiXF7GMNgAcDpSg== + 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" + +mochawesome@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/mochawesome/-/mochawesome-7.1.3.tgz#07b358138f37f5b07b51a1b255d84babfa36fa83" + integrity sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ== + 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" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +obuf@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" + integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + +pg-pool@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" + integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== + +pg-protocol@*, pg-protocol@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" + integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg-types@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.1.tgz#31857e89d00a6c66b06a14e907c3deec03889542" + integrity sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.0.1" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + +pg@^8.8.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" + integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.6.2" + pg-pool "^3.6.1" + pg-protocol "^1.6.0" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + +pify@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-date@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.0.1.tgz#638b62e5c33764c292d37b08f5257ecb09231457" + integrity sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" + integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== + +prettier@^2.7.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +qs@6.10.4: + version "6.10.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" + integrity sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g== + dependencies: + side-channel "^1.0.4" + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg== + dependencies: + throttleit "^1.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rxjs@^7.5.1, rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.6.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +sshpk@^1.14.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +tcomb-validation@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tcomb-validation/-/tcomb-validation-3.4.1.tgz#a7696ec176ce56a081d9e019f8b732a5a8894b65" + integrity sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA== + dependencies: + tcomb "^3.0.0" + +tcomb@^3.0.0, tcomb@^3.2.17: + version "3.2.29" + resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-3.2.29.tgz#32404fe9456d90c2cf4798682d37439f1ccc386c" + integrity sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ== + +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + integrity sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +tough-cookie@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@^4.8.4: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +validator@^13.6.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.11.0.tgz#23ab3fd59290c61248364eabf4067f04955fbb1b" + integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +wait-on@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.2.0.tgz#d76b20ed3fc1e2bebc051fae5c1ff93be7892928" + integrity sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ== + dependencies: + axios "^1.6.1" + joi "^17.11.0" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.1" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.2.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" diff --git a/go.mod b/go.mod index 2203263fc0..99fcd1f494 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module github.com/zitadel/zitadel -go 1.21.1 +go 1.22 // https://go.dev/doc/toolchain -toolchain go1.21.8 +toolchain go1.22.2 require ( - cloud.google.com/go/storage v1.39.0 + cloud.google.com/go/storage v1.40.0 github.com/BurntSushi/toml v1.3.2 github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.21.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.22.0 github.com/Masterminds/squirrel v1.5.4 github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b github.com/benbjohnson/clock v1.3.5 github.com/boombuler/barcode v1.0.1 - github.com/brianvoe/gofakeit/v6 v6.26.4 - github.com/cockroachdb/cockroach-go/v2 v2.3.6 + github.com/brianvoe/gofakeit/v6 v6.28.0 + github.com/cockroachdb/cockroach-go/v2 v2.3.7 github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be github.com/crewjam/saml v0.4.14 github.com/descope/virtualwebauthn v1.0.2 @@ -25,12 +25,12 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.0.4 github.com/fatih/color v1.16.0 github.com/gabriel-vasile/mimetype v1.4.3 - github.com/go-jose/go-jose/v3 v3.0.3 - github.com/go-ldap/ldap/v3 v3.4.6 - github.com/go-webauthn/webauthn v0.10.1 + github.com/go-jose/go-jose/v4 v4.0.1 + github.com/go-ldap/ldap/v3 v3.4.7 + github.com/go-webauthn/webauthn v0.10.2 github.com/gorilla/csrf v1.7.2 github.com/gorilla/mux v1.8.1 - github.com/gorilla/schema v1.2.1 + github.com/gorilla/schema v1.3.0 github.com/gorilla/securecookie v1.1.2 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 @@ -43,7 +43,7 @@ require ( github.com/k3a/html2text v1.2.1 github.com/kevinburke/twilio-go v0.0.0-20231009225535-38b36b35294d github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/minio/minio-go/v7 v7.0.68 + github.com/minio/minio-go/v7 v7.0.69 github.com/mitchellh/mapstructure v1.5.0 github.com/muesli/gamut v0.3.1 github.com/muhlemmer/gu v0.3.1 @@ -60,47 +60,48 @@ require ( github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 github.com/ttacon/libphonenumber v1.2.1 github.com/zitadel/logging v0.6.0 - github.com/zitadel/oidc/v3 v3.18.0 + github.com/zitadel/oidc/v3 v3.21.0 github.com/zitadel/passwap v0.5.0 github.com/zitadel/saml v0.1.3 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 - go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 - go.opentelemetry.io/otel/exporters/prometheus v0.46.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 - go.opentelemetry.io/otel/metric v1.24.0 - go.opentelemetry.io/otel/sdk v1.24.0 - go.opentelemetry.io/otel/sdk/metric v1.24.0 - go.opentelemetry.io/otel/trace v1.24.0 + github.com/zitadel/schema v1.3.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.50.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 + go.opentelemetry.io/otel v1.25.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 + go.opentelemetry.io/otel/exporters/prometheus v0.47.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.25.0 + go.opentelemetry.io/otel/metric v1.25.0 + go.opentelemetry.io/otel/sdk v1.25.0 + go.opentelemetry.io/otel/sdk/metric v1.25.0 + go.opentelemetry.io/otel/trace v1.25.0 go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.21.0 - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/net v0.22.0 - golang.org/x/oauth2 v0.18.0 - golang.org/x/sync v0.6.0 + golang.org/x/crypto v0.22.0 + golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 + golang.org/x/net v0.24.0 + golang.org/x/oauth2 v0.19.0 + golang.org/x/sync v0.7.0 golang.org/x/text v0.14.0 - google.golang.org/api v0.168.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 - google.golang.org/grpc v1.62.1 + google.golang.org/api v0.172.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240412170617-26222e5d3d56 + google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 sigs.k8s.io/yaml v1.4.0 ) require ( - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.45.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.46.0 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/go-webauthn/x v0.1.8 // indirect + github.com/go-webauthn/x v0.1.9 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-tpm v0.9.0 // indirect - github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect + github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect @@ -108,31 +109,30 @@ require ( github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/zenazn/goji v1.0.1 // indirect - github.com/zitadel/schema v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto v0.0.0-20240304212257-790db918fca8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect + google.golang.org/genproto v0.0.0-20240412170617-26222e5d3d56 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 // indirect ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.25.0 // indirect + cloud.google.com/go v0.112.2 // indirect + cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/trace v1.10.5 // indirect + cloud.google.com/go/iam v1.1.7 // indirect + cloud.google.com/go/trace v1.10.6 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/amdonov/xmlsig v0.1.0 // indirect github.com/beevik/etree v1.3.0 github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dlclark/regexp2 v1.11.0 // indirect @@ -150,7 +150,7 @@ require ( github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-errors/errors v1.5.1 // indirect - github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect @@ -158,7 +158,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.6.0 github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect @@ -171,7 +171,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7 // indirect github.com/kevinburke/rest v0.0.0-20231107185522-a9c371f90234 // indirect - github.com/klauspost/compress v1.17.7 // indirect + github.com/klauspost/compress v1.17.8 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -184,9 +184,9 @@ require ( github.com/muesli/kmeans v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.19.0 - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.49.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.52.3 // indirect + github.com/prometheus/procfs v0.13.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/russellhaering/goxmldsig v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 @@ -196,14 +196,13 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/sys v0.18.0 - google.golang.org/appengine v1.6.8 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect + golang.org/x/sys v0.19.0 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - nhooyr.io/websocket v1.8.10 // indirect + nhooyr.io/websocket v1.8.11 // indirect ) diff --git a/go.sum b/go.sum index 875e3ce3c4..4b6f340626 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,23 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU= -cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE= +cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= -cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= -cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= -cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= -cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= -cloud.google.com/go/storage v1.39.0 h1:brbjUa4hbDHhpQf48tjqMaXEV+f1OGoaTmQau9tmCsA= -cloud.google.com/go/storage v1.39.0/go.mod h1:OAEj/WZwUYjA3YHQ10/YcN9ttGuEpLwvaoyBXIPikEk= -cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= -cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= +cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/monitoring v1.18.1 h1:0yvFXK+xQd95VKo6thndjwnJMno7c7Xw1CwMByg0B+8= +cloud.google.com/go/monitoring v1.18.1/go.mod h1:52hTzJ5XOUMRm7jYi7928aEdVxBEmGwA0EjNJXIBvt8= +cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= +cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= +cloud.google.com/go/trace v1.10.6 h1:XF0Ejdw0NpRfAvuZUeQe3ClAG4R/9w5JYICo7l2weaw= +cloud.google.com/go/trace v1.10.6/go.mod h1:EABXagUjxGuKcZMy4pXyz0fJpE5Ghog3jzTxcEsVJS4= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= @@ -27,12 +27,12 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.21.0 h1:OEgjQy1rH4Fbn5IpuI9d0uhLl+j6DkDvh9Q2Ucd6GK8= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.21.0/go.mod h1:EUfJ8lb3pjD8VasPPwqIvG2XVCE6DOT8tY5tcwbWA+A= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.45.0 h1:/BF7rO6PYcmFoyJrq6HA3LqQpFSQei9aNuO1fvV3OqU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.45.0/go.mod h1:WntFIMzxcU+PMBuekFc34UOsEZ9sP+vsnBYTyaNBkOs= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.45.0 h1:o/Nf55GfyLwGDaHkVAkRGgBXeExce73L6N9w2PZTB3k= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.45.0/go.mod h1:qkFPtMouQjW5ugdHIOthiTbweVHUTqbS0Qsu55KqXks= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.22.0 h1:xl4IRfBXPZxwu7dIza8n6wdX5zEJpi0boF5dX22MbYE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.22.0/go.mod h1:P69hhmQh4zwnU5iEdGVowFWg1DiP9x2KsCYBOIaP4us= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.46.0 h1:vaXjFX09ygxNxAiHwByzPBVKltYFVZR8HN4U3TR4vn8= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.46.0/go.mod h1:V28hx+cUCZC9e3qcqszMb+Sbt8cQZtHTiXOmyDzoDOg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.46.0 h1:xlfPHZ5QFvHad9KmrVDoaPpJUT/XluwNDMNHn+k7z/s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.46.0/go.mod h1:mzI44HpPp75Z8/a1sJP1asdHdu7Wui7t10SZ9EEPPnM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= @@ -50,8 +50,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/amdonov/xmlsig v0.1.0 h1:i0iQ3neKLmUhcfIRgiiR3eRPKgXZj+n5lAfqnfKoeXI= github.com/amdonov/xmlsig v0.1.0/go.mod h1:jTR/jO0E8fSl/cLvMesP+RjxyV4Ux4WL1Ip64ZnQpA0= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -81,25 +81,25 @@ github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/brianvoe/gofakeit/v6 v6.26.4 h1:+7JwTAXxw46Hdo1hA/F92Wi7x8vTwbjdFtBWYdm8eII= -github.com/brianvoe/gofakeit/v6 v6.26.4/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/cockroach-go/v2 v2.3.6 h1:Wlv9TzkrG9V7i6u8dEtmXPrBzvfFp+CgJNs696rAajM= -github.com/cockroachdb/cockroach-go/v2 v2.3.6/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= +github.com/cockroachdb/cockroach-go/v2 v2.3.7 h1:nq5GYDuA2zIR/kdLkVLTg7oHTw0UbGU9RWpC+OZVYYU= +github.com/cockroachdb/cockroach-go/v2 v2.3.7/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= @@ -214,14 +214,14 @@ github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= -github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= +github.com/go-ldap/ldap/v3 v3.4.7 h1:3Hbd7mIB1qjd3Ra59fI3JYea/t5kykFu2CVHBca9koE= +github.com/go-ldap/ldap/v3 v3.4.7/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -234,18 +234,19 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-webauthn/webauthn v0.10.1 h1:+RFKj4yHPy282teiiy5sqTYPfRilzBpJyedrz9KsNFE= -github.com/go-webauthn/webauthn v0.10.1/go.mod h1:a7BwAtrSMkeuJXtIKz433Av99nAv01pdfzB0a9xkDnI= -github.com/go-webauthn/x v0.1.8 h1:f1C6k1AyUlDvnIzWSW+G9rN9nbp1hhLXZagUtyxZ8nc= -github.com/go-webauthn/x v0.1.8/go.mod h1:i8UNlGVt3oy6oAFcP4SZB1djZLx/4pbekCbWowjTaJg= +github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4= +github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs= +github.com/go-webauthn/x v0.1.9 h1:v1oeLmoaa+gPOaZqUdDentu6Rl7HkSSsmOT6gxEQHhE= +github.com/go-webauthn/x v0.1.9/go.mod h1:pJNMlIMP1SU7cN8HNlKJpLEnFHCygLCvaLZ8a1xeoQA= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= @@ -294,8 +295,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -307,7 +306,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -319,20 +317,19 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= -github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q= -github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= -github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -344,10 +341,12 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM= -github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= +github.com/gorilla/schema v1.3.0 h1:rbciOzXAx3IB8stEFnfTwO3sYa6EWlQk79XdyustPDA= +github.com/gorilla/schema v1.3.0/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -379,6 +378,9 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -409,6 +411,18 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52 h1:jny9eqYPwkG8IVy7foUoRjQmFLcArCSz+uPsL6KS0HQ= github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52/go.mod h1:RDZ+4PR3mDOtTpVbI0qBE+rdhmtIrtbssiNn38/1OWA= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -450,8 +464,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -501,8 +515,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ= -github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= +github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0= +github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -565,8 +579,8 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= +github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -595,23 +609,23 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= -github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= +github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= +github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= +github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -676,6 +690,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -705,8 +720,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= -github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -714,8 +729,8 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zitadel/logging v0.6.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank= github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow= -github.com/zitadel/oidc/v3 v3.18.0 h1:NGdxLIYbuvaIqc/Na1fu61wBXIbqufp7LsFNV1bXOQw= -github.com/zitadel/oidc/v3 v3.18.0/go.mod h1:tY75hMcm07McpPXzvgvFTNPefPYDnHRYZQZVn9gtAps= +github.com/zitadel/oidc/v3 v3.21.0 h1:dvhPLAOCJQHxZq+1vqd2+TYu1EzwrHhnPSSh4nVamgo= +github.com/zitadel/oidc/v3 v3.21.0/go.mod h1:3uCwJc680oWoTBdzIppMZQS+VNxq+sVcwgodbreuatM= github.com/zitadel/passwap v0.5.0 h1:kFMoRyo0GnxtOz7j9+r/CsRwSCjHGRaAKoUe69NwPvs= github.com/zitadel/passwap v0.5.0/go.mod h1:uqY7D3jqdTFcKsW0Q3Pcv5qDMmSHpVTzUZewUKC1KZA= github.com/zitadel/saml v0.1.3 h1:LI4DOCVyyU1qKPkzs3vrGcA5J3H4pH3+CL9zr9ShkpM= @@ -729,30 +744,30 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= -go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= -go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= -go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.50.0 h1:zvpPXY7RfYAGSdYQLjp6zxdJNSYD/+FFoCTQN9IPxBs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.50.0/go.mod h1:BMn8NB1vsxTljvuorms2hyOs8IBuuBEq0pl7ltOfy30= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 h1:cEPbyTSEHlQR89XVlyo78gqluF8Y3oMeBkXGWzQsfXY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0/go.mod h1:DKdbWcT4GH1D0Y3Sqt/PFXt2naRKDWtU+eE6oLdFNA8= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 h1:vOL89uRfOCCNIjkisd0r7SEdJF3ZJFyCNY34fdZs8eU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo= +go.opentelemetry.io/otel/exporters/prometheus v0.47.0 h1:OL6yk1Z/pEGdDnrBbxSsH+t4FY1zXfBRGd7bjwhlMLU= +go.opentelemetry.io/otel/exporters/prometheus v0.47.0/go.mod h1:xF3N4OSICZDVbbYZydz9MHFro1RjmkPUKEvar2utG+Q= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.25.0 h1:0vZZdECYzhTt9MKQZ5qQ0V+J3MFu4MQaQ3COfugF+FQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.25.0/go.mod h1:e7iXx3HjaSSBXfy9ykVUlupS2Vp7LBIBuT21ousM2Hk= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= +go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= +go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= +go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= +go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -781,15 +796,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= +golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -824,6 +840,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -840,14 +857,17 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -857,8 +877,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -898,19 +918,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -919,7 +939,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -954,13 +973,11 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.168.0 h1:MBRe+Ki4mMN93jhDDbpuRLjRddooArz4FeSObvUMmjY= -google.golang.org/api v0.168.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= +google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -970,12 +987,12 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20240304212257-790db918fca8 h1:Fe8QycXyEd9mJgnwB9kmw00WgB43eQ/xYO5C6gceybQ= -google.golang.org/genproto v0.0.0-20240304212257-790db918fca8/go.mod h1:yA7a1bW1kwl459Ol0m0lV4hLTfrL/7Bkk4Mj2Ir1mWI= -google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 h1:8eadJkXbwDEMNwcB5O0s5Y5eCfyuCLdvaiOIaGTrWmQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto v0.0.0-20240412170617-26222e5d3d56 h1:LlcUFJ4BLmJVS4Kly+WCK7LQqcevmycHj88EPgyhNx8= +google.golang.org/genproto v0.0.0-20240412170617-26222e5d3d56/go.mod h1:n1CaIKYMIlxFt1zJE/1kU40YpSL0drGMbl0Idum1VSs= +google.golang.org/genproto/googleapis/api v0.0.0-20240412170617-26222e5d3d56 h1:KuFzeG+qPmpT8KpJXcrKAyeHhn64dgEICWlccP9qp0U= +google.golang.org/genproto/googleapis/api v0.0.0-20240412170617-26222e5d3d56/go.mod h1:wTHjrkbcS8AoQbb/0v9bFIPItZQPAsyVfgG9YPUhjAM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 h1:zviK8GX4VlMstrK3JkexM5UHjH1VOkRebH9y3jhSBGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -991,8 +1008,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1003,8 +1020,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1045,8 +1060,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= +nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/actions/http_module_config.go b/internal/actions/http_module_config.go index 06ee4c255f..d10ad39676 100644 --- a/internal/actions/http_module_config.go +++ b/internal/actions/http_module_config.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/mitchellh/mapstructure" + "github.com/zitadel/zitadel/internal/zerrors" ) diff --git a/internal/api/authz/system_token.go b/internal/api/authz/system_token.go index da4c7d81d3..9461bae3f7 100644 --- a/internal/api/authz/system_token.go +++ b/internal/api/authz/system_token.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/zitadel/internal/crypto" diff --git a/internal/api/grpc/execution/v3alpha/execution.go b/internal/api/grpc/action/v3alpha/execution.go similarity index 67% rename from internal/api/grpc/execution/v3alpha/execution.go rename to internal/api/grpc/action/v3alpha/execution.go index 5aff2e1098..dde4727c2e 100644 --- a/internal/api/grpc/execution/v3alpha/execution.go +++ b/internal/api/grpc/action/v3alpha/execution.go @@ -1,4 +1,4 @@ -package execution +package action import ( "context" @@ -7,28 +7,32 @@ import ( "github.com/zitadel/zitadel/internal/api/grpc/object/v2" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" ) -func (s *Server) ListExecutionFunctions(_ context.Context, _ *execution.ListExecutionFunctionsRequest) (*execution.ListExecutionFunctionsResponse, error) { - return &execution.ListExecutionFunctionsResponse{ +func (s *Server) ListExecutionFunctions(_ context.Context, _ *action.ListExecutionFunctionsRequest) (*action.ListExecutionFunctionsResponse, error) { + return &action.ListExecutionFunctionsResponse{ Functions: s.ListActionFunctions(), }, nil } -func (s *Server) ListExecutionMethods(_ context.Context, _ *execution.ListExecutionMethodsRequest) (*execution.ListExecutionMethodsResponse, error) { - return &execution.ListExecutionMethodsResponse{ +func (s *Server) ListExecutionMethods(_ context.Context, _ *action.ListExecutionMethodsRequest) (*action.ListExecutionMethodsResponse, error) { + return &action.ListExecutionMethodsResponse{ Methods: s.ListGRPCMethods(), }, nil } -func (s *Server) ListExecutionServices(_ context.Context, _ *execution.ListExecutionServicesRequest) (*execution.ListExecutionServicesResponse, error) { - return &execution.ListExecutionServicesResponse{ +func (s *Server) ListExecutionServices(_ context.Context, _ *action.ListExecutionServicesRequest) (*action.ListExecutionServicesResponse, error) { + return &action.ListExecutionServicesResponse{ Services: s.ListGRPCServices(), }, nil } -func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRequest) (*execution.SetExecutionResponse, error) { +func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + set := &command.SetExecution{ Targets: req.GetTargets(), Includes: req.GetIncludes(), @@ -37,7 +41,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe var err error var details *domain.ObjectDetails switch t := req.GetCondition().GetConditionType().(type) { - case *execution.Condition_Request: + case *action.Condition_Request: cond := &command.ExecutionAPICondition{ Method: t.Request.GetMethod(), Service: t.Request.GetService(), @@ -47,7 +51,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe if err != nil { return nil, err } - case *execution.Condition_Response: + case *action.Condition_Response: cond := &command.ExecutionAPICondition{ Method: t.Response.GetMethod(), Service: t.Response.GetService(), @@ -57,7 +61,7 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe if err != nil { return nil, err } - case *execution.Condition_Event: + case *action.Condition_Event: cond := &command.ExecutionEventCondition{ Event: t.Event.GetEvent(), Group: t.Event.GetGroup(), @@ -67,22 +71,26 @@ func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRe if err != nil { return nil, err } - case *execution.Condition_Function: + case *action.Condition_Function: details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), set, authz.GetInstance(ctx).InstanceID()) if err != nil { return nil, err } } - return &execution.SetExecutionResponse{ + return &action.SetExecutionResponse{ Details: object.DomainToDetailsPb(details), }, nil } -func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecutionRequest) (*execution.DeleteExecutionResponse, error) { +func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutionRequest) (*action.DeleteExecutionResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + var err error var details *domain.ObjectDetails switch t := req.GetCondition().GetConditionType().(type) { - case *execution.Condition_Request: + case *action.Condition_Request: cond := &command.ExecutionAPICondition{ Method: t.Request.GetMethod(), Service: t.Request.GetService(), @@ -92,7 +100,7 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu if err != nil { return nil, err } - case *execution.Condition_Response: + case *action.Condition_Response: cond := &command.ExecutionAPICondition{ Method: t.Response.GetMethod(), Service: t.Response.GetService(), @@ -102,7 +110,7 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu if err != nil { return nil, err } - case *execution.Condition_Event: + case *action.Condition_Event: cond := &command.ExecutionEventCondition{ Event: t.Event.GetEvent(), Group: t.Event.GetGroup(), @@ -112,13 +120,13 @@ func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecu if err != nil { return nil, err } - case *execution.Condition_Function: + case *action.Condition_Function: details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), authz.GetInstance(ctx).InstanceID()) if err != nil { return nil, err } } - return &execution.DeleteExecutionResponse{ + return &action.DeleteExecutionResponse{ Details: object.DomainToDetailsPb(details), }, nil } diff --git a/internal/api/grpc/execution/v3alpha/execution_integration_test.go b/internal/api/grpc/action/v3alpha/execution_integration_test.go similarity index 54% rename from internal/api/grpc/execution/v3alpha/execution_integration_test.go rename to internal/api/grpc/action/v3alpha/execution_integration_test.go index 59f07d7303..752e9b546a 100644 --- a/internal/api/grpc/execution/v3alpha/execution_integration_test.go +++ b/internal/api/grpc/action/v3alpha/execution_integration_test.go @@ -1,6 +1,6 @@ //go:build integration -package execution_test +package action_test import ( "context" @@ -10,28 +10,29 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" ) func TestServer_SetExecution_Request(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - req *execution.SetExecutionRequest - want *execution.SetExecutionResponse + req *action.SetExecutionRequest + want *action.SetExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_All{All: true}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_All{All: true}, }, }, }, @@ -41,10 +42,10 @@ func TestServer_SetExecution_Request(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{}, }, }, Targets: []string{targetResp.GetId()}, @@ -54,11 +55,11 @@ func TestServer_SetExecution_Request(t *testing.T) { { name: "method, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.NotExistingService/List", }, }, @@ -71,11 +72,11 @@ func TestServer_SetExecution_Request(t *testing.T) { { name: "method, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/ListSessions", }, }, @@ -83,7 +84,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -93,11 +94,11 @@ func TestServer_SetExecution_Request(t *testing.T) { { name: "service, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Service{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Service{ Service: "NotExistingService", }, }, @@ -110,11 +111,11 @@ func TestServer_SetExecution_Request(t *testing.T) { { name: "service, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Service{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Service{ Service: "zitadel.session.v2beta.SessionService", }, }, @@ -122,7 +123,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -132,11 +133,11 @@ func TestServer_SetExecution_Request(t *testing.T) { { name: "all, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_All{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_All{ All: true, }, }, @@ -144,7 +145,7 @@ func TestServer_SetExecution_Request(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -167,13 +168,15 @@ func TestServer_SetExecution_Request(t *testing.T) { } func TestServer_SetExecution_Request_Include(t *testing.T) { + ensureFeatureEnabled(t) + targetResp := Tester.CreateTarget(CTX, t) executionCond := "request" Tester.SetExecution(CTX, t, - &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_All{ + &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_All{ All: true, }, }, @@ -186,18 +189,18 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { tests := []struct { name string ctx context.Context - req *execution.SetExecutionRequest - want *execution.SetExecutionResponse + req *action.SetExecutionRequest + want *action.SetExecutionResponse wantErr bool }{ { name: "method, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/ListSessions", }, }, @@ -205,7 +208,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, Includes: []string{executionCond}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -215,11 +218,11 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { { name: "service, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Service{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Service{ Service: "zitadel.session.v2beta.SessionService", }, }, @@ -227,7 +230,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, Includes: []string{executionCond}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -237,11 +240,11 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { { name: "all, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_All{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_All{ All: true, }, }, @@ -249,7 +252,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { }, Includes: []string{executionCond}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -272,24 +275,25 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { } func TestServer_DeleteExecution_Request(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - dep func(ctx context.Context, request *execution.DeleteExecutionRequest) error - req *execution.DeleteExecutionRequest - want *execution.DeleteExecutionResponse + dep func(ctx context.Context, request *action.DeleteExecutionRequest) error + req *action.DeleteExecutionRequest + want *action.DeleteExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_All{All: true}, + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_All{All: true}, }, }, }, @@ -299,10 +303,10 @@ func TestServer_DeleteExecution_Request(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{}, + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{}, }, }, }, @@ -311,11 +315,11 @@ func TestServer_DeleteExecution_Request(t *testing.T) { { name: "method, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/NotExisting", }, }, @@ -327,22 +331,22 @@ func TestServer_DeleteExecution_Request(t *testing.T) { { name: "method, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/GetSession", }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -352,11 +356,11 @@ func TestServer_DeleteExecution_Request(t *testing.T) { { name: "service, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Service{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Service{ Service: "NotExistingService", }, }, @@ -368,22 +372,22 @@ func TestServer_DeleteExecution_Request(t *testing.T) { { name: "service, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Service{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Service{ Service: "zitadel.user.v2beta.UserService", }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -393,22 +397,22 @@ func TestServer_DeleteExecution_Request(t *testing.T) { { name: "all, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_All{ All: true, }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -436,23 +440,24 @@ func TestServer_DeleteExecution_Request(t *testing.T) { } func TestServer_SetExecution_Response(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - req *execution.SetExecutionRequest - want *execution.SetExecutionResponse + req *action.SetExecutionRequest + want *action.SetExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_All{All: true}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_All{All: true}, }, }, }, @@ -462,10 +467,10 @@ func TestServer_SetExecution_Response(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{}, }, }, Targets: []string{targetResp.GetId()}, @@ -475,11 +480,11 @@ func TestServer_SetExecution_Response(t *testing.T) { { name: "method, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Method{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Method{ Method: "/zitadel.session.v2beta.NotExistingService/List", }, }, @@ -492,11 +497,11 @@ func TestServer_SetExecution_Response(t *testing.T) { { name: "method, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Method{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/ListSessions", }, }, @@ -504,7 +509,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -514,11 +519,11 @@ func TestServer_SetExecution_Response(t *testing.T) { { name: "service, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Service{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Service{ Service: "NotExistingService", }, }, @@ -531,11 +536,11 @@ func TestServer_SetExecution_Response(t *testing.T) { { name: "service, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Service{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Service{ Service: "zitadel.session.v2beta.SessionService", }, }, @@ -543,7 +548,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -553,11 +558,11 @@ func TestServer_SetExecution_Response(t *testing.T) { { name: "all, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_All{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_All{ All: true, }, }, @@ -565,7 +570,7 @@ func TestServer_SetExecution_Response(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -588,24 +593,25 @@ func TestServer_SetExecution_Response(t *testing.T) { } func TestServer_DeleteExecution_Response(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - dep func(ctx context.Context, request *execution.DeleteExecutionRequest) error - req *execution.DeleteExecutionRequest - want *execution.DeleteExecutionResponse + dep func(ctx context.Context, request *action.DeleteExecutionRequest) error + req *action.DeleteExecutionRequest + want *action.DeleteExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_All{ All: true, }, }, @@ -617,10 +623,10 @@ func TestServer_DeleteExecution_Response(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{}, + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{}, }, }, }, @@ -629,11 +635,11 @@ func TestServer_DeleteExecution_Response(t *testing.T) { { name: "method, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Method{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/NotExisting", }, }, @@ -645,22 +651,22 @@ func TestServer_DeleteExecution_Response(t *testing.T) { { name: "method, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Method{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/GetSession", }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -670,11 +676,11 @@ func TestServer_DeleteExecution_Response(t *testing.T) { { name: "service, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Service{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Service{ Service: "NotExistingService", }, }, @@ -686,22 +692,22 @@ func TestServer_DeleteExecution_Response(t *testing.T) { { name: "service, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_Service{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_Service{ Service: "zitadel.user.v2beta.UserService", }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -711,22 +717,22 @@ func TestServer_DeleteExecution_Response(t *testing.T) { { name: "all, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_All{ All: true, }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -754,23 +760,24 @@ func TestServer_DeleteExecution_Response(t *testing.T) { } func TestServer_SetExecution_Event(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - req *execution.SetExecutionRequest - want *execution.SetExecutionResponse + req *action.SetExecutionRequest + want *action.SetExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_All{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_All{ All: true, }, }, @@ -782,10 +789,10 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{}, }, }, Targets: []string{targetResp.GetId()}, @@ -798,11 +805,11 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "event, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Event{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Event{ Event: "xxx", }, }, @@ -816,11 +823,11 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "event, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Event{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Event{ Event: "xxx", }, }, @@ -828,7 +835,7 @@ func TestServer_SetExecution_Event(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -841,11 +848,11 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "group, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Group{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Group{ Group: "xxx", }, }, @@ -859,11 +866,11 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "group, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Group{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Group{ Group: "xxx", }, }, @@ -871,7 +878,7 @@ func TestServer_SetExecution_Event(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -881,11 +888,11 @@ func TestServer_SetExecution_Event(t *testing.T) { { name: "all, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_All{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_All{ All: true, }, }, @@ -893,7 +900,7 @@ func TestServer_SetExecution_Event(t *testing.T) { }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -916,24 +923,25 @@ func TestServer_SetExecution_Event(t *testing.T) { } func TestServer_DeleteExecution_Event(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - dep func(ctx context.Context, request *execution.DeleteExecutionRequest) error - req *execution.DeleteExecutionRequest - want *execution.DeleteExecutionResponse + dep func(ctx context.Context, request *action.DeleteExecutionRequest) error + req *action.DeleteExecutionRequest + want *action.DeleteExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_All{ All: true, }, }, @@ -945,10 +953,10 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{}, + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{}, }, }, }, @@ -959,11 +967,11 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "event, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Event{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Event{ Event: "xxx", }, }, @@ -976,22 +984,22 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "event, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Event{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Event{ Event: "xxx", }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -1001,11 +1009,11 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "group, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Group{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Group{ Group: "xxx", }, }, @@ -1017,22 +1025,22 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "group, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_Group{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_Group{ Group: "xxx", }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -1042,18 +1050,18 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "all, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_All{ All: true, }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -1063,22 +1071,22 @@ func TestServer_DeleteExecution_Event(t *testing.T) { { name: "all, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Event{ - Event: &execution.EventExecution{ - Condition: &execution.EventExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Event{ + Event: &action.EventExecution{ + Condition: &action.EventExecution_All{ All: true, }, }, }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -1106,23 +1114,24 @@ func TestServer_DeleteExecution_Event(t *testing.T) { } func TestServer_SetExecution_Function(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - req *execution.SetExecutionRequest - want *execution.SetExecutionResponse + req *action.SetExecutionRequest + want *action.SetExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_All{All: true}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_All{All: true}, }, }, }, @@ -1132,10 +1141,10 @@ func TestServer_SetExecution_Function(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{}, + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{}, }, }, Targets: []string{targetResp.GetId()}, @@ -1145,9 +1154,9 @@ func TestServer_SetExecution_Function(t *testing.T) { { name: "function, not existing", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Function{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Function{ Function: "xxx", }, }, @@ -1158,15 +1167,15 @@ func TestServer_SetExecution_Function(t *testing.T) { { name: "function, ok", ctx: CTX, - req: &execution.SetExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Function{ + req: &action.SetExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Function{ Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication", }, }, Targets: []string{targetResp.GetId()}, }, - want: &execution.SetExecutionResponse{ + want: &action.SetExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -1189,24 +1198,25 @@ func TestServer_SetExecution_Function(t *testing.T) { } func TestServer_DeleteExecution_Function(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - dep func(ctx context.Context, request *execution.DeleteExecutionRequest) error - req *execution.DeleteExecutionRequest - want *execution.DeleteExecutionResponse + dep func(ctx context.Context, request *action.DeleteExecutionRequest) error + req *action.DeleteExecutionRequest + want *action.DeleteExecutionResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{ - Condition: &execution.ResponseExecution_All{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{ + Condition: &action.ResponseExecution_All{ All: true, }, }, @@ -1218,10 +1228,10 @@ func TestServer_DeleteExecution_Function(t *testing.T) { { name: "no condition, error", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Response{ - Response: &execution.ResponseExecution{}, + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Response{ + Response: &action.ResponseExecution{}, }, }, }, @@ -1230,9 +1240,9 @@ func TestServer_DeleteExecution_Function(t *testing.T) { { name: "function, not existing", ctx: CTX, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Function{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Function{ Function: "xxx", }, }, @@ -1242,18 +1252,18 @@ func TestServer_DeleteExecution_Function(t *testing.T) { { name: "function, ok", ctx: CTX, - dep: func(ctx context.Context, request *execution.DeleteExecutionRequest) error { + dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error { Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{}) return nil }, - req: &execution.DeleteExecutionRequest{ - Condition: &execution.Condition{ - ConditionType: &execution.Condition_Function{ + req: &action.DeleteExecutionRequest{ + Condition: &action.Condition{ + ConditionType: &action.Condition_Function{ Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication", }, }, }, - want: &execution.DeleteExecutionResponse{ + want: &action.DeleteExecutionResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), diff --git a/internal/api/grpc/execution/v3alpha/query.go b/internal/api/grpc/action/v3alpha/query.go similarity index 60% rename from internal/api/grpc/execution/v3alpha/query.go rename to internal/api/grpc/action/v3alpha/query.go index 7c47a0a3c5..582510b0bf 100644 --- a/internal/api/grpc/execution/v3alpha/query.go +++ b/internal/api/grpc/action/v3alpha/query.go @@ -1,4 +1,4 @@ -package execution +package action import ( "context" @@ -10,10 +10,14 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" ) -func (s *Server) ListTargets(ctx context.Context, req *execution.ListTargetsRequest) (*execution.ListTargetsResponse, error) { +func (s *Server) ListTargets(ctx context.Context, req *action.ListTargetsRequest) (*action.ListTargetsResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + queries, err := listTargetsRequestToModel(req) if err != nil { return nil, err @@ -22,13 +26,13 @@ func (s *Server) ListTargets(ctx context.Context, req *execution.ListTargetsRequ if err != nil { return nil, err } - return &execution.ListTargetsResponse{ + return &action.ListTargetsResponse{ Result: targetsToPb(resp.Targets), Details: object.ToListDetails(resp.SearchResponse), }, nil } -func listTargetsRequestToModel(req *execution.ListTargetsRequest) (*query.TargetSearchQueries, error) { +func listTargetsRequestToModel(req *action.ListTargetsRequest) (*query.TargetSearchQueries, error) { offset, limit, asc := object.ListQueryToQuery(req.Query) queries, err := targetQueriesToQuery(req.Queries) if err != nil { @@ -45,34 +49,34 @@ func listTargetsRequestToModel(req *execution.ListTargetsRequest) (*query.Target }, nil } -func targetFieldNameToSortingColumn(field execution.TargetFieldName) query.Column { +func targetFieldNameToSortingColumn(field action.TargetFieldName) query.Column { switch field { - case execution.TargetFieldName_FIELD_NAME_UNSPECIFIED: + case action.TargetFieldName_FIELD_NAME_UNSPECIFIED: return query.TargetColumnID - case execution.TargetFieldName_FIELD_NAME_ID: + case action.TargetFieldName_FIELD_NAME_ID: return query.TargetColumnID - case execution.TargetFieldName_FIELD_NAME_CREATION_DATE: + case action.TargetFieldName_FIELD_NAME_CREATION_DATE: return query.TargetColumnCreationDate - case execution.TargetFieldName_FIELD_NAME_CHANGE_DATE: + case action.TargetFieldName_FIELD_NAME_CHANGE_DATE: return query.TargetColumnChangeDate - case execution.TargetFieldName_FIELD_NAME_NAME: + case action.TargetFieldName_FIELD_NAME_NAME: return query.TargetColumnName - case execution.TargetFieldName_FIELD_NAME_TARGET_TYPE: + case action.TargetFieldName_FIELD_NAME_TARGET_TYPE: return query.TargetColumnTargetType - case execution.TargetFieldName_FIELD_NAME_URL: + case action.TargetFieldName_FIELD_NAME_URL: return query.TargetColumnURL - case execution.TargetFieldName_FIELD_NAME_TIMEOUT: + case action.TargetFieldName_FIELD_NAME_TIMEOUT: return query.TargetColumnTimeout - case execution.TargetFieldName_FIELD_NAME_ASYNC: + case action.TargetFieldName_FIELD_NAME_ASYNC: return query.TargetColumnAsync - case execution.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR: + case action.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR: return query.TargetColumnInterruptOnError default: return query.TargetColumnID } } -func targetQueriesToQuery(queries []*execution.TargetSearchQuery) (_ []query.SearchQuery, err error) { +func targetQueriesToQuery(queries []*action.TargetSearchQuery) (_ []query.SearchQuery, err error) { q := make([]query.SearchQuery, len(queries)) for i, query := range queries { q[i], err = targetQueryToQuery(query) @@ -83,69 +87,77 @@ func targetQueriesToQuery(queries []*execution.TargetSearchQuery) (_ []query.Sea return q, nil } -func targetQueryToQuery(query *execution.TargetSearchQuery) (query.SearchQuery, error) { +func targetQueryToQuery(query *action.TargetSearchQuery) (query.SearchQuery, error) { switch q := query.Query.(type) { - case *execution.TargetSearchQuery_TargetNameQuery: + case *action.TargetSearchQuery_TargetNameQuery: return targetNameQueryToQuery(q.TargetNameQuery) - case *execution.TargetSearchQuery_InTargetIdsQuery: + case *action.TargetSearchQuery_InTargetIdsQuery: return targetInTargetIdsQueryToQuery(q.InTargetIdsQuery) default: return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") } } -func targetNameQueryToQuery(q *execution.TargetNameQuery) (query.SearchQuery, error) { +func targetNameQueryToQuery(q *action.TargetNameQuery) (query.SearchQuery, error) { return query.NewTargetNameSearchQuery(object.TextMethodToQuery(q.Method), q.GetTargetName()) } -func targetInTargetIdsQueryToQuery(q *execution.InTargetIDsQuery) (query.SearchQuery, error) { +func targetInTargetIdsQueryToQuery(q *action.InTargetIDsQuery) (query.SearchQuery, error) { return query.NewTargetInIDsSearchQuery(q.GetTargetIds()) } -func (s *Server) GetTargetByID(ctx context.Context, req *execution.GetTargetByIDRequest) (_ *execution.GetTargetByIDResponse, err error) { +func (s *Server) GetTargetByID(ctx context.Context, req *action.GetTargetByIDRequest) (_ *action.GetTargetByIDResponse, err error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + resp, err := s.query.GetTargetByID(ctx, req.GetTargetId()) if err != nil { return nil, err } - return &execution.GetTargetByIDResponse{ + return &action.GetTargetByIDResponse{ Target: targetToPb(resp), }, nil } -func targetsToPb(targets []*query.Target) []*execution.Target { - t := make([]*execution.Target, len(targets)) +func targetsToPb(targets []*query.Target) []*action.Target { + t := make([]*action.Target, len(targets)) for i, target := range targets { t[i] = targetToPb(target) } return t } -func targetToPb(t *query.Target) *execution.Target { - target := &execution.Target{ +func targetToPb(t *query.Target) *action.Target { + target := &action.Target{ Details: object.DomainToDetailsPb(&t.ObjectDetails), TargetId: t.ID, Name: t.Name, Timeout: durationpb.New(t.Timeout), } if t.Async { - target.ExecutionType = &execution.Target_IsAsync{IsAsync: t.Async} + target.ExecutionType = &action.Target_IsAsync{IsAsync: t.Async} } if t.InterruptOnError { - target.ExecutionType = &execution.Target_InterruptOnError{InterruptOnError: t.InterruptOnError} + target.ExecutionType = &action.Target_InterruptOnError{InterruptOnError: t.InterruptOnError} } switch t.TargetType { case domain.TargetTypeWebhook: - target.TargetType = &execution.Target_RestWebhook{RestWebhook: &execution.SetRESTWebhook{Url: t.URL}} + target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{Url: t.URL}} case domain.TargetTypeRequestResponse: - target.TargetType = &execution.Target_RestRequestResponse{RestRequestResponse: &execution.SetRESTRequestResponse{Url: t.URL}} + target.TargetType = &action.Target_RestRequestResponse{RestRequestResponse: &action.SetRESTRequestResponse{Url: t.URL}} default: target.TargetType = nil } return target } -func (s *Server) ListExecutions(ctx context.Context, req *execution.ListExecutionsRequest) (*execution.ListExecutionsResponse, error) { +func (s *Server) ListExecutions(ctx context.Context, req *action.ListExecutionsRequest) (*action.ListExecutionsResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + queries, err := listExecutionsRequestToModel(req) if err != nil { return nil, err @@ -154,13 +166,13 @@ func (s *Server) ListExecutions(ctx context.Context, req *execution.ListExecutio if err != nil { return nil, err } - return &execution.ListExecutionsResponse{ + return &action.ListExecutionsResponse{ Result: executionsToPb(resp.Executions), Details: object.ToListDetails(resp.SearchResponse), }, nil } -func listExecutionsRequestToModel(req *execution.ListExecutionsRequest) (*query.ExecutionSearchQueries, error) { +func listExecutionsRequestToModel(req *action.ListExecutionsRequest) (*query.ExecutionSearchQueries, error) { offset, limit, asc := object.ListQueryToQuery(req.Query) queries, err := executionQueriesToQuery(req.Queries) if err != nil { @@ -176,7 +188,7 @@ func listExecutionsRequestToModel(req *execution.ListExecutionsRequest) (*query. }, nil } -func executionQueriesToQuery(queries []*execution.SearchQuery) (_ []query.SearchQuery, err error) { +func executionQueriesToQuery(queries []*action.SearchQuery) (_ []query.SearchQuery, err error) { q := make([]query.SearchQuery, len(queries)) for i, query := range queries { q[i], err = executionQueryToQuery(query) @@ -187,39 +199,39 @@ func executionQueriesToQuery(queries []*execution.SearchQuery) (_ []query.Search return q, nil } -func executionQueryToQuery(searchQuery *execution.SearchQuery) (query.SearchQuery, error) { +func executionQueryToQuery(searchQuery *action.SearchQuery) (query.SearchQuery, error) { switch q := searchQuery.Query.(type) { - case *execution.SearchQuery_InConditionsQuery: + case *action.SearchQuery_InConditionsQuery: return inConditionsQueryToQuery(q.InConditionsQuery) - case *execution.SearchQuery_ExecutionTypeQuery: + case *action.SearchQuery_ExecutionTypeQuery: return executionTypeToQuery(q.ExecutionTypeQuery) - case *execution.SearchQuery_TargetQuery: + case *action.SearchQuery_TargetQuery: return query.NewExecutionTargetSearchQuery(q.TargetQuery.GetTargetId()) - case *execution.SearchQuery_IncludeQuery: + case *action.SearchQuery_IncludeQuery: return query.NewExecutionIncludeSearchQuery(q.IncludeQuery.GetInclude()) default: return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") } } -func executionTypeToQuery(q *execution.ExecutionTypeQuery) (query.SearchQuery, error) { +func executionTypeToQuery(q *action.ExecutionTypeQuery) (query.SearchQuery, error) { switch q.ExecutionType { - case execution.ExecutionType_EXECUTION_TYPE_UNSPECIFIED: + case action.ExecutionType_EXECUTION_TYPE_UNSPECIFIED: return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified) - case execution.ExecutionType_EXECUTION_TYPE_REQUEST: + case action.ExecutionType_EXECUTION_TYPE_REQUEST: return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeRequest) - case execution.ExecutionType_EXECUTION_TYPE_RESPONSE: + case action.ExecutionType_EXECUTION_TYPE_RESPONSE: return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeResponse) - case execution.ExecutionType_EXECUTION_TYPE_EVENT: + case action.ExecutionType_EXECUTION_TYPE_EVENT: return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeEvent) - case execution.ExecutionType_EXECUTION_TYPE_FUNCTION: + case action.ExecutionType_EXECUTION_TYPE_FUNCTION: return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeFunction) default: return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified) } } -func inConditionsQueryToQuery(q *execution.InConditionsQuery) (query.SearchQuery, error) { +func inConditionsQueryToQuery(q *action.InConditionsQuery) (query.SearchQuery, error) { values := make([]string, len(q.GetConditions())) for i, condition := range q.GetConditions() { id, err := conditionToID(condition) @@ -231,45 +243,45 @@ func inConditionsQueryToQuery(q *execution.InConditionsQuery) (query.SearchQuery return query.NewExecutionInIDsSearchQuery(values) } -func conditionToID(q *execution.Condition) (string, error) { +func conditionToID(q *action.Condition) (string, error) { switch t := q.GetConditionType().(type) { - case *execution.Condition_Request: + case *action.Condition_Request: cond := &command.ExecutionAPICondition{ Method: t.Request.GetMethod(), Service: t.Request.GetService(), All: t.Request.GetAll(), } return cond.ID(domain.ExecutionTypeRequest), nil - case *execution.Condition_Response: + case *action.Condition_Response: cond := &command.ExecutionAPICondition{ Method: t.Response.GetMethod(), Service: t.Response.GetService(), All: t.Response.GetAll(), } return cond.ID(domain.ExecutionTypeResponse), nil - case *execution.Condition_Event: + case *action.Condition_Event: cond := &command.ExecutionEventCondition{ Event: t.Event.GetEvent(), Group: t.Event.GetGroup(), All: t.Event.GetAll(), } return cond.ID(), nil - case *execution.Condition_Function: + case *action.Condition_Function: return t.Function, nil default: return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") } } -func executionsToPb(executions []*query.Execution) []*execution.Execution { - e := make([]*execution.Execution, len(executions)) +func executionsToPb(executions []*query.Execution) []*action.Execution { + e := make([]*action.Execution, len(executions)) for i, execution := range executions { e[i] = executionToPb(execution) } return e } -func executionToPb(e *query.Execution) *execution.Execution { +func executionToPb(e *query.Execution) *action.Execution { var targets, includes []string if len(e.Targets) > 0 { targets = e.Targets @@ -277,7 +289,7 @@ func executionToPb(e *query.Execution) *execution.Execution { if len(e.Includes) > 0 { includes = e.Includes } - return &execution.Execution{ + return &action.Execution{ Details: object.DomainToDetailsPb(&e.ObjectDetails), ExecutionId: e.ID, Targets: targets, diff --git a/internal/api/grpc/execution/v3alpha/query_integration_test.go b/internal/api/grpc/action/v3alpha/query_integration_test.go similarity index 69% rename from internal/api/grpc/execution/v3alpha/query_integration_test.go rename to internal/api/grpc/action/v3alpha/query_integration_test.go index d923a50161..31b5a8025a 100644 --- a/internal/api/grpc/execution/v3alpha/query_integration_test.go +++ b/internal/api/grpc/action/v3alpha/query_integration_test.go @@ -1,6 +1,6 @@ //go:build integration -package execution_test +package action_test import ( "context" @@ -13,27 +13,28 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "github.com/zitadel/zitadel/internal/integration" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" ) func TestServer_GetTargetByID(t *testing.T) { + ensureFeatureEnabled(t) type args struct { ctx context.Context - dep func(context.Context, *execution.GetTargetByIDRequest, *execution.GetTargetByIDResponse) error - req *execution.GetTargetByIDRequest + dep func(context.Context, *action.GetTargetByIDRequest, *action.GetTargetByIDResponse) error + req *action.GetTargetByIDRequest } tests := []struct { name string args args - want *execution.GetTargetByIDResponse + want *action.GetTargetByIDResponse wantErr bool }{ { name: "missing permission", args: args{ ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.GetTargetByIDRequest{}, + req: &action.GetTargetByIDRequest{}, }, wantErr: true, }, @@ -41,7 +42,7 @@ func TestServer_GetTargetByID(t *testing.T) { name: "not found", args: args{ ctx: CTX, - req: &execution.GetTargetByIDRequest{TargetId: "notexisting"}, + req: &action.GetTargetByIDRequest{TargetId: "notexisting"}, }, wantErr: true, }, @@ -49,7 +50,7 @@ func TestServer_GetTargetByID(t *testing.T) { name: "get, ok", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.GetTargetByIDRequest, response *execution.GetTargetByIDResponse) error { + dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error { name := fmt.Sprint(time.Now().UnixNano() + 1) resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false) request.TargetId = resp.GetId() @@ -61,15 +62,15 @@ func TestServer_GetTargetByID(t *testing.T) { response.Target.Details.Sequence = resp.GetDetails().GetSequence() return nil }, - req: &execution.GetTargetByIDRequest{}, + req: &action.GetTargetByIDRequest{}, }, - want: &execution.GetTargetByIDResponse{ - Target: &execution.Target{ + want: &action.GetTargetByIDResponse{ + Target: &action.Target{ Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, @@ -81,7 +82,7 @@ func TestServer_GetTargetByID(t *testing.T) { name: "get, async, ok", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.GetTargetByIDRequest, response *execution.GetTargetByIDResponse) error { + dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error { name := fmt.Sprint(time.Now().UnixNano() + 1) resp := Tester.CreateTargetWithNameAndType(ctx, t, name, true, false) request.TargetId = resp.GetId() @@ -93,20 +94,20 @@ func TestServer_GetTargetByID(t *testing.T) { response.Target.Details.Sequence = resp.GetDetails().GetSequence() return nil }, - req: &execution.GetTargetByIDRequest{}, + req: &action.GetTargetByIDRequest{}, }, - want: &execution.GetTargetByIDResponse{ - Target: &execution.Target{ + want: &action.GetTargetByIDResponse{ + Target: &action.Target{ Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.Target_IsAsync{IsAsync: true}, + ExecutionType: &action.Target_IsAsync{IsAsync: true}, }, }, }, @@ -114,7 +115,7 @@ func TestServer_GetTargetByID(t *testing.T) { name: "get, interruptOnError, ok", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.GetTargetByIDRequest, response *execution.GetTargetByIDResponse) error { + dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error { name := fmt.Sprint(time.Now().UnixNano() + 1) resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, true) request.TargetId = resp.GetId() @@ -126,20 +127,20 @@ func TestServer_GetTargetByID(t *testing.T) { response.Target.Details.Sequence = resp.GetDetails().GetSequence() return nil }, - req: &execution.GetTargetByIDRequest{}, + req: &action.GetTargetByIDRequest{}, }, - want: &execution.GetTargetByIDResponse{ - Target: &execution.Target{ + want: &action.GetTargetByIDResponse{ + Target: &action.Target{ Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.Target_InterruptOnError{InterruptOnError: true}, + ExecutionType: &action.Target_InterruptOnError{InterruptOnError: true}, }, }, }, @@ -178,22 +179,23 @@ func TestServer_GetTargetByID(t *testing.T) { } func TestServer_ListTargets(t *testing.T) { + ensureFeatureEnabled(t) type args struct { ctx context.Context - dep func(context.Context, *execution.ListTargetsRequest, *execution.ListTargetsResponse) error - req *execution.ListTargetsRequest + dep func(context.Context, *action.ListTargetsRequest, *action.ListTargetsResponse) error + req *action.ListTargetsRequest } tests := []struct { name string args args - want *execution.ListTargetsResponse + want *action.ListTargetsResponse wantErr bool }{ { name: "missing permission", args: args{ ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.ListTargetsRequest{}, + req: &action.ListTargetsRequest{}, }, wantErr: true, }, @@ -201,10 +203,10 @@ func TestServer_ListTargets(t *testing.T) { name: "list, not found", args: args{ ctx: CTX, - req: &execution.ListTargetsRequest{ - Queries: []*execution.TargetSearchQuery{ - {Query: &execution.TargetSearchQuery_InTargetIdsQuery{ - InTargetIdsQuery: &execution.InTargetIDsQuery{ + req: &action.ListTargetsRequest{ + Queries: []*action.TargetSearchQuery{ + {Query: &action.TargetSearchQuery_InTargetIdsQuery{ + InTargetIdsQuery: &action.InTargetIDsQuery{ TargetIds: []string{"notfound"}, }, }, @@ -212,22 +214,22 @@ func TestServer_ListTargets(t *testing.T) { }, }, }, - want: &execution.ListTargetsResponse{ + want: &action.ListTargetsResponse{ Details: &object.ListDetails{ TotalResult: 0, }, - Result: []*execution.Target{}, + Result: []*action.Target{}, }, }, { name: "list single id", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListTargetsRequest, response *execution.ListTargetsResponse) error { + dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error { name := fmt.Sprint(time.Now().UnixNano() + 1) resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false) - request.Queries[0].Query = &execution.TargetSearchQuery_InTargetIdsQuery{ - InTargetIdsQuery: &execution.InTargetIDsQuery{ + request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{ + InTargetIdsQuery: &action.InTargetIDsQuery{ TargetIds: []string{resp.GetId()}, }, } @@ -240,21 +242,21 @@ func TestServer_ListTargets(t *testing.T) { response.Result[0].Name = name return nil }, - req: &execution.ListTargetsRequest{ - Queries: []*execution.TargetSearchQuery{{}}, + req: &action.ListTargetsRequest{ + Queries: []*action.TargetSearchQuery{{}}, }, }, - want: &execution.ListTargetsResponse{ + want: &action.ListTargetsResponse{ Details: &object.ListDetails{ TotalResult: 1, }, - Result: []*execution.Target{ + Result: []*action.Target{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, @@ -266,11 +268,11 @@ func TestServer_ListTargets(t *testing.T) { name: "list single name", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListTargetsRequest, response *execution.ListTargetsResponse) error { + dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error { name := fmt.Sprint(time.Now().UnixNano() + 1) resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false) - request.Queries[0].Query = &execution.TargetSearchQuery_TargetNameQuery{ - TargetNameQuery: &execution.TargetNameQuery{ + request.Queries[0].Query = &action.TargetSearchQuery_TargetNameQuery{ + TargetNameQuery: &action.TargetNameQuery{ TargetName: name, }, } @@ -283,21 +285,21 @@ func TestServer_ListTargets(t *testing.T) { response.Result[0].Name = name return nil }, - req: &execution.ListTargetsRequest{ - Queries: []*execution.TargetSearchQuery{{}}, + req: &action.ListTargetsRequest{ + Queries: []*action.TargetSearchQuery{{}}, }, }, - want: &execution.ListTargetsResponse{ + want: &action.ListTargetsResponse{ Details: &object.ListDetails{ TotalResult: 1, }, - Result: []*execution.Target{ + Result: []*action.Target{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, @@ -310,15 +312,15 @@ func TestServer_ListTargets(t *testing.T) { name: "list multiple id", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListTargetsRequest, response *execution.ListTargetsResponse) error { + dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error { name1 := fmt.Sprint(time.Now().UnixNano() + 1) name2 := fmt.Sprint(time.Now().UnixNano() + 3) name3 := fmt.Sprint(time.Now().UnixNano() + 5) resp1 := Tester.CreateTargetWithNameAndType(ctx, t, name1, false, false) resp2 := Tester.CreateTargetWithNameAndType(ctx, t, name2, true, false) resp3 := Tester.CreateTargetWithNameAndType(ctx, t, name3, false, true) - request.Queries[0].Query = &execution.TargetSearchQuery_InTargetIdsQuery{ - InTargetIdsQuery: &execution.InTargetIDsQuery{ + request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{ + InTargetIdsQuery: &action.InTargetIDsQuery{ TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()}, }, } @@ -339,21 +341,21 @@ func TestServer_ListTargets(t *testing.T) { response.Result[2].Name = name3 return nil }, - req: &execution.ListTargetsRequest{ - Queries: []*execution.TargetSearchQuery{{}}, + req: &action.ListTargetsRequest{ + Queries: []*action.TargetSearchQuery{{}}, }, }, - want: &execution.ListTargetsResponse{ + want: &action.ListTargetsResponse{ Details: &object.ListDetails{ TotalResult: 3, }, - Result: []*execution.Target{ + Result: []*action.Target{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, @@ -363,25 +365,25 @@ func TestServer_ListTargets(t *testing.T) { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.Target_IsAsync{IsAsync: true}, + ExecutionType: &action.Target_IsAsync{IsAsync: true}, }, { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), }, - TargetType: &execution.Target_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.Target_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.Target_InterruptOnError{InterruptOnError: true}, + ExecutionType: &action.Target_InterruptOnError{InterruptOnError: true}, }, }, }, @@ -421,24 +423,25 @@ func TestServer_ListTargets(t *testing.T) { } func TestServer_ListExecutions_Request(t *testing.T) { + ensureFeatureEnabled(t) targetResp := Tester.CreateTarget(CTX, t) type args struct { ctx context.Context - dep func(context.Context, *execution.ListExecutionsRequest, *execution.ListExecutionsResponse) error - req *execution.ListExecutionsRequest + dep func(context.Context, *action.ListExecutionsRequest, *action.ListExecutionsResponse) error + req *action.ListExecutionsRequest } tests := []struct { name string args args - want *execution.ListExecutionsResponse + want *action.ListExecutionsResponse wantErr bool }{ { name: "missing permission", args: args{ ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.ListExecutionsRequest{}, + req: &action.ListExecutionsRequest{}, }, wantErr: true, }, @@ -446,7 +449,7 @@ func TestServer_ListExecutions_Request(t *testing.T) { name: "list single condition", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error { + dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error { resp := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{}) response.Details.Timestamp = resp.GetDetails().GetChangeDate() @@ -456,14 +459,14 @@ func TestServer_ListExecutions_Request(t *testing.T) { response.Result[0].Details.Sequence = resp.GetDetails().GetSequence() return nil }, - req: &execution.ListExecutionsRequest{ - Queries: []*execution.SearchQuery{{ - Query: &execution.SearchQuery_InConditionsQuery{ - InConditionsQuery: &execution.InConditionsQuery{ - Conditions: []*execution.Condition{{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + req: &action.ListExecutionsRequest{ + Queries: []*action.SearchQuery{{ + Query: &action.SearchQuery_InConditionsQuery{ + InConditionsQuery: &action.InConditionsQuery{ + Conditions: []*action.Condition{{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/GetSession", }, }, @@ -475,11 +478,11 @@ func TestServer_ListExecutions_Request(t *testing.T) { }}, }, }, - want: &execution.ListExecutionsResponse{ + want: &action.ListExecutionsResponse{ Details: &object.ListDetails{ TotalResult: 1, }, - Result: []*execution.Execution{ + Result: []*action.Execution{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), @@ -494,20 +497,20 @@ func TestServer_ListExecutions_Request(t *testing.T) { name: "list single target", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error { + dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error { target := Tester.CreateTarget(ctx, t) // add target as query to the request - request.Queries[0] = &execution.SearchQuery{ - Query: &execution.SearchQuery_TargetQuery{ - TargetQuery: &execution.TargetQuery{ + request.Queries[0] = &action.SearchQuery{ + Query: &action.SearchQuery_TargetQuery{ + TargetQuery: &action.TargetQuery{ TargetId: target.GetId(), }, }, } - resp := Tester.SetExecution(ctx, t, &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + resp := Tester.SetExecution(ctx, t, &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.management.v1.ManagementService/UpdateAction", }, }, @@ -522,15 +525,15 @@ func TestServer_ListExecutions_Request(t *testing.T) { response.Result[0].Targets[0] = target.GetId() return nil }, - req: &execution.ListExecutionsRequest{ - Queries: []*execution.SearchQuery{{}}, + req: &action.ListExecutionsRequest{ + Queries: []*action.SearchQuery{{}}, }, }, - want: &execution.ListExecutionsResponse{ + want: &action.ListExecutionsResponse{ Details: &object.ListDetails{ TotalResult: 1, }, - Result: []*execution.Execution{ + Result: []*action.Execution{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), @@ -544,20 +547,20 @@ func TestServer_ListExecutions_Request(t *testing.T) { name: "list single include", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error { - Tester.SetExecution(ctx, t, &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error { + Tester.SetExecution(ctx, t, &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.management.v1.ManagementService/GetAction", }, }, }, }, []string{targetResp.GetId()}, []string{}) - resp2 := Tester.SetExecution(ctx, t, &execution.Condition{ - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + resp2 := Tester.SetExecution(ctx, t, &action.Condition{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.management.v1.ManagementService/ListActions", }, }, @@ -571,19 +574,19 @@ func TestServer_ListExecutions_Request(t *testing.T) { response.Result[0].Details.Sequence = resp2.GetDetails().GetSequence() return nil }, - req: &execution.ListExecutionsRequest{ - Queries: []*execution.SearchQuery{{ - Query: &execution.SearchQuery_IncludeQuery{ - IncludeQuery: &execution.IncludeQuery{Include: "request./zitadel.management.v1.ManagementService/GetAction"}, + req: &action.ListExecutionsRequest{ + Queries: []*action.SearchQuery{{ + Query: &action.SearchQuery_IncludeQuery{ + IncludeQuery: &action.IncludeQuery{Include: "request./zitadel.management.v1.ManagementService/GetAction"}, }, }}, }, }, - want: &execution.ListExecutionsResponse{ + want: &action.ListExecutionsResponse{ Details: &object.ListDetails{ TotalResult: 1, }, - Result: []*execution.Execution{ + Result: []*action.Execution{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), @@ -598,7 +601,7 @@ func TestServer_ListExecutions_Request(t *testing.T) { name: "list multiple conditions", args: args{ ctx: CTX, - dep: func(ctx context.Context, request *execution.ListExecutionsRequest, response *execution.ListExecutionsResponse) error { + dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error { resp1 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{}) response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate() @@ -615,33 +618,33 @@ func TestServer_ListExecutions_Request(t *testing.T) { response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence() return nil }, - req: &execution.ListExecutionsRequest{ - Queries: []*execution.SearchQuery{{ - Query: &execution.SearchQuery_InConditionsQuery{ - InConditionsQuery: &execution.InConditionsQuery{ - Conditions: []*execution.Condition{ + req: &action.ListExecutionsRequest{ + Queries: []*action.SearchQuery{{ + Query: &action.SearchQuery_InConditionsQuery{ + InConditionsQuery: &action.InConditionsQuery{ + Conditions: []*action.Condition{ { - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/GetSession", }, }, }, }, { - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/CreateSession", }, }, }, }, { - ConditionType: &execution.Condition_Request{ - Request: &execution.RequestExecution{ - Condition: &execution.RequestExecution_Method{ + ConditionType: &action.Condition_Request{ + Request: &action.RequestExecution{ + Condition: &action.RequestExecution_Method{ Method: "/zitadel.session.v2beta.SessionService/SetSession", }, }, @@ -653,11 +656,11 @@ func TestServer_ListExecutions_Request(t *testing.T) { }}, }, }, - want: &execution.ListExecutionsResponse{ + want: &action.ListExecutionsResponse{ Details: &object.ListDetails{ TotalResult: 3, }, - Result: []*execution.Execution{ + Result: []*action.Execution{ { Details: &object.Details{ ResourceOwner: Tester.Instance.InstanceID(), diff --git a/internal/api/grpc/execution/v3alpha/server.go b/internal/api/grpc/action/v3alpha/server.go similarity index 62% rename from internal/api/grpc/execution/v3alpha/server.go rename to internal/api/grpc/action/v3alpha/server.go index b1a1b5cb4e..dfd813a5ad 100644 --- a/internal/api/grpc/execution/v3alpha/server.go +++ b/internal/api/grpc/action/v3alpha/server.go @@ -1,19 +1,22 @@ -package execution +package action import ( + "context" + "google.golang.org/grpc" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/server" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/query" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + "github.com/zitadel/zitadel/internal/zerrors" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" ) -var _ execution.ExecutionServiceServer = (*Server)(nil) +var _ action.ActionServiceServer = (*Server)(nil) type Server struct { - execution.UnimplementedExecutionServiceServer + action.UnimplementedActionServiceServer command *command.Commands query *query.Queries ListActionFunctions func() []string @@ -40,21 +43,28 @@ func CreateServer( } func (s *Server) RegisterServer(grpcServer *grpc.Server) { - execution.RegisterExecutionServiceServer(grpcServer, s) + action.RegisterActionServiceServer(grpcServer, s) } func (s *Server) AppName() string { - return execution.ExecutionService_ServiceDesc.ServiceName + return action.ActionService_ServiceDesc.ServiceName } func (s *Server) MethodPrefix() string { - return execution.ExecutionService_ServiceDesc.ServiceName + return action.ActionService_ServiceDesc.ServiceName } func (s *Server) AuthMethods() authz.MethodMapping { - return execution.ExecutionService_AuthMethods + return action.ActionService_AuthMethods } func (s *Server) RegisterGateway() server.RegisterGatewayFunc { - return execution.RegisterExecutionServiceHandler + return action.RegisterActionServiceHandler +} + +func checkExecutionEnabled(ctx context.Context) error { + if authz.GetInstance(ctx).Features().Actions { + return nil + } + return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-141bwx3lef", "Errors.action.NotEnabled") } diff --git a/internal/api/grpc/action/v3alpha/server_integration_test.go b/internal/api/grpc/action/v3alpha/server_integration_test.go new file mode 100644 index 0000000000..d27105fc48 --- /dev/null +++ b/internal/api/grpc/action/v3alpha/server_integration_test.go @@ -0,0 +1,69 @@ +//go:build integration + +package action_test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/muhlemmer/gu" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/integration" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" + feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta" +) + +var ( + CTX context.Context + Tester *integration.Tester + Client action.ActionServiceClient +) + +func TestMain(m *testing.M) { + os.Exit(func() int { + ctx, errCtx, cancel := integration.Contexts(5 * time.Minute) + defer cancel() + + Tester = integration.NewTester(ctx) + defer Tester.Done() + Client = Tester.Client.ActionV3 + + CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx + return m.Run() + }()) +} + +func ensureFeatureEnabled(t *testing.T) { + f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{ + Inheritance: true, + }) + require.NoError(t, err) + if f.Actions.GetEnabled() { + return + } + _, err = Tester.Client.FeatureV2.SetInstanceFeatures(CTX, &feature.SetInstanceFeaturesRequest{ + Actions: gu.Ptr(true), + }) + require.NoError(t, err) + retryDuration := time.Minute + if ctxDeadline, ok := CTX.Deadline(); ok { + retryDuration = time.Until(ctxDeadline) + } + require.EventuallyWithT(t, + func(ttt *assert.CollectT) { + f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{ + Inheritance: true, + }) + require.NoError(ttt, err) + if f.Actions.GetEnabled() { + return + } + }, + retryDuration, + 100*time.Millisecond, + "timed out waiting for ensuring instance feature") +} diff --git a/internal/api/grpc/execution/v3alpha/target.go b/internal/api/grpc/action/v3alpha/target.go similarity index 64% rename from internal/api/grpc/execution/v3alpha/target.go rename to internal/api/grpc/action/v3alpha/target.go index 23e23dd5f8..1a01ad2260 100644 --- a/internal/api/grpc/execution/v3alpha/target.go +++ b/internal/api/grpc/action/v3alpha/target.go @@ -1,4 +1,4 @@ -package execution +package action import ( "context" @@ -10,49 +10,61 @@ import ( "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" ) -func (s *Server) CreateTarget(ctx context.Context, req *execution.CreateTargetRequest) (*execution.CreateTargetResponse, error) { +func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + add := createTargetToCommand(req) details, err := s.command.AddTarget(ctx, add, authz.GetInstance(ctx).InstanceID()) if err != nil { return nil, err } - return &execution.CreateTargetResponse{ + return &action.CreateTargetResponse{ Id: add.AggregateID, Details: object.DomainToDetailsPb(details), }, nil } -func (s *Server) UpdateTarget(ctx context.Context, req *execution.UpdateTargetRequest) (*execution.UpdateTargetResponse, error) { +func (s *Server) UpdateTarget(ctx context.Context, req *action.UpdateTargetRequest) (*action.UpdateTargetResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + details, err := s.command.ChangeTarget(ctx, updateTargetToCommand(req), authz.GetInstance(ctx).InstanceID()) if err != nil { return nil, err } - return &execution.UpdateTargetResponse{ + return &action.UpdateTargetResponse{ Details: object.DomainToDetailsPb(details), }, nil } -func (s *Server) DeleteTarget(ctx context.Context, req *execution.DeleteTargetRequest) (*execution.DeleteTargetResponse, error) { +func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetRequest) (*action.DeleteTargetResponse, error) { + if err := checkExecutionEnabled(ctx); err != nil { + return nil, err + } + details, err := s.command.DeleteTarget(ctx, req.GetTargetId(), authz.GetInstance(ctx).InstanceID()) if err != nil { return nil, err } - return &execution.DeleteTargetResponse{ + return &action.DeleteTargetResponse{ Details: object.DomainToDetailsPb(details), }, nil } -func createTargetToCommand(req *execution.CreateTargetRequest) *command.AddTarget { +func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget { var targetType domain.TargetType var url string switch t := req.GetTargetType().(type) { - case *execution.CreateTargetRequest_RestWebhook: + case *action.CreateTargetRequest_RestWebhook: targetType = domain.TargetTypeWebhook url = t.RestWebhook.GetUrl() - case *execution.CreateTargetRequest_RestRequestResponse: + case *action.CreateTargetRequest_RestRequestResponse: targetType = domain.TargetTypeRequestResponse url = t.RestRequestResponse.GetUrl() } @@ -66,7 +78,7 @@ func createTargetToCommand(req *execution.CreateTargetRequest) *command.AddTarge } } -func updateTargetToCommand(req *execution.UpdateTargetRequest) *command.ChangeTarget { +func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarget { if req == nil { return nil } @@ -77,10 +89,10 @@ func updateTargetToCommand(req *execution.UpdateTargetRequest) *command.ChangeTa Name: req.Name, } switch t := req.GetTargetType().(type) { - case *execution.UpdateTargetRequest_RestWebhook: + case *action.UpdateTargetRequest_RestWebhook: target.TargetType = gu.Ptr(domain.TargetTypeWebhook) target.URL = gu.Ptr(t.RestWebhook.GetUrl()) - case *execution.UpdateTargetRequest_RestRequestResponse: + case *action.UpdateTargetRequest_RestRequestResponse: target.TargetType = gu.Ptr(domain.TargetTypeRequestResponse) target.URL = gu.Ptr(t.RestRequestResponse.GetUrl()) } diff --git a/internal/api/grpc/execution/v3alpha/target_integration_test.go b/internal/api/grpc/action/v3alpha/target_integration_test.go similarity index 68% rename from internal/api/grpc/execution/v3alpha/target_integration_test.go rename to internal/api/grpc/action/v3alpha/target_integration_test.go index e45476a1ea..2d45bed6d8 100644 --- a/internal/api/grpc/execution/v3alpha/target_integration_test.go +++ b/internal/api/grpc/action/v3alpha/target_integration_test.go @@ -1,6 +1,6 @@ //go:build integration -package execution_test +package action_test import ( "context" @@ -15,22 +15,23 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" ) func TestServer_CreateTarget(t *testing.T) { + ensureFeatureEnabled(t) tests := []struct { name string ctx context.Context - req *execution.CreateTargetRequest - want *execution.CreateTargetResponse + req *action.CreateTargetRequest + want *action.CreateTargetResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), }, wantErr: true, @@ -38,7 +39,7 @@ func TestServer_CreateTarget(t *testing.T) { { name: "empty name", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: "", }, wantErr: true, @@ -46,7 +47,7 @@ func TestServer_CreateTarget(t *testing.T) { { name: "empty type", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), TargetType: nil, }, @@ -55,10 +56,10 @@ func TestServer_CreateTarget(t *testing.T) { { name: "empty webhook url", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{}, + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{}, }, }, wantErr: true, @@ -66,10 +67,10 @@ func TestServer_CreateTarget(t *testing.T) { { name: "empty request response url", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestRequestResponse{ - RestRequestResponse: &execution.SetRESTRequestResponse{}, + TargetType: &action.CreateTargetRequest_RestRequestResponse{ + RestRequestResponse: &action.SetRESTRequestResponse{}, }, }, wantErr: true, @@ -77,10 +78,10 @@ func TestServer_CreateTarget(t *testing.T) { { name: "empty timeout", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, @@ -92,17 +93,17 @@ func TestServer_CreateTarget(t *testing.T) { { name: "empty execution type, ok", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), ExecutionType: nil, }, - want: &execution.CreateTargetResponse{ + want: &action.CreateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -112,19 +113,19 @@ func TestServer_CreateTarget(t *testing.T) { { name: "async execution, ok", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.CreateTargetRequest_IsAsync{ + ExecutionType: &action.CreateTargetRequest_IsAsync{ IsAsync: true, }, }, - want: &execution.CreateTargetResponse{ + want: &action.CreateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -134,19 +135,19 @@ func TestServer_CreateTarget(t *testing.T) { { name: "interrupt on error execution, ok", ctx: CTX, - req: &execution.CreateTargetRequest{ + req: &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.CreateTargetRequest_InterruptOnError{ + ExecutionType: &action.CreateTargetRequest_InterruptOnError{ InterruptOnError: true, }, }, - want: &execution.CreateTargetResponse{ + want: &action.CreateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -170,27 +171,28 @@ func TestServer_CreateTarget(t *testing.T) { } func TestServer_UpdateTarget(t *testing.T) { + ensureFeatureEnabled(t) type args struct { ctx context.Context - req *execution.UpdateTargetRequest + req *action.UpdateTargetRequest } tests := []struct { name string - prepare func(request *execution.UpdateTargetRequest) error + prepare func(request *action.UpdateTargetRequest) error args args - want *execution.UpdateTargetResponse + want *action.UpdateTargetResponse wantErr bool }{ { name: "missing permission", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { targetID := Tester.CreateTarget(CTX, t).GetId() request.TargetId = targetID return nil }, args: args{ ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.UpdateTargetRequest{ + req: &action.UpdateTargetRequest{ Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)), }, }, @@ -198,13 +200,13 @@ func TestServer_UpdateTarget(t *testing.T) { }, { name: "not existing", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { request.TargetId = "notexisting" return nil }, args: args{ ctx: CTX, - req: &execution.UpdateTargetRequest{ + req: &action.UpdateTargetRequest{ Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)), }, }, @@ -212,18 +214,18 @@ func TestServer_UpdateTarget(t *testing.T) { }, { name: "change name, ok", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { targetID := Tester.CreateTarget(CTX, t).GetId() request.TargetId = targetID return nil }, args: args{ ctx: CTX, - req: &execution.UpdateTargetRequest{ + req: &action.UpdateTargetRequest{ Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)), }, }, - want: &execution.UpdateTargetResponse{ + want: &action.UpdateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -232,22 +234,22 @@ func TestServer_UpdateTarget(t *testing.T) { }, { name: "change type, ok", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { targetID := Tester.CreateTarget(CTX, t).GetId() request.TargetId = targetID return nil }, args: args{ ctx: CTX, - req: &execution.UpdateTargetRequest{ - TargetType: &execution.UpdateTargetRequest_RestRequestResponse{ - RestRequestResponse: &execution.SetRESTRequestResponse{ + req: &action.UpdateTargetRequest{ + TargetType: &action.UpdateTargetRequest_RestRequestResponse{ + RestRequestResponse: &action.SetRESTRequestResponse{ Url: "https://example.com", }, }, }, }, - want: &execution.UpdateTargetResponse{ + want: &action.UpdateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -256,22 +258,22 @@ func TestServer_UpdateTarget(t *testing.T) { }, { name: "change url, ok", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { targetID := Tester.CreateTarget(CTX, t).GetId() request.TargetId = targetID return nil }, args: args{ ctx: CTX, - req: &execution.UpdateTargetRequest{ - TargetType: &execution.UpdateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + req: &action.UpdateTargetRequest{ + TargetType: &action.UpdateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com/hooks/new", }, }, }, }, - want: &execution.UpdateTargetResponse{ + want: &action.UpdateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -280,18 +282,18 @@ func TestServer_UpdateTarget(t *testing.T) { }, { name: "change timeout, ok", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { targetID := Tester.CreateTarget(CTX, t).GetId() request.TargetId = targetID return nil }, args: args{ ctx: CTX, - req: &execution.UpdateTargetRequest{ + req: &action.UpdateTargetRequest{ Timeout: durationpb.New(20 * time.Second), }, }, - want: &execution.UpdateTargetResponse{ + want: &action.UpdateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -300,20 +302,20 @@ func TestServer_UpdateTarget(t *testing.T) { }, { name: "change execution type, ok", - prepare: func(request *execution.UpdateTargetRequest) error { + prepare: func(request *action.UpdateTargetRequest) error { targetID := Tester.CreateTarget(CTX, t).GetId() request.TargetId = targetID return nil }, args: args{ ctx: CTX, - req: &execution.UpdateTargetRequest{ - ExecutionType: &execution.UpdateTargetRequest_IsAsync{ + req: &action.UpdateTargetRequest{ + ExecutionType: &action.UpdateTargetRequest_IsAsync{ IsAsync: true, }, }, }, - want: &execution.UpdateTargetResponse{ + want: &action.UpdateTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), @@ -338,18 +340,19 @@ func TestServer_UpdateTarget(t *testing.T) { } func TestServer_DeleteTarget(t *testing.T) { + ensureFeatureEnabled(t) target := Tester.CreateTarget(CTX, t) tests := []struct { name string ctx context.Context - req *execution.DeleteTargetRequest - want *execution.DeleteTargetResponse + req *action.DeleteTargetRequest + want *action.DeleteTargetResponse wantErr bool }{ { name: "missing permission", ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner), - req: &execution.DeleteTargetRequest{ + req: &action.DeleteTargetRequest{ TargetId: target.GetId(), }, wantErr: true, @@ -357,7 +360,7 @@ func TestServer_DeleteTarget(t *testing.T) { { name: "empty id", ctx: CTX, - req: &execution.DeleteTargetRequest{ + req: &action.DeleteTargetRequest{ TargetId: "", }, wantErr: true, @@ -365,10 +368,10 @@ func TestServer_DeleteTarget(t *testing.T) { { name: "delete target", ctx: CTX, - req: &execution.DeleteTargetRequest{ + req: &action.DeleteTargetRequest{ TargetId: target.GetId(), }, - want: &execution.DeleteTargetResponse{ + want: &action.DeleteTargetResponse{ Details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), diff --git a/internal/api/grpc/execution/v3alpha/target_test.go b/internal/api/grpc/action/v3alpha/target_test.go similarity index 77% rename from internal/api/grpc/execution/v3alpha/target_test.go rename to internal/api/grpc/action/v3alpha/target_test.go index 02939e5154..f630a6c715 100644 --- a/internal/api/grpc/execution/v3alpha/target_test.go +++ b/internal/api/grpc/action/v3alpha/target_test.go @@ -1,4 +1,4 @@ -package execution +package action import ( "testing" @@ -10,12 +10,12 @@ import ( "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" ) func Test_createTargetToCommand(t *testing.T) { type args struct { - req *execution.CreateTargetRequest + req *action.CreateTargetRequest } tests := []struct { name string @@ -35,15 +35,15 @@ func Test_createTargetToCommand(t *testing.T) { }, { name: "all fields (async webhook)", - args: args{&execution.CreateTargetRequest{ + args: args{&action.CreateTargetRequest{ Name: "target 1", - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com/hooks/1", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.CreateTargetRequest_IsAsync{ + ExecutionType: &action.CreateTargetRequest_IsAsync{ IsAsync: true, }, }}, @@ -58,15 +58,15 @@ func Test_createTargetToCommand(t *testing.T) { }, { name: "all fields (interrupting response)", - args: args{&execution.CreateTargetRequest{ + args: args{&action.CreateTargetRequest{ Name: "target 1", - TargetType: &execution.CreateTargetRequest_RestRequestResponse{ - RestRequestResponse: &execution.SetRESTRequestResponse{ + TargetType: &action.CreateTargetRequest_RestRequestResponse{ + RestRequestResponse: &action.SetRESTRequestResponse{ Url: "https://example.com/hooks/1", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.CreateTargetRequest_InterruptOnError{ + ExecutionType: &action.CreateTargetRequest_InterruptOnError{ InterruptOnError: true, }, }}, @@ -90,7 +90,7 @@ func Test_createTargetToCommand(t *testing.T) { func Test_updateTargetToCommand(t *testing.T) { type args struct { - req *execution.UpdateTargetRequest + req *action.UpdateTargetRequest } tests := []struct { name string @@ -104,7 +104,7 @@ func Test_updateTargetToCommand(t *testing.T) { }, { name: "all fields nil", - args: args{&execution.UpdateTargetRequest{ + args: args{&action.UpdateTargetRequest{ Name: nil, TargetType: nil, Timeout: nil, @@ -121,7 +121,7 @@ func Test_updateTargetToCommand(t *testing.T) { }, { name: "all fields empty", - args: args{&execution.UpdateTargetRequest{ + args: args{&action.UpdateTargetRequest{ Name: gu.Ptr(""), TargetType: nil, Timeout: durationpb.New(0), @@ -138,15 +138,15 @@ func Test_updateTargetToCommand(t *testing.T) { }, { name: "all fields (async webhook)", - args: args{&execution.UpdateTargetRequest{ + args: args{&action.UpdateTargetRequest{ Name: gu.Ptr("target 1"), - TargetType: &execution.UpdateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.UpdateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com/hooks/1", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.UpdateTargetRequest_IsAsync{ + ExecutionType: &action.UpdateTargetRequest_IsAsync{ IsAsync: true, }, }}, @@ -161,15 +161,15 @@ func Test_updateTargetToCommand(t *testing.T) { }, { name: "all fields (interrupting response)", - args: args{&execution.UpdateTargetRequest{ + args: args{&action.UpdateTargetRequest{ Name: gu.Ptr("target 1"), - TargetType: &execution.UpdateTargetRequest_RestRequestResponse{ - RestRequestResponse: &execution.SetRESTRequestResponse{ + TargetType: &action.UpdateTargetRequest_RestRequestResponse{ + RestRequestResponse: &action.SetRESTRequestResponse{ Url: "https://example.com/hooks/1", }, }, Timeout: durationpb.New(10 * time.Second), - ExecutionType: &execution.UpdateTargetRequest_InterruptOnError{ + ExecutionType: &action.UpdateTargetRequest_InterruptOnError{ InterruptOnError: true, }, }}, diff --git a/internal/api/grpc/admin/custom_text_converter.go b/internal/api/grpc/admin/custom_text_converter.go index a7471525d9..4bb76b3617 100644 --- a/internal/api/grpc/admin/custom_text_converter.go +++ b/internal/api/grpc/admin/custom_text_converter.go @@ -172,6 +172,7 @@ func SetLoginTextToDomain(req *admin_pb.SetCustomLoginTextsRequest) *domain.Cust result.RegistrationUser = text.RegistrationUserScreenTextPbToDomain(req.RegistrationUserText) result.ExternalRegistrationUserOverview = text.ExternalRegistrationUserOverviewScreenTextPbToDomain(req.ExternalRegistrationUserOverviewText) result.RegistrationOrg = text.RegistrationOrgScreenTextPbToDomain(req.RegistrationOrgText) + result.LinkingUserPrompt = text.LinkingUserPromptScreenTextPbToDomain(req.LinkingUserPromptText) result.LinkingUsersDone = text.LinkingUserDoneScreenTextPbToDomain(req.LinkingUserDoneText) result.ExternalNotFound = text.ExternalUserNotFoundScreenTextPbToDomain(req.ExternalUserNotFoundText) result.LoginSuccess = text.SuccessLoginScreenTextPbToDomain(req.SuccessLoginText) diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go index e47d1f04f7..13a11c14db 100644 --- a/internal/api/grpc/admin/export.go +++ b/internal/api/grpc/admin/export.go @@ -491,13 +491,14 @@ func (s *Server) getLockoutPolicy(ctx context.Context, orgID string) (_ *managem ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID, false) + queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID) if err != nil { return nil, err } if !queriedLockout.IsDefault { return &management_pb.AddCustomLockoutPolicyRequest{ MaxPasswordAttempts: uint32(queriedLockout.MaxPasswordAttempts), + MaxOtpAttempts: uint32(queriedLockout.MaxOTPAttempts), }, nil } return nil, nil @@ -1059,6 +1060,7 @@ func (s *Server) getCustomLoginTexts(ctx context.Context, org string, languages RegistrationUserText: text_grpc.RegistrationUserScreenTextToPb(text.RegistrationUser), ExternalRegistrationUserOverviewText: text_grpc.ExternalRegistrationUserOverviewScreenTextToPb(text.ExternalRegistrationUserOverview), RegistrationOrgText: text_grpc.RegistrationOrgScreenTextToPb(text.RegistrationOrg), + LinkingUserPromptText: text_grpc.LinkingUserPromptScreenTextToPb(text.LinkingUserPrompt), LinkingUserDoneText: text_grpc.LinkingUserDoneScreenTextToPb(text.LinkingUsersDone), ExternalUserNotFoundText: text_grpc.ExternalUserNotFoundScreenTextToPb(text.ExternalNotFound), SuccessLoginText: text_grpc.SuccessLoginScreenTextToPb(text.LoginSuccess), diff --git a/internal/api/grpc/admin/iam_member_integration_test.go b/internal/api/grpc/admin/iam_member_integration_test.go index 12f4f321c0..06a8acb914 100644 --- a/internal/api/grpc/admin/iam_member_integration_test.go +++ b/internal/api/grpc/admin/iam_member_integration_test.go @@ -7,10 +7,10 @@ import ( "testing" "github.com/brianvoe/gofakeit/v6" - "google.golang.org/protobuf/types/known/timestamppb" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/zitadel/zitadel/internal/integration" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" "github.com/zitadel/zitadel/pkg/grpc/member" diff --git a/internal/api/grpc/admin/iam_settings.go b/internal/api/grpc/admin/iam_settings.go index dde4100c32..04ca77305d 100644 --- a/internal/api/grpc/admin/iam_settings.go +++ b/internal/api/grpc/admin/iam_settings.go @@ -3,7 +3,6 @@ package admin import ( "context" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/object" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" ) @@ -46,68 +45,6 @@ func (s *Server) UpdateSecretGenerator(ctx context.Context, req *admin_pb.Update }, nil } -func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigRequest) (*admin_pb.GetSMTPConfigResponse, error) { - smtp, err := s.query.SMTPConfigByAggregateID(ctx, authz.GetInstance(ctx).InstanceID()) - if err != nil { - return nil, err - } - return &admin_pb.GetSMTPConfigResponse{ - SmtpConfig: SMTPConfigToPb(smtp), - }, nil -} - -func (s *Server) AddSMTPConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) (*admin_pb.AddSMTPConfigResponse, error) { - details, err := s.command.AddSMTPConfig(ctx, AddSMTPToConfig(req)) - if err != nil { - return nil, err - } - return &admin_pb.AddSMTPConfigResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), - }, nil -} - -func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) (*admin_pb.UpdateSMTPConfigResponse, error) { - details, err := s.command.ChangeSMTPConfig(ctx, UpdateSMTPToConfig(req)) - if err != nil { - return nil, err - } - return &admin_pb.UpdateSMTPConfigResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), - }, nil -} - -func (s *Server) RemoveSMTPConfig(ctx context.Context, _ *admin_pb.RemoveSMTPConfigRequest) (*admin_pb.RemoveSMTPConfigResponse, error) { - details, err := s.command.RemoveSMTPConfig(ctx) - if err != nil { - return nil, err - } - return &admin_pb.RemoveSMTPConfigResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), - }, nil -} - -func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.UpdateSMTPConfigPasswordRequest) (*admin_pb.UpdateSMTPConfigPasswordResponse, error) { - details, err := s.command.ChangeSMTPConfigPassword(ctx, req.Password) - if err != nil { - return nil, err - } - return &admin_pb.UpdateSMTPConfigPasswordResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), - }, nil -} - func (s *Server) GetSecurityPolicy(ctx context.Context, req *admin_pb.GetSecurityPolicyRequest) (*admin_pb.GetSecurityPolicyResponse, error) { policy, err := s.query.SecurityPolicy(ctx) if err != nil { diff --git a/internal/api/grpc/admin/iam_settings_converter.go b/internal/api/grpc/admin/iam_settings_converter.go index 8fd1a26f06..66e0dd3653 100644 --- a/internal/api/grpc/admin/iam_settings_converter.go +++ b/internal/api/grpc/admin/iam_settings_converter.go @@ -133,6 +133,7 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType) func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config { return &smtp.Config{ + Description: req.Description, Tls: req.Tls, From: req.SenderAddress, FromName: req.SenderName, @@ -147,26 +148,30 @@ func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config { func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config { return &smtp.Config{ + Description: req.Description, Tls: req.Tls, From: req.SenderAddress, FromName: req.SenderName, ReplyToAddress: req.ReplyToAddress, SMTP: smtp.SMTP{ - Host: req.Host, - User: req.User, + Host: req.Host, + User: req.User, + Password: req.Password, }, } } func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig { mapped := &settings_pb.SMTPConfig{ + Description: smtp.Description, Tls: smtp.TLS, SenderAddress: smtp.SenderAddress, SenderName: smtp.SenderName, ReplyToAddress: smtp.ReplyToAddress, Host: smtp.Host, User: smtp.User, - Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID), + Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.ResourceOwner), + Id: smtp.ID, } return mapped } diff --git a/internal/api/grpc/admin/import.go b/internal/api/grpc/admin/import.go index 6aeb761e9a..5eab0fb8d7 100644 --- a/internal/api/grpc/admin/import.go +++ b/internal/api/grpc/admin/import.go @@ -292,7 +292,7 @@ func getFileFromGCS(ctx context.Context, input *admin_pb.ImportDataRequest_GCSIn return ioutil.ReadAll(reader) } -func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, ctxData authz.CtxData, org *admin_pb.DataOrg, success *admin_pb.ImportDataSuccess, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator crypto.Generator) error { +func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, ctxData authz.CtxData, org *admin_pb.DataOrg, success *admin_pb.ImportDataSuccess, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode crypto.Generator) error { _, err := s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), []string{}) if err != nil { *errors = append(*errors, &admin_pb.ImportDataError{Type: "org", Id: org.GetOrgId(), Message: err.Error()}) @@ -325,7 +325,7 @@ func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataEr *errors = append(*errors, &admin_pb.ImportDataError{Type: "domain_policy", Id: org.GetOrgId(), Message: err.Error()}) } } - return importResources(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator) + return importResources(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode) } func importLabelPolicy(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, org *admin_pb.DataOrg) error { @@ -584,13 +584,13 @@ func importProjects(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa return nil } -func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, appSecretGenerator crypto.Generator) error { +func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) error { if org.OidcApps == nil { return nil } for _, app := range org.GetOidcApps() { logging.Debugf("import oidcapplication: %s", app.GetAppId()) - _, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId(), appSecretGenerator) + _, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId()) if err != nil { *errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()}) if isCtxTimeout(ctx) { @@ -605,13 +605,13 @@ func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa return nil } -func importAPIApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, appSecretGenerator crypto.Generator) error { +func importAPIApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) error { if org.ApiApps == nil { return nil } for _, app := range org.GetApiApps() { logging.Debugf("import apiapplication: %s", app.GetAppId()) - _, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId(), appSecretGenerator) + _, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId()) if err != nil { *errors = append(*errors, &admin_pb.ImportDataError{Type: "api_app", Id: app.GetAppId(), Message: err.Error()}) if isCtxTimeout(ctx) { @@ -700,7 +700,7 @@ func importProjectRoles(ctx context.Context, s *Server, errors *[]*admin_pb.Impo return nil } -func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator crypto.Generator) error { +func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode crypto.Generator) error { if err := importOrgDomains(ctx, s, errors, successOrg, org); err != nil { return err } @@ -742,10 +742,10 @@ func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportD if err := importProjects(ctx, s, errors, successOrg, org, count); err != nil { return err } - if err := importOIDCApps(ctx, s, errors, successOrg, org, count, appSecretGenerator); err != nil { + if err := importOIDCApps(ctx, s, errors, successOrg, org, count); err != nil { return err } - if err := importAPIApps(ctx, s, errors, successOrg, org, count, appSecretGenerator); err != nil { + if err := importAPIApps(ctx, s, errors, successOrg, org, count); err != nil { return err } if err := importAppKeys(ctx, s, errors, successOrg, org, count); err != nil { @@ -1023,10 +1023,6 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm success := &admin_pb.ImportDataSuccess{} count := &counts{} - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, nil, err - } initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) if err != nil { return nil, nil, err @@ -1064,7 +1060,7 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm count.appKeysCount += len(org.GetAppKeys()) } for _, org := range orgs { - if err = importOrg1(ctx, s, &errors, ctxData, org, success, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator); err != nil { + if err = importOrg1(ctx, s, &errors, ctxData, org, success, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode); err != nil { return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err } } diff --git a/internal/api/grpc/admin/language.go b/internal/api/grpc/admin/language.go index eecf32d4ed..fd6f19f571 100644 --- a/internal/api/grpc/admin/language.go +++ b/internal/api/grpc/admin/language.go @@ -2,6 +2,7 @@ package admin import ( "context" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/domain" diff --git a/internal/api/grpc/admin/lockout_converter.go b/internal/api/grpc/admin/lockout_converter.go index 555e0c2e89..eabd5104e1 100644 --- a/internal/api/grpc/admin/lockout_converter.go +++ b/internal/api/grpc/admin/lockout_converter.go @@ -8,5 +8,6 @@ import ( func UpdateLockoutPolicyToDomain(p *admin.UpdateLockoutPolicyRequest) *domain.LockoutPolicy { return &domain.LockoutPolicy{ MaxPasswordAttempts: uint64(p.MaxPasswordAttempts), + MaxOTPAttempts: uint64(p.MaxOtpAttempts), } } diff --git a/internal/api/grpc/admin/milestone_converter.go b/internal/api/grpc/admin/milestone_converter.go index 97ac1ae583..614cd878bc 100644 --- a/internal/api/grpc/admin/milestone_converter.go +++ b/internal/api/grpc/admin/milestone_converter.go @@ -1,13 +1,14 @@ package admin import ( + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/repository/milestone" "github.com/zitadel/zitadel/internal/zerrors" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" milestone_pb "github.com/zitadel/zitadel/pkg/grpc/milestone" - "google.golang.org/protobuf/types/known/timestamppb" ) func listMilestonesToModel(instanceID string, req *admin_pb.ListMilestonesRequest) (*query.MilestonesSearchQueries, error) { diff --git a/internal/api/grpc/admin/restrictions_integration_allow_public_org_registrations_test.go b/internal/api/grpc/admin/restrictions_integration_allow_public_org_registrations_test.go index 92707df9d6..30250e772a 100644 --- a/internal/api/grpc/admin/restrictions_integration_allow_public_org_registrations_test.go +++ b/internal/api/grpc/admin/restrictions_integration_allow_public_org_registrations_test.go @@ -5,8 +5,6 @@ package admin_test import ( "bytes" "context" - "github.com/muhlemmer/gu" - "github.com/stretchr/testify/assert" "io" "net/http" "net/http/cookiejar" @@ -14,6 +12,8 @@ import ( "testing" "time" + "github.com/muhlemmer/gu" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/pkg/grpc/admin" diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go index e367765983..96bf208347 100644 --- a/internal/api/grpc/admin/server.go +++ b/internal/api/grpc/admin/server.go @@ -30,7 +30,6 @@ type Server struct { query *query.Queries assetsAPIDomain func(context.Context) string userCodeAlg crypto.EncryptionAlgorithm - passwordHashAlg crypto.HashAlgorithm auditLogRetention time.Duration } @@ -53,7 +52,6 @@ func CreateServer( query: query, assetsAPIDomain: assets.AssetAPI(externalSecure), userCodeAlg: userCodeAlg, - passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), auditLogRetention: auditLogRetention, } } diff --git a/internal/api/grpc/admin/smtp.go b/internal/api/grpc/admin/smtp.go new file mode 100644 index 0000000000..c06e3654da --- /dev/null +++ b/internal/api/grpc/admin/smtp.go @@ -0,0 +1,130 @@ +package admin + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/object" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" +) + +func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigRequest) (*admin_pb.GetSMTPConfigResponse, error) { + smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) + if err != nil { + return nil, err + } + return &admin_pb.GetSMTPConfigResponse{ + SmtpConfig: SMTPConfigToPb(smtp), + }, nil +} + +func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPConfigByIdRequest) (*admin_pb.GetSMTPConfigByIdResponse, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + resourceOwner := instanceID // Will be replaced when orgs have smtp configs + + smtp, err := s.query.SMTPConfigByID(ctx, instanceID, resourceOwner, req.Id) + if err != nil { + return nil, err + } + return &admin_pb.GetSMTPConfigByIdResponse{ + SmtpConfig: SMTPConfigToPb(smtp), + }, nil +} + +func (s *Server) AddSMTPConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) (*admin_pb.AddSMTPConfigResponse, error) { + id, details, err := s.command.AddSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), AddSMTPToConfig(req)) + if err != nil { + return nil, err + } + return &admin_pb.AddSMTPConfigResponse{ + Details: object.ChangeToDetailsPb( + details.Sequence, + details.EventDate, + details.ResourceOwner), + Id: id, + }, nil +} + +func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) (*admin_pb.UpdateSMTPConfigResponse, error) { + details, err := s.command.ChangeSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, UpdateSMTPToConfig(req)) + if err != nil { + return nil, err + } + return &admin_pb.UpdateSMTPConfigResponse{ + Details: object.ChangeToDetailsPb( + details.Sequence, + details.EventDate, + details.ResourceOwner), + }, nil +} + +func (s *Server) RemoveSMTPConfig(ctx context.Context, req *admin_pb.RemoveSMTPConfigRequest) (*admin_pb.RemoveSMTPConfigResponse, error) { + details, err := s.command.RemoveSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) + if err != nil { + return nil, err + } + return &admin_pb.RemoveSMTPConfigResponse{ + Details: object.ChangeToDetailsPb( + details.Sequence, + details.EventDate, + details.ResourceOwner), + }, nil +} + +func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.UpdateSMTPConfigPasswordRequest) (*admin_pb.UpdateSMTPConfigPasswordResponse, error) { + details, err := s.command.ChangeSMTPConfigPassword(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.Password) + if err != nil { + return nil, err + } + return &admin_pb.UpdateSMTPConfigPasswordResponse{ + Details: object.ChangeToDetailsPb( + details.Sequence, + details.EventDate, + details.ResourceOwner), + }, nil +} + +func (s *Server) ListSMTPConfigs(ctx context.Context, req *admin_pb.ListSMTPConfigsRequest) (*admin_pb.ListSMTPConfigsResponse, error) { + queries, err := listSMTPConfigsToModel(req) + if err != nil { + return nil, err + } + result, err := s.query.SearchSMTPConfigs(ctx, queries) + if err != nil { + return nil, err + } + return &admin_pb.ListSMTPConfigsResponse{ + Details: object.ToListDetails(result.Count, result.Sequence, result.LastRun), + Result: SMTPConfigsToPb(result.Configs), + }, nil +} + +func (s *Server) ActivateSMTPConfig(ctx context.Context, req *admin_pb.ActivateSMTPConfigRequest) (*admin_pb.ActivateSMTPConfigResponse, error) { + // Get the ID of current SMTP active provider if any + currentActiveProviderID := "" + smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) + if err == nil { + currentActiveProviderID = smtp.ID + } + + result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, currentActiveProviderID) + if err != nil { + return nil, err + + } + + return &admin_pb.ActivateSMTPConfigResponse{ + Details: object.DomainToAddDetailsPb(result), + }, nil +} + +func (s *Server) DeactivateSMTPConfig(ctx context.Context, req *admin_pb.DeactivateSMTPConfigRequest) (*admin_pb.DeactivateSMTPConfigResponse, error) { + result, err := s.command.DeactivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) + if err != nil { + return nil, err + + } + return &admin_pb.DeactivateSMTPConfigResponse{ + Details: object.DomainToAddDetailsPb(result), + }, nil +} diff --git a/internal/api/grpc/admin/smtp_converters.go b/internal/api/grpc/admin/smtp_converters.go new file mode 100644 index 0000000000..2ebeed58ef --- /dev/null +++ b/internal/api/grpc/admin/smtp_converters.go @@ -0,0 +1,41 @@ +package admin + +import ( + "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/query" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings" +) + +func listSMTPConfigsToModel(req *admin_pb.ListSMTPConfigsRequest) (*query.SMTPConfigsSearchQueries, error) { + offset, limit, asc := object.ListQueryToModel(req.Query) + return &query.SMTPConfigsSearchQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + }, + }, nil +} + +func SMTPConfigToProviderPb(config *query.SMTPConfig) *settings_pb.SMTPConfig { + return &settings_pb.SMTPConfig{ + Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner), + Id: config.ID, + Description: config.Description, + Tls: config.TLS, + Host: config.Host, + User: config.User, + State: settings_pb.SMTPConfigState(config.State), + SenderAddress: config.SenderAddress, + SenderName: config.SenderName, + } +} + +func SMTPConfigsToPb(configs []*query.SMTPConfig) []*settings_pb.SMTPConfig { + c := make([]*settings_pb.SMTPConfig, len(configs)) + for i, config := range configs { + c[i] = SMTPConfigToProviderPb(config) + } + return c +} diff --git a/internal/api/grpc/auth/language.go b/internal/api/grpc/auth/language.go index 9f1d65bbb7..cf2d1421d7 100644 --- a/internal/api/grpc/auth/language.go +++ b/internal/api/grpc/auth/language.go @@ -2,9 +2,9 @@ package auth import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/i18n" - auth_pb "github.com/zitadel/zitadel/pkg/grpc/auth" ) diff --git a/internal/api/grpc/auth/password.go b/internal/api/grpc/auth/password.go index 0cbc8d4f61..4629fa31cf 100644 --- a/internal/api/grpc/auth/password.go +++ b/internal/api/grpc/auth/password.go @@ -3,9 +3,8 @@ package auth import ( "context" - "github.com/zitadel/zitadel/internal/api/grpc/object" - "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/object" auth_pb "github.com/zitadel/zitadel/pkg/grpc/auth" ) diff --git a/internal/api/grpc/change/changes.go b/internal/api/grpc/change/changes.go index 2205d89544..52c5afb3b8 100644 --- a/internal/api/grpc/change/changes.go +++ b/internal/api/grpc/change/changes.go @@ -4,7 +4,6 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/query" change_pb "github.com/zitadel/zitadel/pkg/grpc/change" "github.com/zitadel/zitadel/pkg/grpc/message" diff --git a/internal/api/grpc/client/middleware/tracing.go b/internal/api/grpc/client/middleware/tracing.go index 0ac5a2b2b6..32a144c2f1 100644 --- a/internal/api/grpc/client/middleware/tracing.go +++ b/internal/api/grpc/client/middleware/tracing.go @@ -4,9 +4,10 @@ import ( "context" "strings" - grpc_utils "github.com/zitadel/zitadel/internal/api/grpc" grpc_trace "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" + + grpc_utils "github.com/zitadel/zitadel/internal/api/grpc" ) type GRPCMethod string diff --git a/internal/api/grpc/execution/v3alpha/server_integration_test.go b/internal/api/grpc/execution/v3alpha/server_integration_test.go deleted file mode 100644 index 41cdf3f8d4..0000000000 --- a/internal/api/grpc/execution/v3alpha/server_integration_test.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build integration - -package execution_test - -import ( - "context" - "os" - "testing" - "time" - - "github.com/zitadel/zitadel/internal/integration" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" -) - -var ( - CTX context.Context - Tester *integration.Tester - Client execution.ExecutionServiceClient -) - -func TestMain(m *testing.M) { - os.Exit(func() int { - ctx, errCtx, cancel := integration.Contexts(5 * time.Minute) - defer cancel() - - Tester = integration.NewTester(ctx) - defer Tester.Done() - Client = Tester.Client.ExecutionV3 - - CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx - return m.Run() - }()) -} diff --git a/internal/api/grpc/feature/v2/converter.go b/internal/api/grpc/feature/v2/converter.go index 55ab38978a..aa8b1287f4 100644 --- a/internal/api/grpc/feature/v2/converter.go +++ b/internal/api/grpc/feature/v2/converter.go @@ -14,6 +14,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command. TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections, LegacyIntrospection: req.OidcLegacyIntrospection, UserSchema: req.UserSchema, + Actions: req.Actions, TokenExchange: req.OidcTokenExchange, } } @@ -26,6 +27,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe OidcLegacyIntrospection: featureSourceToFlagPb(&f.LegacyIntrospection), UserSchema: featureSourceToFlagPb(&f.UserSchema), OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange), + Actions: featureSourceToFlagPb(&f.Actions), } } @@ -36,6 +38,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm LegacyIntrospection: req.OidcLegacyIntrospection, UserSchema: req.UserSchema, TokenExchange: req.OidcTokenExchange, + Actions: req.Actions, } } @@ -47,6 +50,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat OidcLegacyIntrospection: featureSourceToFlagPb(&f.LegacyIntrospection), UserSchema: featureSourceToFlagPb(&f.UserSchema), OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange), + Actions: featureSourceToFlagPb(&f.Actions), } } diff --git a/internal/api/grpc/feature/v2/converter_test.go b/internal/api/grpc/feature/v2/converter_test.go index baac093b6d..78aea5eb15 100644 --- a/internal/api/grpc/feature/v2/converter_test.go +++ b/internal/api/grpc/feature/v2/converter_test.go @@ -22,6 +22,7 @@ func Test_systemFeaturesToCommand(t *testing.T) { OidcTriggerIntrospectionProjections: gu.Ptr(false), OidcLegacyIntrospection: nil, UserSchema: gu.Ptr(true), + Actions: gu.Ptr(true), OidcTokenExchange: gu.Ptr(true), } want := &command.SystemFeatures{ @@ -29,6 +30,7 @@ func Test_systemFeaturesToCommand(t *testing.T) { TriggerIntrospectionProjections: gu.Ptr(false), LegacyIntrospection: nil, UserSchema: gu.Ptr(true), + Actions: gu.Ptr(true), TokenExchange: gu.Ptr(true), } got := systemFeaturesToCommand(arg) @@ -58,6 +60,10 @@ func Test_systemFeaturesToPb(t *testing.T) { Level: feature.LevelSystem, Value: true, }, + Actions: query.FeatureSource[bool]{ + Level: feature.LevelSystem, + Value: true, + }, TokenExchange: query.FeatureSource[bool]{ Level: feature.LevelSystem, Value: false, @@ -89,6 +95,10 @@ func Test_systemFeaturesToPb(t *testing.T) { Enabled: false, Source: feature_pb.Source_SOURCE_SYSTEM, }, + Actions: &feature_pb.FeatureFlag{ + Enabled: true, + Source: feature_pb.Source_SOURCE_SYSTEM, + }, } got := systemFeaturesToPb(arg) assert.Equal(t, want, got) @@ -101,6 +111,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) { OidcLegacyIntrospection: nil, UserSchema: gu.Ptr(true), OidcTokenExchange: gu.Ptr(true), + Actions: gu.Ptr(true), } want := &command.InstanceFeatures{ LoginDefaultOrg: gu.Ptr(true), @@ -108,6 +119,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) { LegacyIntrospection: nil, UserSchema: gu.Ptr(true), TokenExchange: gu.Ptr(true), + Actions: gu.Ptr(true), } got := instanceFeaturesToCommand(arg) assert.Equal(t, want, got) @@ -136,6 +148,10 @@ func Test_instanceFeaturesToPb(t *testing.T) { Level: feature.LevelInstance, Value: true, }, + Actions: query.FeatureSource[bool]{ + Level: feature.LevelInstance, + Value: true, + }, TokenExchange: query.FeatureSource[bool]{ Level: feature.LevelSystem, Value: false, @@ -163,6 +179,10 @@ func Test_instanceFeaturesToPb(t *testing.T) { Enabled: true, Source: feature_pb.Source_SOURCE_INSTANCE, }, + Actions: &feature_pb.FeatureFlag{ + Enabled: true, + Source: feature_pb.Source_SOURCE_INSTANCE, + }, OidcTokenExchange: &feature_pb.FeatureFlag{ Enabled: false, Source: feature_pb.Source_SOURCE_SYSTEM, diff --git a/internal/api/grpc/feature/v2/feature_integration_test.go b/internal/api/grpc/feature/v2/feature_integration_test.go index 5413936b21..5dcd0c37f4 100644 --- a/internal/api/grpc/feature/v2/feature_integration_test.go +++ b/internal/api/grpc/feature/v2/feature_integration_test.go @@ -219,6 +219,7 @@ func TestServer_GetSystemFeatures(t *testing.T) { assertFeatureFlag(t, tt.want.OidcTriggerIntrospectionProjections, got.OidcTriggerIntrospectionProjections) assertFeatureFlag(t, tt.want.OidcLegacyIntrospection, got.OidcLegacyIntrospection) assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema) + assertFeatureFlag(t, tt.want.Actions, got.Actions) }) } } @@ -389,6 +390,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) { Enabled: false, Source: feature.Source_SOURCE_UNSPECIFIED, }, + Actions: &feature.FeatureFlag{ + Enabled: false, + Source: feature.Source_SOURCE_UNSPECIFIED, + }, }, }, { @@ -398,6 +403,7 @@ func TestServer_GetInstanceFeatures(t *testing.T) { LoginDefaultOrg: gu.Ptr(true), OidcTriggerIntrospectionProjections: gu.Ptr(false), UserSchema: gu.Ptr(true), + Actions: gu.Ptr(true), }) require.NoError(t, err) }, @@ -418,6 +424,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) { Enabled: true, Source: feature.Source_SOURCE_INSTANCE, }, + Actions: &feature.FeatureFlag{ + Enabled: true, + Source: feature.Source_SOURCE_INSTANCE, + }, }, }, { @@ -451,6 +461,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) { Enabled: false, Source: feature.Source_SOURCE_UNSPECIFIED, }, + Actions: &feature.FeatureFlag{ + Enabled: false, + Source: feature.Source_SOURCE_UNSPECIFIED, + }, }, }, } diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go index c92f2bd3b0..2acaf4d3d8 100644 --- a/internal/api/grpc/idp/converter.go +++ b/internal/api/grpc/idp/converter.go @@ -274,6 +274,20 @@ func OptionsToCommand(options *idp_pb.Options) idp.Options { IsLinkingAllowed: options.IsLinkingAllowed, IsAutoCreation: options.IsAutoCreation, IsAutoUpdate: options.IsAutoUpdate, + AutoLinkingOption: autoLinkingOptionToCommand(options.AutoLinking), + } +} + +func autoLinkingOptionToCommand(linking idp_pb.AutoLinkingOption) domain.AutoLinkingOption { + switch linking { + case idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME: + return domain.AutoLinkingOptionUsername + case idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL: + return domain.AutoLinkingOptionEmail + case idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED: + return domain.AutoLinkingOptionUnspecified + default: + return domain.AutoLinkingOptionUnspecified } } @@ -398,6 +412,7 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig { IsCreationAllowed: config.IsCreationAllowed, IsAutoCreation: config.IsAutoCreation, IsAutoUpdate: config.IsAutoUpdate, + AutoLinking: autoLinkingOptionToPb(config.AutoLinking), }, } if config.OAuthIDPTemplate != nil { @@ -451,6 +466,19 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig { return providerConfig } +func autoLinkingOptionToPb(linking domain.AutoLinkingOption) idp_pb.AutoLinkingOption { + switch linking { + case domain.AutoLinkingOptionUnspecified: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED + case domain.AutoLinkingOptionUsername: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME + case domain.AutoLinkingOptionEmail: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL + default: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED + } +} + func oauthConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.OAuthIDPTemplate) { providerConfig.Config = &idp_pb.ProviderConfig_Oauth{ Oauth: &idp_pb.OAuthConfig{ diff --git a/internal/api/grpc/management/custom_text_converter.go b/internal/api/grpc/management/custom_text_converter.go index 06dfed6a8d..aa5aa05a67 100644 --- a/internal/api/grpc/management/custom_text_converter.go +++ b/internal/api/grpc/management/custom_text_converter.go @@ -171,6 +171,7 @@ func SetLoginCustomTextToDomain(req *mgmt_pb.SetCustomLoginTextsRequest) *domain result.RegistrationUser = text.RegistrationUserScreenTextPbToDomain(req.RegistrationUserText) result.ExternalRegistrationUserOverview = text.ExternalRegistrationUserOverviewScreenTextPbToDomain(req.ExternalRegistrationUserOverviewText) result.RegistrationOrg = text.RegistrationOrgScreenTextPbToDomain(req.RegistrationOrgText) + result.LinkingUserPrompt = text.LinkingUserPromptScreenTextPbToDomain(req.LinkingUserPromptText) result.LinkingUsersDone = text.LinkingUserDoneScreenTextPbToDomain(req.LinkingUserDoneText) result.ExternalNotFound = text.ExternalUserNotFoundScreenTextPbToDomain(req.ExternalUserNotFoundText) result.LoginSuccess = text.SuccessLoginScreenTextPbToDomain(req.SuccessLoginText) diff --git a/internal/api/grpc/management/language.go b/internal/api/grpc/management/language.go index fbcfdc9fbe..e0526a0185 100644 --- a/internal/api/grpc/management/language.go +++ b/internal/api/grpc/management/language.go @@ -2,6 +2,7 @@ package management import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/i18n" mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" diff --git a/internal/api/grpc/management/policy_lockout.go b/internal/api/grpc/management/policy_lockout.go index a36ee862d8..740a34e3a2 100644 --- a/internal/api/grpc/management/policy_lockout.go +++ b/internal/api/grpc/management/policy_lockout.go @@ -10,7 +10,7 @@ import ( ) func (s *Server) GetLockoutPolicy(ctx context.Context, req *mgmt_pb.GetLockoutPolicyRequest) (*mgmt_pb.GetLockoutPolicyResponse, error) { - policy, err := s.query.LockoutPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID, false) + policy, err := s.query.LockoutPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/policy_lockout_converter.go b/internal/api/grpc/management/policy_lockout_converter.go index 57910c4aba..83f5648103 100644 --- a/internal/api/grpc/management/policy_lockout_converter.go +++ b/internal/api/grpc/management/policy_lockout_converter.go @@ -8,11 +8,13 @@ import ( func AddLockoutPolicyToDomain(p *mgmt.AddCustomLockoutPolicyRequest) *domain.LockoutPolicy { return &domain.LockoutPolicy{ MaxPasswordAttempts: uint64(p.MaxPasswordAttempts), + MaxOTPAttempts: uint64(p.MaxOtpAttempts), } } func UpdateLockoutPolicyToDomain(p *mgmt.UpdateCustomLockoutPolicyRequest) *domain.LockoutPolicy { return &domain.LockoutPolicy{ MaxPasswordAttempts: uint64(p.MaxPasswordAttempts), + MaxOTPAttempts: uint64(p.MaxOtpAttempts), } } diff --git a/internal/api/grpc/management/project_application.go b/internal/api/grpc/management/project_application.go index 09612ce072..730b6ff22f 100644 --- a/internal/api/grpc/management/project_application.go +++ b/internal/api/grpc/management/project_application.go @@ -8,7 +8,6 @@ import ( change_grpc "github.com/zitadel/zitadel/internal/api/grpc/change" object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" project_grpc "github.com/zitadel/zitadel/internal/api/grpc/project" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/repository/project" @@ -81,11 +80,7 @@ func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChanges } func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID, appSecretGenerator) + app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -110,11 +105,7 @@ func (s *Server) AddSAMLApp(ctx context.Context, req *mgmt_pb.AddSAMLAppRequest) } func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID, appSecretGenerator) + app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -209,11 +200,7 @@ func (s *Server) RemoveApp(ctx context.Context, req *mgmt_pb.RemoveAppRequest) ( } func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.RegenerateOIDCClientSecretRequest) (*mgmt_pb.RegenerateOIDCClientSecretResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID, appSecretGenerator) + config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -228,11 +215,7 @@ func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.Re } func (s *Server) RegenerateAPIClientSecret(ctx context.Context, req *mgmt_pb.RegenerateAPIClientSecretRequest) (*mgmt_pb.RegenerateAPIClientSecretResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID, appSecretGenerator) + config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index 638da236b7..cdefabeab9 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -23,13 +23,12 @@ var _ management.ManagementServiceServer = (*Server)(nil) type Server struct { management.UnimplementedManagementServiceServer - command *command.Commands - query *query.Queries - systemDefaults systemdefaults.SystemDefaults - assetAPIPrefix func(context.Context) string - passwordHashAlg crypto.HashAlgorithm - userCodeAlg crypto.EncryptionAlgorithm - externalSecure bool + command *command.Commands + query *query.Queries + systemDefaults systemdefaults.SystemDefaults + assetAPIPrefix func(context.Context) string + userCodeAlg crypto.EncryptionAlgorithm + externalSecure bool } func CreateServer( @@ -40,13 +39,12 @@ func CreateServer( externalSecure bool, ) *Server { return &Server{ - command: command, - query: query, - systemDefaults: sd, - assetAPIPrefix: assets.AssetAPI(externalSecure), - passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), - userCodeAlg: userCodeAlg, - externalSecure: externalSecure, + command: command, + query: query, + systemDefaults: sd, + assetAPIPrefix: assets.AssetAPI(externalSecure), + userCodeAlg: userCodeAlg, + externalSecure: externalSecure, } } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index ef7ed0d731..eae4f40d35 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -800,18 +800,13 @@ func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachin } func (s *Server) GenerateMachineSecret(ctx context.Context, req *mgmt_pb.GenerateMachineSecretRequest) (*mgmt_pb.GenerateMachineSecretResponse, error) { - // use SecretGeneratorTypeAppSecret as the secrets will be used in the client_credentials grant like a client secret - secretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } user, err := s.getUserByID(ctx, req.GetUserId()) if err != nil { return nil, err } set := new(command.GenerateMachineSecret) - details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, secretGenerator, set) + details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, set) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go index a2de7a601b..32496b431c 100644 --- a/internal/api/grpc/management/user_converter.go +++ b/internal/api/grpc/management/user_converter.go @@ -7,18 +7,17 @@ import ( "github.com/zitadel/logging" "golang.org/x/text/language" - "github.com/zitadel/zitadel/internal/command" - "github.com/zitadel/zitadel/pkg/grpc/user" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/authn" "github.com/zitadel/zitadel/internal/api/grpc/metadata" "github.com/zitadel/zitadel/internal/api/grpc/object" user_grpc "github.com/zitadel/zitadel/internal/api/grpc/user" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/query" mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" + "github.com/zitadel/zitadel/pkg/grpc/user" ) func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) { diff --git a/internal/api/grpc/policy/password_lockout_policy.go b/internal/api/grpc/policy/password_lockout_policy.go index af73f1bcef..c33076234d 100644 --- a/internal/api/grpc/policy/password_lockout_policy.go +++ b/internal/api/grpc/policy/password_lockout_policy.go @@ -10,6 +10,7 @@ func ModelLockoutPolicyToPb(policy *query.LockoutPolicy) *policy_pb.LockoutPolic return &policy_pb.LockoutPolicy{ IsDefault: policy.IsDefault, MaxPasswordAttempts: policy.MaxPasswordAttempts, + MaxOtpAttempts: policy.MaxOTPAttempts, Details: object.ToViewDetailsPb( policy.Sequence, policy.CreationDate, diff --git a/internal/api/grpc/server/middleware/tracing.go b/internal/api/grpc/server/middleware/tracing.go index cf369bc462..748257551c 100644 --- a/internal/api/grpc/server/middleware/tracing.go +++ b/internal/api/grpc/server/middleware/tracing.go @@ -4,9 +4,10 @@ import ( "context" "strings" - grpc_utils "github.com/zitadel/zitadel/internal/api/grpc" grpc_trace "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" + + grpc_utils "github.com/zitadel/zitadel/internal/api/grpc" ) type GRPCMethod string diff --git a/internal/api/grpc/server/middleware/validation_interceptor.go b/internal/api/grpc/server/middleware/validation_interceptor.go index e7f48bcb62..dee3672566 100644 --- a/internal/api/grpc/server/middleware/validation_interceptor.go +++ b/internal/api/grpc/server/middleware/validation_interceptor.go @@ -3,12 +3,12 @@ package middleware import ( "context" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" //import to make sure go.mod does not lose it //because dependency is only needed for generated code _ "github.com/envoyproxy/protoc-gen-validate/validate" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func ValidationHandler() grpc.UnaryServerInterceptor { @@ -17,8 +17,8 @@ func ValidationHandler() grpc.UnaryServerInterceptor { } } -//validator interface needed for github.com/envoyproxy/protoc-gen-validate -//(it does not expose an interface itself) +// validator interface needed for github.com/envoyproxy/protoc-gen-validate +// (it does not expose an interface itself) type validator interface { Validate() error } diff --git a/internal/api/grpc/session/v2/session.go b/internal/api/grpc/session/v2/session.go index 29c897948b..df07602e85 100644 --- a/internal/api/grpc/session/v2/session.go +++ b/internal/api/grpc/session/v2/session.go @@ -6,11 +6,10 @@ import ( "net/http" "time" + "github.com/muhlemmer/gu" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/muhlemmer/gu" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/object/v2" "github.com/zitadel/zitadel/internal/command" diff --git a/internal/api/grpc/session/v2/session_integration_test.go b/internal/api/grpc/session/v2/session_integration_test.go index 3b2ceebbfe..cc70c75ea7 100644 --- a/internal/api/grpc/session/v2/session_integration_test.go +++ b/internal/api/grpc/session/v2/session_integration_test.go @@ -390,6 +390,27 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) { verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantIntentFactor) } +func TestServer_CreateSession_successfulIntent_instant(t *testing.T) { + idpID := Tester.AddGenericOAuthProvider(t) + + intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, User.GetUserId(), "id") + createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{ + Checks: &session.Checks{ + User: &session.CheckUser{ + Search: &session.CheckUser_UserId{ + UserId: User.GetUserId(), + }, + }, + IdpIntent: &session.CheckIDPIntent{ + IdpIntentId: intentID, + IdpIntentToken: token, + }, + }, + }) + require.NoError(t, err) + verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantIntentFactor) +} + func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) { idpID := Tester.AddGenericOAuthProvider(t) diff --git a/internal/api/grpc/settings/v2/settings.go b/internal/api/grpc/settings/v2/settings.go index c16178c370..11f41f26b6 100644 --- a/internal/api/grpc/settings/v2/settings.go +++ b/internal/api/grpc/settings/v2/settings.go @@ -90,7 +90,7 @@ func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *settings.G } func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockoutSettingsRequest) (*settings.GetLockoutSettingsResponse, error) { - current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false) + current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx())) if err != nil { return nil, err } diff --git a/internal/api/grpc/settings/v2/settings_converter.go b/internal/api/grpc/settings/v2/settings_converter.go index 1bd9837127..f00bbf0f9d 100644 --- a/internal/api/grpc/settings/v2/settings_converter.go +++ b/internal/api/grpc/settings/v2/settings_converter.go @@ -160,6 +160,7 @@ func legalAndSupportSettingsToPb(current *query.PrivacyPolicy) *settings.LegalAn func lockoutSettingsToPb(current *query.LockoutPolicy) *settings.LockoutSettings { return &settings.LockoutSettings{ MaxPasswordAttempts: current.MaxPasswordAttempts, + MaxOtpAttempts: current.MaxOTPAttempts, ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault), } } diff --git a/internal/api/grpc/settings/v2/settings_converter_test.go b/internal/api/grpc/settings/v2/settings_converter_test.go index 7fcd5d96ce..d1cde07b87 100644 --- a/internal/api/grpc/settings/v2/settings_converter_test.go +++ b/internal/api/grpc/settings/v2/settings_converter_test.go @@ -339,10 +339,12 @@ func Test_legalSettingsToPb(t *testing.T) { func Test_lockoutSettingsToPb(t *testing.T) { arg := &query.LockoutPolicy{ MaxPasswordAttempts: 22, + MaxOTPAttempts: 22, IsDefault: true, } want := &settings.LockoutSettings{ MaxPasswordAttempts: 22, + MaxOtpAttempts: 22, ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE, } got := lockoutSettingsToPb(arg) diff --git a/internal/api/grpc/system/instance_integration_test.go b/internal/api/grpc/system/instance_integration_test.go index a04118f59f..b4a6cea227 100644 --- a/internal/api/grpc/system/instance_integration_test.go +++ b/internal/api/grpc/system/instance_integration_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/zitadel/pkg/grpc/instance" "github.com/zitadel/zitadel/pkg/grpc/object" system_pb "github.com/zitadel/zitadel/pkg/grpc/system" diff --git a/internal/api/grpc/system/limits_integration_auditlogretention_test.go b/internal/api/grpc/system/limits_integration_auditlogretention_test.go index 96ceafcdc3..55ac76e998 100644 --- a/internal/api/grpc/system/limits_integration_auditlogretention_test.go +++ b/internal/api/grpc/system/limits_integration_auditlogretention_test.go @@ -4,7 +4,6 @@ package system_test import ( "context" - "google.golang.org/protobuf/types/known/timestamppb" "math/rand" "sync" "testing" @@ -13,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/pkg/grpc/admin" "github.com/zitadel/zitadel/pkg/grpc/auth" diff --git a/internal/api/grpc/system/quota_converter.go b/internal/api/grpc/system/quota_converter.go index 1945ff0951..73d231b36e 100644 --- a/internal/api/grpc/system/quota_converter.go +++ b/internal/api/grpc/system/quota_converter.go @@ -1,10 +1,11 @@ package system import ( - "github.com/zitadel/zitadel/internal/command" - "github.com/zitadel/zitadel/pkg/grpc/quota" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/pkg/grpc/quota" ) type setQuotaRequest interface { diff --git a/internal/api/grpc/text/custom_text.go b/internal/api/grpc/text/custom_text.go index 0f3da00b53..52c9c4af02 100644 --- a/internal/api/grpc/text/custom_text.go +++ b/internal/api/grpc/text/custom_text.go @@ -64,6 +64,7 @@ func CustomLoginTextToPb(text *domain.CustomLoginText) *text_pb.LoginCustomText RegistrationUserText: RegistrationUserScreenTextToPb(text.RegistrationUser), ExternalRegistrationUserOverviewText: ExternalRegistrationUserOverviewScreenTextToPb(text.ExternalRegistrationUserOverview), RegistrationOrgText: RegistrationOrgScreenTextToPb(text.RegistrationOrg), + LinkingUserPromptText: LinkingUserPromptScreenTextToPb(text.LinkingUserPrompt), LinkingUserDoneText: LinkingUserDoneScreenTextToPb(text.LinkingUsersDone), ExternalUserNotFoundText: ExternalUserNotFoundScreenTextToPb(text.ExternalNotFound), SuccessLoginText: SuccessLoginScreenTextToPb(text.LoginSuccess), @@ -422,6 +423,15 @@ func LinkingUserDoneScreenTextToPb(text domain.LinkingUserDoneScreenText) *text_ } } +func LinkingUserPromptScreenTextToPb(text domain.LinkingUserPromptScreenText) *text_pb.LinkingUserPromptScreenText { + return &text_pb.LinkingUserPromptScreenText{ + Title: text.Title, + Description: text.Description, + LinkButtonText: text.LinkButtonText, + OtherButtonText: text.OtherButtonText, + } +} + func ExternalUserNotFoundScreenTextToPb(text domain.ExternalUserNotFoundScreenText) *text_pb.ExternalUserNotFoundScreenText { return &text_pb.ExternalUserNotFoundScreenText{ Title: text.Title, @@ -890,6 +900,15 @@ func RegistrationOrgScreenTextPbToDomain(text *text_pb.RegistrationOrgScreenText } } +func LinkingUserPromptScreenTextPbToDomain(text *text_pb.LinkingUserPromptScreenText) domain.LinkingUserPromptScreenText { + return domain.LinkingUserPromptScreenText{ + Title: text.GetTitle(), + Description: text.GetDescription(), + LinkButtonText: text.GetLinkButtonText(), + OtherButtonText: text.GetOtherButtonText(), + } +} + func LinkingUserDoneScreenTextPbToDomain(text *text_pb.LinkingUserDoneScreenText) domain.LinkingUserDoneScreenText { if text == nil { return domain.LinkingUserDoneScreenText{} diff --git a/internal/api/grpc/user/converter.go b/internal/api/grpc/user/converter.go index f5381f9001..50c47faaa9 100644 --- a/internal/api/grpc/user/converter.go +++ b/internal/api/grpc/user/converter.go @@ -73,7 +73,7 @@ func MachineToPb(view *query.Machine) *user_pb.Machine { return &user_pb.Machine{ Name: view.Name, Description: view.Description, - HasSecret: view.Secret != nil, + HasSecret: view.EncodedSecret != "", AccessTokenType: AccessTokenTypeToPb(view.AccessTokenType), } } diff --git a/internal/api/grpc/user/schema/v3alpha/schema_integration_test.go b/internal/api/grpc/user/schema/v3alpha/schema_integration_test.go index bbd3200655..f84c338ab4 100644 --- a/internal/api/grpc/user/schema/v3alpha/schema_integration_test.go +++ b/internal/api/grpc/user/schema/v3alpha/schema_integration_test.go @@ -75,6 +75,8 @@ func ensureFeatureEnabled(t *testing.T) { } func TestServer_CreateUserSchema(t *testing.T) { + ensureFeatureEnabled(t) + tests := []struct { name string ctx context.Context @@ -315,7 +317,6 @@ func TestServer_CreateUserSchema(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ensureFeatureEnabled(t) got, err := Client.CreateUserSchema(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) @@ -330,6 +331,8 @@ func TestServer_CreateUserSchema(t *testing.T) { } func TestServer_UpdateUserSchema(t *testing.T) { + ensureFeatureEnabled(t) + type args struct { ctx context.Context req *schema.UpdateUserSchemaRequest @@ -572,7 +575,6 @@ func TestServer_UpdateUserSchema(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ensureFeatureEnabled(t) err := tt.prepare(tt.args.req) require.NoError(t, err) @@ -588,6 +590,8 @@ func TestServer_UpdateUserSchema(t *testing.T) { } func TestServer_DeactivateUserSchema(t *testing.T) { + ensureFeatureEnabled(t) + type args struct { ctx context.Context req *schema.DeactivateUserSchemaRequest @@ -647,7 +651,6 @@ func TestServer_DeactivateUserSchema(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ensureFeatureEnabled(t) err := tt.args.prepare(tt.args.req) require.NoError(t, err) @@ -663,6 +666,8 @@ func TestServer_DeactivateUserSchema(t *testing.T) { } func TestServer_ReactivateUserSchema(t *testing.T) { + ensureFeatureEnabled(t) + type args struct { ctx context.Context req *schema.ReactivateUserSchemaRequest @@ -722,7 +727,6 @@ func TestServer_ReactivateUserSchema(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ensureFeatureEnabled(t) err := tt.args.prepare(tt.args.req) require.NoError(t, err) @@ -738,6 +742,8 @@ func TestServer_ReactivateUserSchema(t *testing.T) { } func TestServer_DeleteUserSchema(t *testing.T) { + ensureFeatureEnabled(t) + type args struct { ctx context.Context req *schema.DeleteUserSchemaRequest @@ -797,7 +803,6 @@ func TestServer_DeleteUserSchema(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ensureFeatureEnabled(t) err := tt.args.prepare(tt.args.req) require.NoError(t, err) diff --git a/internal/api/grpc/user/v2/query.go b/internal/api/grpc/user/v2/query.go index 2d0798c2ce..aac070cede 100644 --- a/internal/api/grpc/user/v2/query.go +++ b/internal/api/grpc/user/v2/query.go @@ -109,7 +109,7 @@ func machineToPb(userQ *query.Machine) *user.MachineUser { return &user.MachineUser{ Name: userQ.Name, Description: userQ.Description, - HasSecret: userQ.Secret != nil, + HasSecret: userQ.EncodedSecret != "", AccessTokenType: accessTokenTypeToPb(userQ.AccessTokenType), } } diff --git a/internal/api/http/middleware/metrics_interceptor.go b/internal/api/http/middleware/metrics_interceptor.go index 78d953ba3a..32a84f4b15 100644 --- a/internal/api/http/middleware/metrics_interceptor.go +++ b/internal/api/http/middleware/metrics_interceptor.go @@ -3,9 +3,8 @@ package middleware import ( "net/http" - "github.com/zitadel/zitadel/internal/telemetry/metrics" - http_utils "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/telemetry/metrics" ) func DefaultMetricsHandler(handler http.Handler) http.Handler { diff --git a/internal/api/http/middleware/telemetry_interceptor.go b/internal/api/http/middleware/telemetry_interceptor.go index ab9fccca77..3fa66e71fd 100644 --- a/internal/api/http/middleware/telemetry_interceptor.go +++ b/internal/api/http/middleware/telemetry_interceptor.go @@ -3,9 +3,8 @@ package middleware import ( "net/http" - "github.com/zitadel/zitadel/internal/telemetry" - http_utils "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/telemetry" ) func DefaultTelemetryHandler(handler http.Handler) http.Handler { diff --git a/internal/api/http/origin.go b/internal/api/http/origin.go index f8ff8a7c71..e403acd7bf 100644 --- a/internal/api/http/origin.go +++ b/internal/api/http/origin.go @@ -22,7 +22,7 @@ func IsOriginAllowed(allowList []string, origin string) bool { return false } -//IsOrigin checks if provided string is an origin (scheme://hostname[:port]) without path, query or fragment +// IsOrigin checks if provided string is an origin (scheme://hostname[:port]) without path, query or fragment func IsOrigin(rawOrigin string) bool { parsedUrl, err := url.Parse(rawOrigin) if err != nil { diff --git a/internal/api/oidc/access_token.go b/internal/api/oidc/access_token.go index b4aca5f929..fce600b62b 100644 --- a/internal/api/oidc/access_token.go +++ b/internal/api/oidc/access_token.go @@ -13,6 +13,7 @@ import ( "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/user/model" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -35,7 +36,10 @@ type accessToken struct { var ErrInvalidTokenFormat = errors.New("invalid token format") -func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (*accessToken, error) { +func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (_ *accessToken, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + var tokenID, subject string if tokenIDSubject, err := s.Provider().Crypto().Decrypt(tkn); err == nil { diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index d54fc0ac92..384dc402a5 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -41,12 +41,12 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest return o.createAuthRequest(ctx, req, userID) } -func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, req *oidc.AuthRequest) (scope, audience []string, err error) { - project, err := o.query.ProjectByClientID(ctx, req.ClientID) +func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, clientID string, reqScope []string) (scope, audience []string, err error) { + project, err := o.query.ProjectByClientID(ctx, clientID) if err != nil { return nil, nil, err } - scope, err = o.assertProjectRoleScopesByProject(ctx, project, req.Scopes) + scope, err = o.assertProjectRoleScopesByProject(ctx, project, reqScope) if err != nil { return nil, nil, err } @@ -59,7 +59,7 @@ func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, req * } func (o *OPStorage) createAuthRequestLoginClient(ctx context.Context, req *oidc.AuthRequest, hintUserID, loginClient string) (op.AuthRequest, error) { - scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, req) + scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, req.ClientID, req.Scopes) if err != nil { return nil, err } @@ -96,7 +96,7 @@ func (o *OPStorage) createAuthRequest(ctx context.Context, req *oidc.AuthRequest if !ok { return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id") } - scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, req) + scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, req.ClientID, req.Scopes) if err != nil { return nil, err } diff --git a/internal/api/oidc/auth_request_integration_test.go b/internal/api/oidc/auth_request_integration_test.go index 75306fbfb7..c36e06c6aa 100644 --- a/internal/api/oidc/auth_request_integration_test.go +++ b/internal/api/oidc/auth_request_integration_test.go @@ -28,14 +28,14 @@ var ( ) func TestOPStorage_CreateAuthRequest(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) id := createAuthRequest(t, clientID, redirectURI) require.Contains(t, id, command.IDPrefixV2) } func TestOPStorage_CreateAccessToken_code(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -124,7 +124,7 @@ func TestOPStorage_CreateAccessToken_implicit(t *testing.T) { } func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -147,7 +147,7 @@ func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) { } func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) @@ -183,7 +183,7 @@ func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) { } func TestOPStorage_RevokeToken_access_token(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) @@ -226,7 +226,7 @@ func TestOPStorage_RevokeToken_access_token(t *testing.T) { } func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) @@ -263,7 +263,7 @@ func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T } func TestOPStorage_RevokeToken_refresh_token(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) @@ -306,7 +306,7 @@ func TestOPStorage_RevokeToken_refresh_token(t *testing.T) { } func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) @@ -343,7 +343,7 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing. } func TestOPStorage_RevokeToken_invalid_client(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -365,7 +365,7 @@ func TestOPStorage_RevokeToken_invalid_client(t *testing.T) { assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime) // simulate second client (not part of the audience) trying to revoke the token - otherClientID := createClient(t) + otherClientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, otherClientID, redirectURI) require.NoError(t, err) err = rp.RevokeToken(CTX, provider, tokens.AccessToken, "") @@ -373,7 +373,7 @@ func TestOPStorage_RevokeToken_invalid_client(t *testing.T) { } func TestOPStorage_TerminateSession(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI) @@ -410,7 +410,7 @@ func TestOPStorage_TerminateSession(t *testing.T) { } func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) @@ -454,7 +454,7 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) { } func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI) diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index 2857991b8a..bdbf6bd67b 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -9,7 +9,7 @@ import ( "time" "github.com/dop251/goja" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" @@ -1050,8 +1050,13 @@ func (s *Server) verifyClientSecret(ctx context.Context, client *query.OIDCClien if secret == "" { return oidc.ErrInvalidClient().WithDescription("empty client secret") } - if err = crypto.CompareHash(client.ClientSecret, []byte(secret), s.hashAlg); err != nil { + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := s.hasher.Verify(client.HashedSecret, secret) + spanPasswordComparison.EndWithError(err) + if err != nil { + s.command.OIDCSecretCheckFailed(ctx, client.AppID, client.ProjectID, client.Settings.ResourceOwner) return oidc.ErrInvalidClient().WithParent(err).WithDescription("invalid secret") } + s.command.OIDCSecretCheckSucceeded(ctx, client.AppID, client.ProjectID, client.Settings.ResourceOwner, updated) return nil } diff --git a/internal/api/oidc/client_credentials.go b/internal/api/oidc/client_credentials.go index c3622680c9..a62fa54a2b 100644 --- a/internal/api/oidc/client_credentials.go +++ b/internal/api/oidc/client_credentials.go @@ -7,8 +7,8 @@ import ( "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -41,15 +41,18 @@ func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecr if err != nil { return nil, err // defaults to server error } - if user.Machine == nil || user.Machine.Secret == nil { + if user.Machine == nil || user.Machine.EncodedSecret == "" { return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-pieP8", "Errors.User.Machine.Secret.NotExisting") } - if err = crypto.CompareHash(user.Machine.Secret, []byte(clientSecret), s.hashAlg); err != nil { + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := s.hasher.Verify(user.Machine.EncodedSecret, clientSecret) + spanPasswordComparison.EndWithError(err) + if err != nil { s.command.MachineSecretCheckFailed(ctx, user.ID, user.ResourceOwner) return nil, zerrors.ThrowInvalidArgument(err, "OIDC-VoXo6", "Errors.User.Machine.Secret.Invalid") } - s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner) + s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner, updated) return &clientCredentialsClient{ id: clientID, user: user, diff --git a/internal/api/oidc/client_integration_test.go b/internal/api/oidc/client_integration_test.go index 96812120d8..c7ace3c097 100644 --- a/internal/api/oidc/client_integration_test.go +++ b/internal/api/oidc/client_integration_test.go @@ -25,36 +25,6 @@ import ( "github.com/zitadel/zitadel/pkg/grpc/user" ) -func TestOPStorage_SetUserinfoFromToken(t *testing.T) { - clientID := createClient(t) - authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess) - sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) - linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ - AuthRequestId: authRequestID, - CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ - Session: &oidc_pb.Session{ - SessionId: sessionID, - SessionToken: sessionToken, - }, - }, - }) - require.NoError(t, err) - - // code exchange - code := assertCodeResponse(t, linkResp.GetCallbackUrl()) - tokens, err := exchangeTokens(t, clientID, code, redirectURI) - require.NoError(t, err) - assertTokens(t, tokens, true) - assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime) - - // test actual userinfo - provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) - require.NoError(t, err) - userinfo, err := rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider) - require.NoError(t, err) - assertUserinfo(t, userinfo) -} - func TestServer_Introspect(t *testing.T) { project, err := Tester.CreateProject(CTX) require.NoError(t, err) @@ -172,17 +142,6 @@ func TestServer_Introspect(t *testing.T) { } } -func assertUserinfo(t *testing.T, userinfo *oidc.UserInfo) { - assert.Equal(t, User.GetUserId(), userinfo.Subject) - assert.Equal(t, "Mickey", userinfo.GivenName) - assert.Equal(t, "Mouse", userinfo.FamilyName) - assert.Equal(t, "Mickey Mouse", userinfo.Name) - assert.NotEmpty(t, userinfo.PreferredUsername) - assert.Equal(t, userinfo.PreferredUsername, userinfo.Email) - assert.False(t, bool(userinfo.EmailVerified)) - assertOIDCTime(t, userinfo.UpdatedAt, User.GetDetails().GetChangeDate().AsTime()) -} - func assertIntrospection( t *testing.T, introspection *oidc.IntrospectionResponse, diff --git a/internal/api/oidc/device_auth.go b/internal/api/oidc/device_auth.go index eb4a6e5a85..45f6453556 100644 --- a/internal/api/oidc/device_auth.go +++ b/internal/api/oidc/device_auth.go @@ -11,7 +11,6 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/tracing" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -68,21 +67,20 @@ func (c *DeviceAuthorizationConfig) toOPConfig() op.DeviceAuthorizationConfig { // StoreDeviceAuthorization creates a new Device Authorization request. // Implements the op.DeviceAuthorizationStorage interface. -func (o *OPStorage) StoreDeviceAuthorization(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes []string) (err error) { +func (o *OPStorage) StoreDeviceAuthorization(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scope []string) (err error) { const logMsg = "store device authorization" - logger := logging.WithFields("client_id", clientID, "device_code", deviceCode, "user_code", userCode, "expires", expires, "scopes", scopes) + logger := logging.WithFields("client_id", clientID, "device_code", deviceCode, "user_code", userCode, "expires", expires, "scope", scope) ctx, span := tracing.NewSpan(ctx) defer func() { logger.OnError(err).Error(logMsg) span.EndWithError(err) }() - - scopes, err = o.assertProjectRoleScopes(ctx, clientID, scopes) + scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, clientID, scope) if err != nil { - return zerrors.ThrowPreconditionFailed(err, "OIDC-She4t", "Errors.Internal") + return err } - details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, expires, scopes) + details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, expires, scope, audience) if err == nil { logger.SetFields("details", details).Debug(logMsg) } @@ -94,6 +92,7 @@ func newDeviceAuthorizationState(d *query.DeviceAuth) *op.DeviceAuthorizationSta return &op.DeviceAuthorizationState{ ClientID: d.ClientID, Scopes: d.Scopes, + Audience: d.Audience, Expires: d.Expires, Done: d.State.Done(), Denied: d.State.Denied(), diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go index 04934e0dc8..0615193b03 100644 --- a/internal/api/oidc/introspect.go +++ b/internal/api/oidc/introspect.go @@ -11,7 +11,6 @@ import ( "github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" @@ -29,7 +28,6 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR return s.LegacyServer.Introspect(ctx, r) } if features.TriggerIntrospectionProjections { - // Execute all triggers in one concurrent sweep. query.TriggerIntrospectionProjections(ctx) } @@ -101,7 +99,7 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR if err = validateIntrospectionAudience(token.audience, client.clientID, client.projectID); err != nil { return nil, err } - userInfo, err := s.userInfo(ctx, token.userID, client.projectID, token.scope, []string{client.projectID}) + userInfo, err := s.userInfo(ctx, token.userID, client.projectID, client.projectRoleAssertion, token.scope, []string{client.projectID}) if err != nil { return nil, err } @@ -125,9 +123,10 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR } type introspectionClientResult struct { - clientID string - projectID string - err error + clientID string + projectID string + projectRoleAssertion bool + err error } var errNoClientSecret = errors.New("client has no configured secret") @@ -135,38 +134,68 @@ var errNoClientSecret = errors.New("client has no configured secret") func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCredentials, rc chan<- *introspectionClientResult) { ctx, span := tracing.NewSpan(ctx) - clientID, projectID, err := func() (string, string, error) { + clientID, projectID, projectRoleAssertion, err := func() (string, string, bool, error) { client, err := s.clientFromCredentials(ctx, cc) if err != nil { - return "", "", err + return "", "", false, err } if cc.ClientAssertion != "" { verifier := op.NewJWTProfileVerifierKeySet(keySetMap(client.PublicKeys), op.IssuerFromContext(ctx), time.Hour, time.Second) if _, err := op.VerifyJWTAssertion(ctx, cc.ClientAssertion, verifier); err != nil { - return "", "", oidc.ErrUnauthorizedClient().WithParent(err) + return "", "", false, oidc.ErrUnauthorizedClient().WithParent(err) } - return client.ClientID, client.ProjectID, nil + return client.ClientID, client.ProjectID, client.ProjectRoleAssertion, nil } - if client.ClientSecret != nil { - if err := crypto.CompareHash(client.ClientSecret, []byte(cc.ClientSecret), s.hashAlg); err != nil { - return "", "", oidc.ErrUnauthorizedClient().WithParent(err) + if client.HashedSecret != "" { + if err := s.introspectionClientSecretAuth(ctx, client, cc.ClientSecret); err != nil { + return "", "", false, oidc.ErrUnauthorizedClient().WithParent(err) } - return client.ClientID, client.ProjectID, nil + return client.ClientID, client.ProjectID, client.ProjectRoleAssertion, nil } - return "", "", oidc.ErrUnauthorizedClient().WithParent(errNoClientSecret) + return "", "", false, oidc.ErrUnauthorizedClient().WithParent(errNoClientSecret) }() span.EndWithError(err) rc <- &introspectionClientResult{ - clientID: clientID, - projectID: projectID, - err: err, + clientID: clientID, + projectID: projectID, + projectRoleAssertion: projectRoleAssertion, + err: err, } } +var errNoAppType = errors.New("introspection client without app type") + +func (s *Server) introspectionClientSecretAuth(ctx context.Context, client *query.IntrospectionClient, secret string) error { + var ( + successCommand func(ctx context.Context, appID, projectID, resourceOwner, updated string) + failedCommand func(ctx context.Context, appID, projectID, resourceOwner string) + ) + switch client.AppType { + case query.AppTypeAPI: + successCommand = s.command.APISecretCheckSucceeded + failedCommand = s.command.APISecretCheckFailed + case query.AppTypeOIDC: + successCommand = s.command.OIDCSecretCheckSucceeded + failedCommand = s.command.OIDCSecretCheckFailed + default: + return zerrors.ThrowInternal(errNoAppType, "OIDC-ooD5Ot", "Errors.Internal") + } + + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := s.hasher.Verify(client.HashedSecret, secret) + spanPasswordComparison.EndWithError(err) + if err != nil { + failedCommand(ctx, client.AppID, client.ProjectID, client.ResourceOwner) + return err + } + successCommand(ctx, client.AppID, client.ProjectID, client.ResourceOwner, updated) + return nil +} + // clientFromCredentials parses the client ID early, // and makes a single query for the client for either auth methods. func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredentials) (client *query.IntrospectionClient, err error) { diff --git a/internal/api/oidc/key.go b/internal/api/oidc/key.go index a5e0422a73..c4102c1fd2 100644 --- a/internal/api/oidc/key.go +++ b/internal/api/oidc/key.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/jonboulle/clockwork" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/op" diff --git a/internal/api/oidc/key_test.go b/internal/api/oidc/key_test.go index 266372fbc9..e7cf39c090 100644 --- a/internal/api/oidc/key_test.go +++ b/internal/api/oidc/key_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/api/oidc/oidc_integration_test.go b/internal/api/oidc/oidc_integration_test.go index 1e6f8ed118..09e76391bd 100644 --- a/internal/api/oidc/oidc_integration_test.go +++ b/internal/api/oidc/oidc_integration_test.go @@ -39,23 +39,23 @@ const ( func TestMain(m *testing.M) { os.Exit(func() int { - ctx, errCtx, cancel := integration.Contexts(10 * time.Minute) + ctx, _, cancel := integration.Contexts(10 * time.Minute) defer cancel() Tester = integration.NewTester(ctx) defer Tester.Done() - CTX, _ = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx + CTX = Tester.WithAuthorization(ctx, integration.OrgOwner) User = Tester.CreateHumanUser(CTX) Tester.SetUserPassword(CTX, User.GetUserId(), integration.UserPassword, false) Tester.RegisterUserPasskey(CTX, User.GetUserId()) - CTXLOGIN, _ = Tester.WithAuthorization(ctx, integration.Login), errCtx + CTXLOGIN = Tester.WithAuthorization(ctx, integration.Login) return m.Run() }()) } func Test_ZITADEL_API_missing_audience_scope(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -84,7 +84,7 @@ func Test_ZITADEL_API_missing_audience_scope(t *testing.T) { } func Test_ZITADEL_API_missing_authentication(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, zitadelAudienceScope) createResp, err := Tester.Client.SessionV2.CreateSession(CTX, &session.CreateSessionRequest{ Checks: &session.Checks{ @@ -118,7 +118,7 @@ func Test_ZITADEL_API_missing_authentication(t *testing.T) { } func Test_ZITADEL_API_missing_mfa(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, zitadelAudienceScope) sessionID, sessionToken, startTime, changeTime := Tester.CreatePasswordSession(t, CTX, User.GetUserId(), integration.UserPassword) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -146,7 +146,7 @@ func Test_ZITADEL_API_missing_mfa(t *testing.T) { } func Test_ZITADEL_API_success(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, zitadelAudienceScope) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -176,7 +176,7 @@ func Test_ZITADEL_API_success(t *testing.T) { func Test_ZITADEL_API_glob_redirects(t *testing.T) { const redirectURI = "https://my-org-1yfnjl2xj-my-app.vercel.app/api/auth/callback/zitadel" - clientID := createClientWithOpts(t, clientOpts{ + clientID, _ := createClientWithOpts(t, clientOpts{ redirectURI: "https://my-org-*-my-app.vercel.app/api/auth/callback/zitadel", logoutURI: "https://my-org-*-my-app.vercel.app/", devMode: true, @@ -209,7 +209,7 @@ func Test_ZITADEL_API_glob_redirects(t *testing.T) { } func Test_ZITADEL_API_inactive_access_token(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess, zitadelAudienceScope) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ @@ -249,7 +249,7 @@ func Test_ZITADEL_API_inactive_access_token(t *testing.T) { } func Test_ZITADEL_API_terminated_session(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess, zitadelAudienceScope) @@ -291,7 +291,7 @@ func Test_ZITADEL_API_terminated_session(t *testing.T) { } func Test_ZITADEL_API_terminated_session_user_disabled(t *testing.T) { - clientID := createClient(t) + clientID, _ := createClient(t) tests := []struct { name string disable func(userID string) error @@ -361,7 +361,7 @@ func Test_ZITADEL_API_terminated_session_user_disabled(t *testing.T) { } } -func createClient(t testing.TB) string { +func createClient(t testing.TB) (clientID, projectID string) { return createClientWithOpts(t, clientOpts{ redirectURI: redirectURI, logoutURI: logoutRedirectURI, @@ -375,12 +375,12 @@ type clientOpts struct { devMode bool } -func createClientWithOpts(t testing.TB, opts clientOpts) string { +func createClientWithOpts(t testing.TB, opts clientOpts) (clientID, projectID string) { project, err := Tester.CreateProject(CTX) require.NoError(t, err) app, err := Tester.CreateOIDCNativeClient(CTX, opts.redirectURI, opts.logoutURI, project.GetId(), opts.devMode) require.NoError(t, err) - return app.GetClientId() + return app.GetClientId(), project.GetId() } func createImplicitClient(t testing.TB) string { diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 98a69a77d0..3daa7e61ff 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -80,6 +80,7 @@ type OPStorage struct { } func NewServer( + ctx context.Context, config Config, defaultLogoutRedirectURI string, externalSecure bool, @@ -93,13 +94,14 @@ func NewServer( userAgentCookie, instanceHandler func(http.Handler) http.Handler, accessHandler *middleware.AccessInterceptor, fallbackLogger *slog.Logger, + hashConfig crypto.HashConfig, ) (*Server, error) { opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey) if err != nil { return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w") } storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, externalSecure) - keyCache := newPublicKeyCache(context.TODO(), config.PublicKeyCacheMaxAge, query.GetPublicKeyByID) + keyCache := newPublicKeyCache(ctx, config.PublicKeyCacheMaxAge, query.GetPublicKeyByID) accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true)) idTokenHintKeySet := newOidcKeySet(keyCache) @@ -119,7 +121,10 @@ func NewServer( if err != nil { return nil, zerrors.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider") } - + hasher, err := hashConfig.NewHasher() + if err != nil { + return nil, zerrors.ThrowInternal(err, "OIDC-Aij4e", "cannot create secret hasher") + } server := &Server{ LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), repo: repo, @@ -133,7 +138,7 @@ func NewServer( defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime, defaultIdTokenLifetime: config.DefaultIdTokenLifetime, fallbackLogger: fallbackLogger, - hashAlg: crypto.NewBCrypt(10), // as we are only verifying in oidc, the cost is already part of the hash string and the config here is irrelevant. + hasher: hasher, signingKeyAlgorithm: config.SigningKeyAlgorithm, assetAPIPrefix: assets.AssetAPI(externalSecure), } diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go index fe10db3219..e164740539 100644 --- a/internal/api/oidc/server.go +++ b/internal/api/oidc/server.go @@ -35,7 +35,7 @@ type Server struct { defaultIdTokenLifetime time.Duration fallbackLogger *slog.Logger - hashAlg crypto.HashAlgorithm + hasher *crypto.Hasher signingKeyAlgorithm string assetAPIPrefix func(ctx context.Context) string } @@ -188,13 +188,6 @@ func (s *Server) DeviceToken(ctx context.Context, r *op.ClientRequest[oidc.Devic return s.LegacyServer.DeviceToken(ctx, r) } -func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoRequest]) (_ *op.Response, err error) { - ctx, span := tracing.NewSpan(ctx) - defer func() { span.EndWithError(err) }() - - return s.LegacyServer.UserInfo(ctx, r) -} - func (s *Server) Revocation(ctx context.Context, r *op.ClientRequest[oidc.RevocationRequest]) (_ *op.Response, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/api/oidc/token_exchange.go b/internal/api/oidc/token_exchange.go index 77c9b46fa2..c50cf5859d 100644 --- a/internal/api/oidc/token_exchange.go +++ b/internal/api/oidc/token_exchange.go @@ -216,7 +216,7 @@ func (s *Server) createExchangeTokens(ctx context.Context, tokenType oidc.TokenT ) if slices.Contains(scopes, oidc.ScopeOpenID) || tokenType == oidc.JWTTokenType || tokenType == oidc.IDTokenType { projectID := client.client.ProjectID - userInfo, err = s.userInfo(ctx, subjectToken.userID, projectID, scopes, []string{projectID}) + userInfo, err = s.userInfo(ctx, subjectToken.userID, projectID, client.client.ProjectRoleAssertion, scopes, []string{projectID}) if err != nil { return nil, err } diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index a960c6ceca..e05a1a9f5d 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -5,21 +5,66 @@ import ( "encoding/base64" "encoding/json" "fmt" + "net/http" "slices" "strings" "github.com/dop251/goja" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/zitadel/internal/actions" "github.com/zitadel/zitadel/internal/actions/object" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/telemetry/tracing" ) -func (s *Server) userInfo(ctx context.Context, userID, projectID string, scope, roleAudience []string) (_ *oidc.UserInfo, err error) { - roleAudience, requestedRoles := prepareRoles(ctx, projectID, scope, roleAudience) +func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoRequest]) (_ *op.Response, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { + err = oidcError(err) + span.EndWithError(err) + }() + + features := authz.GetFeatures(ctx) + if features.LegacyIntrospection { + return s.LegacyServer.UserInfo(ctx, r) + } + if features.TriggerIntrospectionProjections { + query.TriggerOIDCUserInfoProjections(ctx) + } + + token, err := s.verifyAccessToken(ctx, r.Data.AccessToken) + if err != nil { + return nil, op.NewStatusError(oidc.ErrAccessDenied().WithDescription("access token invalid").WithParent(err), http.StatusUnauthorized) + } + + var ( + projectID string + assertion bool + ) + if token.clientID != "" { + projectID, assertion, err = s.query.GetOIDCUserinfoClientByID(ctx, token.clientID) + if err != nil { + return nil, err + } + } + + userInfo, err := s.userInfo(ctx, token.userID, projectID, assertion, token.scope, nil) + if err != nil { + return nil, err + } + return op.NewResponse(userInfo), nil +} + +func (s *Server) userInfo(ctx context.Context, userID, projectID string, projectRoleAssertion bool, scope, roleAudience []string) (_ *oidc.UserInfo, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + roleAudience, requestedRoles := prepareRoles(ctx, projectID, projectRoleAssertion, scope, roleAudience) qu, err := s.query.GetOIDCUserInfo(ctx, userID, roleAudience) if err != nil { return nil, err @@ -31,15 +76,14 @@ func (s *Server) userInfo(ctx context.Context, userID, projectID string, scope, // prepareRoles scans the requested scopes, appends to roleAudience and returns the requestedRoles. // +// Scopes with [ScopeProjectRolePrefix] are added to requestedRoles. // When [ScopeProjectsRoles] is present and roleAudience was empty, // project IDs with the [domain.ProjectIDScope] prefix are added to the roleAudience. // -// Scopes with [ScopeProjectRolePrefix] are added to requestedRoles. -// -// If the resulting requestedRoles or roleAudience are not not empty, +// If projectRoleAssertion is true and the resulting requestedRoles or roleAudience are not empty, // the current projectID will always be parts or roleAudience. // Else nil, nil is returned. -func prepareRoles(ctx context.Context, projectID string, scope, roleAudience []string) (ra, requestedRoles []string) { +func prepareRoles(ctx context.Context, projectID string, projectRoleAssertion bool, scope, roleAudience []string) (ra, requestedRoles []string) { // if all roles are requested take the audience for those from the scopes if slices.Contains(scope, ScopeProjectsRoles) && len(roleAudience) == 0 { roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope) @@ -50,7 +94,7 @@ func prepareRoles(ctx context.Context, projectID string, scope, roleAudience []s requestedRoles = append(requestedRoles, role) } } - if len(requestedRoles) == 0 && len(roleAudience) == 0 { + if !projectRoleAssertion && len(requestedRoles) == 0 && len(roleAudience) == 0 { return nil, nil } @@ -170,7 +214,10 @@ func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) { } } -func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo) error { +func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + queriedActions, err := s.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, qu.User.ResourceOwner) if err != nil { return err diff --git a/internal/api/oidc/userinfo_integration_test.go b/internal/api/oidc/userinfo_integration_test.go new file mode 100644 index 0000000000..78cd5479ed --- /dev/null +++ b/internal/api/oidc/userinfo_integration_test.go @@ -0,0 +1,270 @@ +//go:build integration + +package oidc_test + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v3/pkg/client/rp" + "github.com/zitadel/oidc/v3/pkg/oidc" + "golang.org/x/oauth2" + + oidc_api "github.com/zitadel/zitadel/internal/api/oidc" + "github.com/zitadel/zitadel/internal/integration" + feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta" + "github.com/zitadel/zitadel/pkg/grpc/management" + oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta" +) + +// TestServer_UserInfo is a top-level test which re-executes the actual +// userinfo integration test against a matrix of different feature flags. +// This ensure that the response of the different implementations remains the same. +func TestServer_UserInfo(t *testing.T) { + iamOwnerCTX := Tester.WithAuthorization(CTX, integration.IAMOwner) + t.Cleanup(func() { + _, err := Tester.Client.FeatureV2.ResetInstanceFeatures(iamOwnerCTX, &feature.ResetInstanceFeaturesRequest{}) + require.NoError(t, err) + }) + tests := []struct { + name string + legacy bool + trigger bool + }{ + { + name: "legacy enabled", + legacy: true, + }, + { + name: "legacy disabled, trigger disabled", + legacy: false, + trigger: false, + }, + { + name: "legacy disabled, trigger enabled", + legacy: false, + trigger: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := Tester.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{ + OidcLegacyIntrospection: &tt.legacy, + OidcTriggerIntrospectionProjections: &tt.trigger, + }) + require.NoError(t, err) + testServer_UserInfo(t) + }) + } +} + +// testServer_UserInfo is the actual userinfo integration test, +// which calls the userinfo endpoint with different client configurations, roles and token scopes. +func testServer_UserInfo(t *testing.T) { + const role = "testUserRole" + clientID, projectID := createClient(t) + _, err := Tester.Client.Mgmt.AddProjectRole(CTX, &management.AddProjectRoleRequest{ + ProjectId: projectID, + RoleKey: role, + DisplayName: "test", + }) + require.NoError(t, err) + _, err = Tester.Client.Mgmt.AddUserGrant(CTX, &management.AddUserGrantRequest{ + UserId: User.GetUserId(), + ProjectId: projectID, + RoleKeys: []string{role}, + }) + require.NoError(t, err) + + tests := []struct { + name string + prepare func(t *testing.T, clientID string, scope []string) *oidc.Tokens[*oidc.IDTokenClaims] + scope []string + assertions []func(*testing.T, *oidc.UserInfo) + wantErr bool + }{ + { + name: "invalid token", + prepare: func(*testing.T, string, []string) *oidc.Tokens[*oidc.IDTokenClaims] { + return &oidc.Tokens[*oidc.IDTokenClaims]{ + Token: &oauth2.Token{ + AccessToken: "DEAFBEEFDEADBEEF", + TokenType: oidc.BearerToken, + }, + IDTokenClaims: &oidc.IDTokenClaims{ + TokenClaims: oidc.TokenClaims{ + Subject: User.GetUserId(), + }, + }, + } + }, + scope: []string{oidc.ScopeProfile, oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeOfflineAccess}, + assertions: []func(*testing.T, *oidc.UserInfo){ + func(t *testing.T, ui *oidc.UserInfo) { + assert.Nil(t, ui) + }, + }, + wantErr: true, + }, + { + name: "standard scopes", + prepare: getTokens, + scope: []string{oidc.ScopeProfile, oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeOfflineAccess}, + assertions: []func(*testing.T, *oidc.UserInfo){ + assertUserinfo, + func(t *testing.T, ui *oidc.UserInfo) { + assertNoReservedScopes(t, ui.Claims) + }, + }, + }, + { + name: "project role assertion", + prepare: func(t *testing.T, clientID string, scope []string) *oidc.Tokens[*oidc.IDTokenClaims] { + _, err := Tester.Client.Mgmt.UpdateProject(CTX, &management.UpdateProjectRequest{ + Id: projectID, + Name: fmt.Sprintf("project-%d", time.Now().UnixNano()), + ProjectRoleAssertion: true, + }) + require.NoError(t, err) + t.Cleanup(func() { + _, err := Tester.Client.Mgmt.UpdateProject(CTX, &management.UpdateProjectRequest{ + Id: projectID, + Name: fmt.Sprintf("project-%d", time.Now().UnixNano()), + ProjectRoleAssertion: false, + }) + require.NoError(t, err) + }) + resp, err := Tester.Client.Mgmt.GetProjectByID(CTX, &management.GetProjectByIDRequest{Id: projectID}) + require.NoError(t, err) + require.True(t, resp.GetProject().GetProjectRoleAssertion(), "project role assertion") + + return getTokens(t, clientID, scope) + }, + scope: []string{oidc.ScopeProfile, oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeOfflineAccess}, + assertions: []func(*testing.T, *oidc.UserInfo){ + assertUserinfo, + func(t *testing.T, ui *oidc.UserInfo) { + assertProjectRoleClaims(t, projectID, ui.Claims, role) + }, + }, + }, + { + name: "projects roles scope", + prepare: getTokens, + scope: []string{oidc.ScopeProfile, oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeOfflineAccess, oidc_api.ScopeProjectRolePrefix + role}, + assertions: []func(*testing.T, *oidc.UserInfo){ + assertUserinfo, + func(t *testing.T, ui *oidc.UserInfo) { + assertProjectRoleClaims(t, projectID, ui.Claims, role) + }, + }, + }, + { + name: "PAT", + prepare: func(t *testing.T, clientID string, scope []string) *oidc.Tokens[*oidc.IDTokenClaims] { + user := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.OrgOwner) + return &oidc.Tokens[*oidc.IDTokenClaims]{ + Token: &oauth2.Token{ + AccessToken: user.Token, + TokenType: oidc.BearerToken, + }, + IDTokenClaims: &oidc.IDTokenClaims{ + TokenClaims: oidc.TokenClaims{ + Subject: user.ID, + }, + }, + } + }, + assertions: []func(*testing.T, *oidc.UserInfo){ + func(t *testing.T, ui *oidc.UserInfo) { + user := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.OrgOwner) + assert.Equal(t, user.ID, ui.Subject) + assert.Equal(t, user.PreferredLoginName, ui.PreferredUsername) + assert.Equal(t, user.Machine.Name, ui.Name) + assert.Equal(t, user.ResourceOwner, ui.Claims[oidc_api.ClaimResourceOwnerID]) + assert.NotEmpty(t, ui.Claims[oidc_api.ClaimResourceOwnerName]) + assert.NotEmpty(t, ui.Claims[oidc_api.ClaimResourceOwnerPrimaryDomain]) + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tokens := tt.prepare(t, clientID, tt.scope) + provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) + require.NoError(t, err) + userinfo, err := rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + for _, assertion := range tt.assertions { + assertion(t, userinfo) + } + }) + } +} + +func getTokens(t *testing.T, clientID string, scope []string) *oidc.Tokens[*oidc.IDTokenClaims] { + authRequestID := createAuthRequest(t, clientID, redirectURI, scope...) + sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) + linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ + AuthRequestId: authRequestID, + CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ + Session: &oidc_pb.Session{ + SessionId: sessionID, + SessionToken: sessionToken, + }, + }, + }) + require.NoError(t, err) + + // code exchange + code := assertCodeResponse(t, linkResp.GetCallbackUrl()) + tokens, err := exchangeTokens(t, clientID, code, redirectURI) + require.NoError(t, err) + assertTokens(t, tokens, true) + assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime) + + return tokens +} + +func assertUserinfo(t *testing.T, userinfo *oidc.UserInfo) { + t.Helper() + assert.Equal(t, User.GetUserId(), userinfo.Subject) + assert.Equal(t, "Mickey", userinfo.GivenName) + assert.Equal(t, "Mouse", userinfo.FamilyName) + assert.Equal(t, "Mickey Mouse", userinfo.Name) + assert.NotEmpty(t, userinfo.PreferredUsername) + assert.Equal(t, userinfo.PreferredUsername, userinfo.Email) + assert.False(t, bool(userinfo.EmailVerified)) + assertOIDCTime(t, userinfo.UpdatedAt, User.GetDetails().GetChangeDate().AsTime()) +} + +func assertNoReservedScopes(t *testing.T, claims map[string]any) { + t.Helper() + t.Log(claims) + for claim := range claims { + assert.Falsef(t, strings.HasPrefix(claim, oidc_api.ClaimPrefix), "claim %s has prefix %s", claim, oidc_api.ClaimPrefix) + } +} + +func assertProjectRoleClaims(t *testing.T, projectID string, claims map[string]any, roles ...string) { + t.Helper() + projectIDRoleClaim := fmt.Sprintf(oidc_api.ClaimProjectRolesFormat, projectID) + for _, claim := range []string{oidc_api.ClaimProjectRoles, projectIDRoleClaim} { + roleMap, ok := claims[claim].(map[string]any) + require.Truef(t, ok, "claim %s not found or wrong type %T", claim, claims[claim]) + for _, roleKey := range roles { + role, ok := roleMap[roleKey].(map[string]any) + require.Truef(t, ok, "role %s not found or wrong type %T", roleKey, roleMap[roleKey]) + assert.Equal(t, role[Tester.Organisation.ID], Tester.Organisation.Domain, "org domain in role") + } + } +} diff --git a/internal/api/oidc/userinfo_test.go b/internal/api/oidc/userinfo_test.go index d241f0d86c..21e06e21c4 100644 --- a/internal/api/oidc/userinfo_test.go +++ b/internal/api/oidc/userinfo_test.go @@ -17,9 +17,10 @@ import ( func Test_prepareRoles(t *testing.T) { type args struct { - projectID string - scope []string - roleAudience []string + projectID string + projectRoleAssertion bool + scope []string + roleAudience []string } tests := []struct { name string @@ -30,19 +31,32 @@ func Test_prepareRoles(t *testing.T) { { name: "empty scope and roleAudience", args: args{ - projectID: "projID", - scope: nil, - roleAudience: nil, + projectID: "projID", + projectRoleAssertion: false, + scope: nil, + roleAudience: nil, }, wantRa: nil, wantRequestedRoles: nil, }, + { + name: "project role assertion", + args: args{ + projectID: "projID", + projectRoleAssertion: true, + scope: nil, + roleAudience: nil, + }, + wantRa: []string{"projID"}, + wantRequestedRoles: []string{}, + }, { name: "some scope and roleAudience", args: args{ - projectID: "projID", - scope: []string{"openid", "profile"}, - roleAudience: []string{"project2"}, + projectID: "projID", + projectRoleAssertion: false, + scope: []string{"openid", "profile"}, + roleAudience: []string{"project2"}, }, wantRa: []string{"project2", "projID"}, wantRequestedRoles: []string{}, @@ -50,9 +64,10 @@ func Test_prepareRoles(t *testing.T) { { name: "scope projects roles", args: args{ - projectID: "projID", - scope: []string{ScopeProjectsRoles, domain.ProjectIDScope + "project2" + domain.AudSuffix}, - roleAudience: nil, + projectID: "projID", + projectRoleAssertion: false, + scope: []string{ScopeProjectsRoles, domain.ProjectIDScope + "project2" + domain.AudSuffix}, + roleAudience: nil, }, wantRa: []string{"project2", "projID"}, wantRequestedRoles: []string{}, @@ -60,9 +75,10 @@ func Test_prepareRoles(t *testing.T) { { name: "scope project role prefix", args: args{ - projectID: "projID", - scope: []string{"openid", "profile", ScopeProjectRolePrefix + "foo", ScopeProjectRolePrefix + "bar"}, - roleAudience: nil, + projectID: "projID", + projectRoleAssertion: false, + scope: []string{"openid", "profile", ScopeProjectRolePrefix + "foo", ScopeProjectRolePrefix + "bar"}, + roleAudience: nil, }, wantRa: []string{"projID"}, wantRequestedRoles: []string{"foo", "bar"}, @@ -70,7 +86,7 @@ func Test_prepareRoles(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotRa, gotRequestedRoles := prepareRoles(context.Background(), tt.args.projectID, tt.args.scope, tt.args.roleAudience) + gotRa, gotRequestedRoles := prepareRoles(context.Background(), tt.args.projectID, tt.args.projectRoleAssertion, tt.args.scope, tt.args.roleAudience) assert.Equal(t, tt.wantRa, gotRa, "roleAudience") assert.Equal(t, tt.wantRequestedRoles, gotRequestedRoles, "requestedRoles") }) diff --git a/internal/api/saml/certificate.go b/internal/api/saml/certificate.go index 1dd6a7407f..144a7e10a0 100644 --- a/internal/api/saml/certificate.go +++ b/internal/api/saml/certificate.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/zitadel/logging" "github.com/zitadel/saml/pkg/provider/key" diff --git a/internal/api/ui/login/device_auth.go b/internal/api/ui/login/device_auth.go index 2135772e48..b05cc1a5fd 100644 --- a/internal/api/ui/login/device_auth.go +++ b/internal/api/ui/login/device_auth.go @@ -76,7 +76,7 @@ func (l *Login) renderDeviceAuthDone(w http.ResponseWriter, r *http.Request, aut } } -// handleDeviceUserCode serves the Device Authorization user code submission form. +// handleDeviceAuthUserCode serves the Device Authorization user code submission form. // The "user_code" may be submitted by URL (GET) or form (POST). // When a "user_code" is received and found through query, // handleDeviceAuthUserCode will create a new AuthRequest in the repository. diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index 14fc930751..98c2dde6ff 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -449,6 +449,59 @@ func (l *Login) handleExternalUserAuthenticated( callback(w, r, authReq) } +// checkAutoLinking checks if a user with the provided information (username or email) already exists within ZITADEL. +// The decision, which information will be checked is based on the IdP template option. +// The function returns a boolean whether a user was found or not. +func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) bool { + queries := make([]query.SearchQuery, 0, 2) + var user *query.NotifyUser + switch provider.AutoLinking { + case domain.AutoLinkingOptionUnspecified: + // is auto linking is disable, we shouldn't even get here, but in case we do we can directly return + return false + case domain.AutoLinkingOptionUsername: + // if we're checking for usernames there are to options: + // + // If no specific org has been requested (by id or domain scope), we'll check the provided username against + // all existing loginnames and directly use that result to either prompt or continue with other idp options. + if authReq.RequestedOrgID == "" { + user, err := l.query.GetNotifyUserByLoginName(r.Context(), false, externalUser.PreferredUsername) + if err != nil { + return false + } + l.renderLinkingUserPrompt(w, r, authReq, user, nil) + return true + } + // If a specific org has been requested, we'll check the provided username against usernames (of that org). + usernameQuery, err := query.NewUserUsernameSearchQuery(externalUser.PreferredUsername, query.TextEqualsIgnoreCase) + if err != nil { + return false + } + queries = append(queries, usernameQuery) + case domain.AutoLinkingOptionEmail: + // Email will always be checked against verified email addresses. + emailQuery, err := query.NewUserVerifiedEmailSearchQuery(string(externalUser.Email)) + if err != nil { + return false + } + queries = append(queries, emailQuery) + } + // restrict the possible organization if needed (for email and usernames) + if authReq.RequestedOrgID != "" { + resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(authReq.RequestedOrgID, query.TextEquals) + if err != nil { + return false + } + queries = append(queries, resourceOwnerQuery) + } + user, err := l.query.GetNotifyUser(r.Context(), false, queries...) + if err != nil { + return false + } + l.renderLinkingUserPrompt(w, r, authReq, user, nil) + return true +} + // externalUserNotExisting is called if an externalAuthentication couldn't find a corresponding externalID // possible solutions are: // @@ -470,6 +523,13 @@ func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request, } human, idpLink, _ := mapExternalUserToLoginUser(externalUser, orgIAMPolicy.UserLoginMustBeDomain) + // let's check if auto-linking is enabled and if the user would be found by the corresponding option + if provider.AutoLinking != domain.AutoLinkingOptionUnspecified { + if l.checkAutoLinking(w, r, authReq, provider, externalUser) { + return + } + } + // if auto creation or creation itself is disabled, send the user to the notFoundOption if !provider.IsCreationAllowed || !provider.IsAutoCreation { l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLink, err) diff --git a/internal/api/ui/login/link_prompt_handler.go b/internal/api/ui/login/link_prompt_handler.go new file mode 100644 index 0000000000..04293bd864 --- /dev/null +++ b/internal/api/ui/login/link_prompt_handler.go @@ -0,0 +1,62 @@ +package login + +import ( + "net/http" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" +) + +const ( + tmplLinkingUserPrompt = "link_user_prompt" +) + +type linkingUserPromptData struct { + userData + Username string + Linking domain.AutoLinkingOption + UserID string +} + +type linkingUserPromptFormData struct { + OtherUser bool `schema:"other"` + UserID string `schema:"userID"` +} + +func (l *Login) renderLinkingUserPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, user *query.NotifyUser, err error) { + var errID, errMessage string + if err != nil { + errID, errMessage = l.getErrorMessage(r, err) + } + translator := l.getTranslator(r.Context(), authReq) + identification := user.PreferredLoginName + // hide the suffix in case the option is set and the auth request has been started with the primary domain scope + if authReq.RequestedOrgDomain && authReq.LabelPolicy != nil && authReq.LabelPolicy.HideLoginNameSuffix { + identification = user.Username + } + data := &linkingUserPromptData{ + Username: identification, + UserID: user.ID, + userData: l.getUserData(r, authReq, translator, "LinkingUserPrompt.Title", "LinkingUserPrompt.Description", errID, errMessage), + } + l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplLinkingUserPrompt], data, nil) +} + +func (l *Login) handleLinkingUserPrompt(w http.ResponseWriter, r *http.Request) { + data := new(linkingUserPromptFormData) + authReq, err := l.getAuthRequestAndParseData(r, data) + if err != nil { + l.renderLogin(w, r, authReq, err) + return + } + if data.OtherUser { + l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, nil) + return + } + err = l.authRepo.SelectUser(r.Context(), authReq.ID, data.UserID, authReq.AgentID) + if err != nil { + l.renderLogin(w, r, authReq, err) + return + } + l.renderNextStep(w, r, authReq) +} diff --git a/internal/api/ui/login/mfa_init_verify_handler.go b/internal/api/ui/login/mfa_init_verify_handler.go index a5e7326a69..d8e930b19b 100644 --- a/internal/api/ui/login/mfa_init_verify_handler.go +++ b/internal/api/ui/login/mfa_init_verify_handler.go @@ -5,12 +5,11 @@ import ( "html/template" "net/http" - "github.com/zitadel/zitadel/internal/domain" - svg "github.com/ajstarks/svgo" "github.com/boombuler/barcode/qr" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/qrcode" ) diff --git a/internal/api/ui/login/mfa_verify_u2f_handler.go b/internal/api/ui/login/mfa_verify_u2f_handler.go index c6cbe359ea..3afa416823 100644 --- a/internal/api/ui/login/mfa_verify_u2f_handler.go +++ b/internal/api/ui/login/mfa_verify_u2f_handler.go @@ -4,9 +4,8 @@ import ( "encoding/base64" "net/http" - "github.com/zitadel/zitadel/internal/domain" - http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" + "github.com/zitadel/zitadel/internal/domain" ) const ( diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index f4ca69b771..2154248bae 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -83,6 +83,7 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName tmplLDAPLogin: "ldap_login.html", tmplDeviceAuthUserCode: "device_usercode.html", tmplDeviceAuthAction: "device_action.html", + tmplLinkingUserPrompt: "link_user_prompt.html", } funcs := map[string]interface{}{ "resourceUrl": func(file string) string { @@ -235,6 +236,9 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName "ldapUrl": func() string { return path.Join(r.pathPrefix, EndpointLDAPCallback) }, + "linkingUserPromptUrl": func() string { + return path.Join(r.pathPrefix, EndpointLinkingUserPrompt) + }, } var err error r.Renderer, err = renderer.NewRenderer( diff --git a/internal/api/ui/login/router.go b/internal/api/ui/login/router.go index 414ffb1919..1e5a297b06 100644 --- a/internal/api/ui/login/router.go +++ b/internal/api/ui/login/router.go @@ -53,6 +53,8 @@ const ( EndpointDeviceAuth = "/device" EndpointDeviceAuthAction = "/device/{action}" + + EndpointLinkingUserPrompt = "/link/user" ) var ( @@ -122,5 +124,6 @@ func CreateRouter(login *Login, interceptors ...mux.MiddlewareFunc) *mux.Router router.SkipClean(true).Handle("", http.RedirectHandler(HandlerPrefix+"/", http.StatusMovedPermanently)) router.HandleFunc(EndpointDeviceAuth, login.handleDeviceAuthUserCode).Methods(http.MethodGet, http.MethodPost) router.HandleFunc(EndpointDeviceAuthAction, login.handleDeviceAuthAction).Methods(http.MethodGet, http.MethodPost) + router.HandleFunc(EndpointLinkingUserPrompt, login.handleLinkingUserPrompt).Methods(http.MethodPost) return router } diff --git a/internal/api/ui/login/select_user_handler.go b/internal/api/ui/login/select_user_handler.go index 2f9292d7ae..3febbaed7a 100644 --- a/internal/api/ui/login/select_user_handler.go +++ b/internal/api/ui/login/select_user_handler.go @@ -3,9 +3,8 @@ package login import ( "net/http" - "github.com/zitadel/zitadel/internal/domain" - http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" + "github.com/zitadel/zitadel/internal/domain" ) const ( diff --git a/internal/api/ui/login/static/i18n/bg.yaml b/internal/api/ui/login/static/i18n/bg.yaml index 2f3d8741ff..12f5e57bd6 100644 --- a/internal/api/ui/login/static/i18n/bg.yaml +++ b/internal/api/ui/login/static/i18n/bg.yaml @@ -316,6 +316,11 @@ LogoutDone: Title: Излязъл Description: Вие излязохте успешно. LoginButtonText: Влизам +LinkingUserPrompt: + Title: Намерен съществуващ потребител + Description: „Искате ли да свържете съществуващия си акаунт:“ + LinkButtonText: Връзка + OtherButtonText: Други възможности LinkingUsersDone: Title: Свързване с потребители Description: Свързването с потребители е готово. diff --git a/internal/api/ui/login/static/i18n/cs.yaml b/internal/api/ui/login/static/i18n/cs.yaml index e1f136ff9a..3b97b14bf2 100644 --- a/internal/api/ui/login/static/i18n/cs.yaml +++ b/internal/api/ui/login/static/i18n/cs.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Byli jste úspěšně odhlášeni. LoginButtonText: Přihlásit se +LinkingUserPrompt: + Title: Nalezen stávající uživatel + Description: "Chcete propojit svůj stávající účet:" + LinkButtonText: Odkaz + OtherButtonText: Jiné možnosti + LinkingUsersDone: Title: Propojení uživatele Description: Uživatel propojen. diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml index 74e4d8cdf1..d258f09aa6 100644 --- a/internal/api/ui/login/static/i18n/de.yaml +++ b/internal/api/ui/login/static/i18n/de.yaml @@ -324,6 +324,12 @@ LogoutDone: Description: Du wurdest erfolgreich abgemeldet. LoginButtonText: Anmelden +LinkingUserPrompt: + Title: Vorhandener Benutzer gefunden + Description: "Möchten Sie Ihr bestehendes Konto verknüpfen:" + LinkButtonText: Verknüpfen + OtherButtonText: Andere Optionen + LinkingUsersDone: Title: Benutzerkonto verknpüfen Description: Benuzterkonto verknpüft. diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml index 247029b1d7..338107060d 100644 --- a/internal/api/ui/login/static/i18n/en.yaml +++ b/internal/api/ui/login/static/i18n/en.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: You have logged out successfully. LoginButtonText: Login +LinkingUserPrompt: + Title: Existing User Found + Description: "Do you want to link your existing account:" + LinkButtonText: Link + OtherButtonText: Other options + LinkingUsersDone: Title: Linking User Description: User linked. diff --git a/internal/api/ui/login/static/i18n/es.yaml b/internal/api/ui/login/static/i18n/es.yaml index 1f2a288266..ef091f2910 100644 --- a/internal/api/ui/login/static/i18n/es.yaml +++ b/internal/api/ui/login/static/i18n/es.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Cerraste la sesión con éxito. LoginButtonText: iniciar sesión +LinkingUserPrompt: + Title: Usuario existente encontrado + Description: "¿Quieres vincular tu cuenta existente?" + LinkButtonText: Vincular + OtherButtonText: Otras opciones + LinkingUsersDone: Title: Vinculación de usuario Description: usuario vinculado con éxito. diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml index fc101eb627..de5d1ca495 100644 --- a/internal/api/ui/login/static/i18n/fr.yaml +++ b/internal/api/ui/login/static/i18n/fr.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Vous vous êtes déconnecté avec succès. LoginButtonText: connexion +LinkingUserPrompt: + Title: Utilisateur existant trouvé + Description: "Souhaitez-vous associer votre compte existant:" + LinkButtonText: Lier + OtherButtonText: Autres options + LinkingUsersDone: Title: Userlinking Description: Le lien avec l'utilisateur est terminé. diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml index 277513436c..24e9733943 100644 --- a/internal/api/ui/login/static/i18n/it.yaml +++ b/internal/api/ui/login/static/i18n/it.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Ti sei disconnesso con successo. LoginButtonText: Accedi +LinkingUserPrompt: + Title: Utente esistente trovato + Description: "Desideri collegare il tuo account esistente:" + LinkButtonText: Collegare + OtherButtonText: Altre opzioni + LinkingUsersDone: Title: Collegamento utente Description: Collegamento fatto. diff --git a/internal/api/ui/login/static/i18n/ja.yaml b/internal/api/ui/login/static/i18n/ja.yaml index 18070eebdd..e4ca75bad7 100644 --- a/internal/api/ui/login/static/i18n/ja.yaml +++ b/internal/api/ui/login/static/i18n/ja.yaml @@ -317,6 +317,12 @@ LogoutDone: Description: 正常にログアウトしました。 LoginButtonText: ログイン +LinkingUserPrompt: + Title: 既存のユーザーが見つかりました + Description: "既存のアカウントをリンクしますか:" + LinkButtonText: リンク + OtherButtonText: その他のオプション + LinkingUsersDone: Title: ユーザーリンク Description: ユーザーリンクが完了しました。 diff --git a/internal/api/ui/login/static/i18n/mk.yaml b/internal/api/ui/login/static/i18n/mk.yaml index cbe4fa64e7..dafc520d56 100644 --- a/internal/api/ui/login/static/i18n/mk.yaml +++ b/internal/api/ui/login/static/i18n/mk.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Успешно сте одјавени. LoginButtonText: најава +LinkingUserPrompt: + Title: Пронајден е постоечки корисник + Description: "Дали сакате да ја поврзете вашата постоечка сметка:" + LinkButtonText: Bрска + OtherButtonText: Други опции + LinkingUsersDone: Title: Поврзување на корисници Description: Поврзувањето на корисници е завршено. diff --git a/internal/api/ui/login/static/i18n/nl.yaml b/internal/api/ui/login/static/i18n/nl.yaml index 6a5cdba1a6..68d8ffa4a1 100644 --- a/internal/api/ui/login/static/i18n/nl.yaml +++ b/internal/api/ui/login/static/i18n/nl.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: U heeft succesvol uitgelogd. LoginButtonText: Inloggen +LinkingUserPrompt: + Title: Bestaande gebruiker gevonden + Description: "Wilt u uw bestaande account koppelen:" + LinkButtonText: Koppeling + OtherButtonText: Andere opties + LinkingUsersDone: Title: Koppeling Gebruiker Description: Gebruiker gekoppeld. diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml index 5e920c7f2a..e23ce3fa71 100644 --- a/internal/api/ui/login/static/i18n/pl.yaml +++ b/internal/api/ui/login/static/i18n/pl.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Wylogowano pomyślnie. LoginButtonText: Zaloguj się +LinkingUserPrompt: + Title: Znaleziono istniejącego użytkownika + Description: "Czy chcesz połączyć swoje istniejące konto:" + LinkButtonText: Połączyć + OtherButtonText: Inne opcje + LinkingUsersDone: Title: Łączenie użytkowników Description: Łączenie użytkowników zakończone pomyślnie. diff --git a/internal/api/ui/login/static/i18n/pt.yaml b/internal/api/ui/login/static/i18n/pt.yaml index 3016c49dda..19d00c72f4 100644 --- a/internal/api/ui/login/static/i18n/pt.yaml +++ b/internal/api/ui/login/static/i18n/pt.yaml @@ -321,6 +321,12 @@ LogoutDone: Description: Você fez logout com sucesso. LoginButtonText: login +LinkingUserPrompt: + Title: Usuário existente encontrado + Description: "Deseja vincular sua conta existente:" + LinkButtonText: Link + OtherButtonText: Outras opções + LinkingUsersDone: Title: Vinculação de usuários Description: Vinculação de usuários concluída. diff --git a/internal/api/ui/login/static/i18n/ru.yaml b/internal/api/ui/login/static/i18n/ru.yaml index 5782e026f8..fb88312486 100644 --- a/internal/api/ui/login/static/i18n/ru.yaml +++ b/internal/api/ui/login/static/i18n/ru.yaml @@ -324,6 +324,12 @@ LogoutDone: Description: Вы успешно вышли из системы. LoginButtonText: вход +LinkingUserPrompt: + Title: Существующий пользователь найден + Description: "Хотите ли вы связать существующую учетную запись:" + LinkButtonText: Связь + OtherButtonText: Другие варианты + LinkingUsersDone: Title: Привязка пользователя Description: Привязка пользователя выполнена. diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml index 58bd349499..bf31d270bb 100644 --- a/internal/api/ui/login/static/i18n/zh.yaml +++ b/internal/api/ui/login/static/i18n/zh.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: 您已成功退出登录。 LoginButtonText: 登录 +LinkingUserPrompt: + Title: 已找到现有用户 + Description: "您想关联您现有的帐户吗:" + LinkButtonText: 关联 + OtherButtonText: 其他选项 + LinkingUsersDone: Title: 用户链接 Description: 用户链接完成。 diff --git a/internal/api/ui/login/static/templates/link_user_prompt.html b/internal/api/ui/login/static/templates/link_user_prompt.html new file mode 100644 index 0000000000..9d6ed36cd1 --- /dev/null +++ b/internal/api/ui/login/static/templates/link_user_prompt.html @@ -0,0 +1,37 @@ +{{template "main-top" .}} + +
+

{{t "LinkingUserPrompt.Title"}}

+

+ {{t "LinkingUserPrompt.Description"}}
+ {{.Username}} +

+
+ + +
+ + {{ .CSRF }} + + + + + {{template "error-message" .}} + +
+ + + + + + +
+ +
+ + + + + + +{{template "main-bottom" .}} diff --git a/internal/api/ui/login/static/templates/register_option.html b/internal/api/ui/login/static/templates/register_option.html index 67cd972797..30d53e012e 100644 --- a/internal/api/ui/login/static/templates/register_option.html +++ b/internal/api/ui/login/static/templates/register_option.html @@ -40,12 +40,6 @@ {{template "error-message" .}} - - diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index d101a436ff..d89eb35a8b 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -19,7 +19,7 @@ type AuthRequestRepository interface { CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser, info *domain.BrowserInfo, migrationCheck bool) error SetExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser) error SetLinkingUser(ctx context.Context, request *domain.AuthRequest, externalUser *domain.ExternalUser) error - SelectUser(ctx context.Context, id, userID, userAgentID string) error + SelectUser(ctx context.Context, authReqID, userID, userAgentID string) error SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) error diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 82a60b641f..b7c85ab79e 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -75,7 +75,7 @@ type loginPolicyViewProvider interface { } type lockoutPolicyViewProvider interface { - LockoutPolicyByOrg(context.Context, bool, string, bool) (*query.LockoutPolicy, error) + LockoutPolicyByOrg(context.Context, bool, string) (*query.LockoutPolicy, error) } type idpProviderViewProvider interface { @@ -304,10 +304,10 @@ func (repo *AuthRequestRepo) setLinkingUser(ctx context.Context, request *domain return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAgentID string) (err error) { +func (repo *AuthRequestRepo) SelectUser(ctx context.Context, authReqID, userID, userAgentID string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - request, err := repo.getAuthRequest(ctx, id, userAgentID) + request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) if err != nil { return err } @@ -366,6 +366,7 @@ func lockoutPolicyToDomain(policy *query.LockoutPolicy) *domain.LockoutPolicy { }, Default: policy.IsDefault, MaxPasswordAttempts: policy.MaxPasswordAttempts, + MaxOTPAttempts: policy.MaxOTPAttempts, ShowLockOutFailures: policy.ShowFailures, } } @@ -1281,7 +1282,7 @@ func privacyPolicyToDomain(p *query.PrivacyPolicy) *domain.PrivacyPolicy { } func (repo *AuthRequestRepo) getLockoutPolicy(ctx context.Context, orgID string) (*query.LockoutPolicy, error) { - policy, err := repo.LockoutPolicyViewProvider.LockoutPolicyByOrg(ctx, false, orgID, false) + policy, err := repo.LockoutPolicyViewProvider.LockoutPolicyByOrg(ctx, false, orgID) if err != nil { return nil, err } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 55bcf2e56d..99e0c78ec6 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -184,7 +184,7 @@ type mockLockoutPolicy struct { policy *query.LockoutPolicy } -func (m *mockLockoutPolicy) LockoutPolicyByOrg(context.Context, bool, string, bool) (*query.LockoutPolicy, error) { +func (m *mockLockoutPolicy) LockoutPolicyByOrg(context.Context, bool, string) (*query.LockoutPolicy, error) { return m.policy, nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/token.go b/internal/auth/repository/eventsourcing/eventstore/token.go index ba58dfd4d7..4068a68667 100644 --- a/internal/auth/repository/eventsourcing/eventstore/token.go +++ b/internal/auth/repository/eventsourcing/eventstore/token.go @@ -21,7 +21,10 @@ type TokenRepo struct { View *view.View } -func (repo *TokenRepo) TokenByIDs(ctx context.Context, userID, tokenID string) (*usr_model.TokenView, error) { +func (repo *TokenRepo) TokenByIDs(ctx context.Context, userID, tokenID string) (_ *usr_model.TokenView, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + instanceID := authz.GetInstance(ctx).InstanceID() // always load the latest sequence first, so in case the token was not found by id, @@ -68,7 +71,10 @@ func (repo *TokenRepo) TokenByIDs(ctx context.Context, userID, tokenID string) ( return model.TokenViewToModel(token), nil } -func (r *TokenRepo) getUserEvents(ctx context.Context, userID, instanceID string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error) { +func (r *TokenRepo) getUserEvents(ctx context.Context, userID, instanceID string, changeDate time.Time, eventTypes []eventstore.EventType) (_ []eventstore.Event, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + query, err := usr_view.UserByIDQuery(userID, instanceID, changeDate, eventTypes) if err != nil { return nil, err diff --git a/internal/auth/repository/eventsourcing/view/token.go b/internal/auth/repository/eventsourcing/view/token.go index b19f5a69aa..0fe1d6d187 100644 --- a/internal/auth/repository/eventsourcing/view/token.go +++ b/internal/auth/repository/eventsourcing/view/token.go @@ -5,6 +5,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/telemetry/tracing" usr_view "github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/zerrors" @@ -87,6 +88,9 @@ func (v *View) DeleteOrgTokens(event eventstore.Event) error { } func (v *View) GetLatestTokenSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + q := &query.CurrentStateSearchQueries{ Queries: make([]query.SearchQuery, 2), } diff --git a/internal/auth_request/repository/mock/repository.mock.go b/internal/auth_request/repository/mock/repository.mock.go index 9dd297728b..773018b214 100644 --- a/internal/auth_request/repository/mock/repository.mock.go +++ b/internal/auth_request/repository/mock/repository.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./mock/repository.mock.go github.com/zitadel/zitadel/internal/auth_request/repository AuthRequestCache // + // Package mock is a generated GoMock package. package mock diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index a64951de8c..097a922653 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" diff --git a/internal/command/command.go b/internal/command/command.go index 95a76acb8b..86730fabcc 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "math/big" "net/http" "strconv" @@ -33,9 +34,10 @@ type Commands struct { jobs sync.WaitGroup - checkPermission domain.PermissionCheck - newCode cryptoCodeFunc - newCodeWithDefault cryptoCodeWithDefaultFunc + checkPermission domain.PermissionCheck + newEncryptedCode encrypedCodeFunc + newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc + newHashedSecret hashedSecretFunc eventstore *eventstore.Eventstore static static.Storage @@ -49,8 +51,8 @@ type Commands struct { smtpEncryption crypto.EncryptionAlgorithm smsEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm - userPasswordHasher *crypto.PasswordHasher - codeAlg crypto.HashAlgorithm + userPasswordHasher *crypto.Hasher + secretHasher *crypto.Hasher machineKeySize int applicationKeySize int domainVerificationAlg crypto.EncryptionAlgorithm @@ -106,6 +108,15 @@ func StartCommands( idGenerator := id.SonyFlakeGenerator() // reuse the oidcEncryption to be able to handle both tokens in the interceptor later on sessionAlg := oidcEncryption + + secretHasher, err := defaults.SecretHasher.NewHasher() + if err != nil { + return nil, fmt.Errorf("secret hasher: %w", err) + } + userPasswordHasher, err := defaults.PasswordHasher.NewHasher() + if err != nil { + return nil, fmt.Errorf("password hasher: %w", err) + } repo = &Commands{ eventstore: es, static: staticStore, @@ -123,14 +134,20 @@ func StartCommands( smtpEncryption: smtpEncryption, smsEncryption: smsEncryption, userEncryption: userEncryption, + userPasswordHasher: userPasswordHasher, + secretHasher: secretHasher, + machineKeySize: int(defaults.SecretGenerators.MachineKeySize), + applicationKeySize: int(defaults.SecretGenerators.ApplicationKeySize), domainVerificationAlg: domainVerificationEncryption, + domainVerificationGenerator: crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, domainVerificationEncryption), + domainVerificationValidator: api_http.ValidateDomain, keyAlgorithm: oidcEncryption, certificateAlgorithm: samlEncryption, webauthnConfig: webAuthN, httpClient: httpClient, checkPermission: permissionCheck, - newCode: newCryptoCode, - newCodeWithDefault: newCryptoCodeWithDefaultConfig, + newEncryptedCode: newEncryptedCode, + newEncryptedCodeWithDefault: newEncryptedCodeWithDefaultConfig, sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg), sessionTokenVerifier: sessionTokenVerifier, defaultAccessTokenLifetime: defaultAccessTokenLifetime, @@ -145,25 +162,17 @@ func StartCommands( GrpcServiceExisting: func(service string) bool { return false }, GrpcMethodExisting: func(method string) bool { return false }, ActionFunctionExisting: domain.FunctionExists(), - } - - repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost) - repo.userPasswordHasher, err = defaults.PasswordHasher.PasswordHasher() - if err != nil { - return nil, err - } - repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize) - repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize) - - repo.multifactors = domain.MultifactorConfigs{ - OTP: domain.OTPConfig{ - CryptoMFA: otpEncryption, - Issuer: defaults.Multifactors.OTP.Issuer, + multifactors: domain.MultifactorConfigs{ + OTP: domain.OTPConfig{ + CryptoMFA: otpEncryption, + Issuer: defaults.Multifactors.OTP.Issuer, + }, }, } - repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg) - repo.domainVerificationValidator = api_http.ValidateDomain + if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil { + repo.newHashedSecret = newHashedSecretWithDefault(secretHasher, defaultSecretGenerators.ClientSecret) + } return repo, nil } diff --git a/internal/command/crypto.go b/internal/command/crypto.go index dfbbc00012..5db4764f28 100644 --- a/internal/command/crypto.go +++ b/internal/command/crypto.go @@ -7,27 +7,26 @@ import ( "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/zerrors" ) -type cryptoCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) +type encrypedCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) -type cryptoCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) +type encryptedCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error) var emptyConfig = &crypto.GeneratorConfig{} -type CryptoCode struct { +type EncryptedCode struct { Crypted *crypto.CryptoValue Plain string Expiry time.Duration } -func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) { - return newCryptoCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig) +func newEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return newEncryptedCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig) } -func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) { - gen, config, err := secretGenerator(ctx, filter, typ, alg, defaultConfig) +func newEncryptedCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error) { + gen, config, err := encryptedCodeGenerator(ctx, filter, typ, alg, defaultConfig) if err != nil { return nil, err } @@ -35,41 +34,47 @@ func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.Filt if err != nil { return nil, err } - return &CryptoCode{ + return &EncryptedCode{ Crypted: crypted, Plain: plain, Expiry: config.Expiry, }, nil } -func verifyCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error { - gen, _, err := secretGenerator(ctx, filter, typ, alg, emptyConfig) +func verifyEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error { + gen, _, err := encryptedCodeGenerator(ctx, filter, typ, alg, emptyConfig) if err != nil { return err } - return crypto.VerifyCode(creation, expiry, crypted, plain, gen) + return crypto.VerifyCode(creation, expiry, crypted, plain, gen.Alg()) } -func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) { - config, err := secretGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig) +func encryptedCodeGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) { + config, err := cryptoGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig) if err != nil { return nil, nil, err } - switch a := alg.(type) { - case crypto.HashAlgorithm: - return crypto.NewHashGenerator(*config, a), config, nil - case crypto.EncryptionAlgorithm: - return crypto.NewEncryptionGenerator(*config, a), config, nil - default: - return nil, nil, zerrors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", a) + return crypto.NewEncryptionGenerator(*config, alg), config, nil +} + +type hashedSecretFunc func(ctx context.Context, filter preparation.FilterToQueryReducer) (encodedHash, plain string, err error) + +func newHashedSecretWithDefault(hasher *crypto.Hasher, defaultConfig *crypto.GeneratorConfig) hashedSecretFunc { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) { + config, err := cryptoGeneratorConfigWithDefault(ctx, filter, domain.SecretGeneratorTypeAppSecret, defaultConfig) + if err != nil { + return "", "", err + } + generator := crypto.NewHashGenerator(*config, hasher) + return generator.NewCode() } } -func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) { - return secretGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig) +func cryptoGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) { + return cryptoGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig) } -func secretGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) { +func cryptoGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) { wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ) events, err := filter(ctx, wm.Query()) if err != nil { diff --git a/internal/command/crypto_test.go b/internal/command/crypto_test.go index 14df176d12..07db20e2ec 100644 --- a/internal/command/crypto_test.go +++ b/internal/command/crypto_test.go @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/passwap" + "github.com/zitadel/passwap/bcrypt" "go.uber.org/mock/gomock" "github.com/zitadel/zitadel/internal/command/preparation" @@ -15,12 +17,11 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" - "github.com/zitadel/zitadel/internal/zerrors" ) -func mockCode(code string, exp time.Duration) cryptoCodeFunc { - return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) { - return &CryptoCode{ +func mockEncryptedCode(code string, exp time.Duration) encrypedCodeFunc { + return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return &EncryptedCode{ Crypted: &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -33,9 +34,9 @@ func mockCode(code string, exp time.Duration) cryptoCodeFunc { } } -func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFunc { - return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto, _ *crypto.GeneratorConfig) (*CryptoCode, error) { - return &CryptoCode{ +func mockEncryptedCodeWithDefault(code string, exp time.Duration) encryptedCodeWithDefaultFunc { + return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, _ *crypto.GeneratorConfig) (*EncryptedCode, error) { + return &EncryptedCode{ Crypted: &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -48,6 +49,12 @@ func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFu } } +func mockHashedSecret(secret string) hashedSecretFunc { + return func(_ context.Context, _ preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) { + return secret, secret, nil + } +} + var ( testGeneratorConfig = crypto.GeneratorConfig{ Length: 12, @@ -74,7 +81,7 @@ func testSecretGeneratorAddedEvent(typ domain.SecretGeneratorType) *instance.Sec func Test_newCryptoCode(t *testing.T) { type args struct { typ domain.SecretGeneratorType - alg crypto.Crypto + alg crypto.EncryptionAlgorithm } tests := []struct { name string @@ -87,7 +94,7 @@ func Test_newCryptoCode(t *testing.T) { eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, wantErr: io.ErrClosedPipe, }, @@ -98,13 +105,13 @@ func Test_newCryptoCode(t *testing.T) { )), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := newCryptoCode(context.Background(), tt.eventstore.Filter, tt.args.typ, tt.args.alg) + got, err := newEncryptedCode(context.Background(), tt.eventstore.Filter, tt.args.typ, tt.args.alg) //nolint:staticcheck require.ErrorIs(t, err, tt.wantErr) if tt.wantErr == nil { require.NotNil(t, got) @@ -120,12 +127,12 @@ func Test_verifyCryptoCode(t *testing.T) { es := eventstoreExpect(t, expectFilter( eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), )) - code, err := newCryptoCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockHashAlg(gomock.NewController(t))) + code, err := newEncryptedCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockEncryptionAlg(gomock.NewController(t))) //nolint:staticcheck require.NoError(t, err) type args struct { typ domain.SecretGeneratorType - alg crypto.Crypto + alg crypto.EncryptionAlgorithm expiry time.Duration crypted *crypto.CryptoValue plain string @@ -141,7 +148,7 @@ func Test_verifyCryptoCode(t *testing.T) { eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), expiry: code.Expiry, crypted: code.Crypted, plain: code.Plain, @@ -155,7 +162,7 @@ func Test_verifyCryptoCode(t *testing.T) { )), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), expiry: code.Expiry, crypted: code.Crypted, plain: code.Plain, @@ -168,7 +175,7 @@ func Test_verifyCryptoCode(t *testing.T) { )), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), expiry: code.Expiry, crypted: code.Crypted, plain: "wrong", @@ -178,7 +185,7 @@ func Test_verifyCryptoCode(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := verifyCryptoCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) + err := verifyEncryptedCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) //nolint:staticcheck if tt.wantErr { assert.Error(t, err) return @@ -188,10 +195,10 @@ func Test_verifyCryptoCode(t *testing.T) { } } -func Test_secretGenerator(t *testing.T) { +func Test_cryptoCodeGenerator(t *testing.T) { type args struct { typ domain.SecretGeneratorType - alg crypto.Crypto + alg crypto.EncryptionAlgorithm defaultConfig *crypto.GeneratorConfig } tests := []struct { @@ -207,24 +214,11 @@ func Test_secretGenerator(t *testing.T) { eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), defaultConfig: emptyConfig, }, wantErr: io.ErrClosedPipe, }, - { - name: "hash generator", - eventsore: eventstoreExpect(t, expectFilter( - eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), - )), - args: args{ - typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), - defaultConfig: emptyConfig, - }, - want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))), - wantConf: &testGeneratorConfig, - }, { name: "encryption generator", eventsore: eventstoreExpect(t, expectFilter( @@ -238,17 +232,6 @@ func Test_secretGenerator(t *testing.T) { want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), wantConf: &testGeneratorConfig, }, - { - name: "hash generator with default config", - eventsore: eventstoreExpect(t, expectFilter()), - args: args{ - typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), - defaultConfig: &testGeneratorConfig, - }, - want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))), - wantConf: &testGeneratorConfig, - }, { name: "encryption generator with default config", eventsore: eventstoreExpect(t, expectFilter()), @@ -260,25 +243,79 @@ func Test_secretGenerator(t *testing.T) { want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), wantConf: &testGeneratorConfig, }, - { - name: "unsupported type", - eventsore: eventstoreExpect(t, expectFilter( - eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), - )), - args: args{ - typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: nil, - defaultConfig: emptyConfig, - }, - wantErr: zerrors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", nil), - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig) + got, gotConf, err := encryptedCodeGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig) //nolint:staticcheck require.ErrorIs(t, err, tt.wantErr) assert.IsType(t, tt.want, got) assert.Equal(t, tt.wantConf, gotConf) }) } } + +func Test_newHashedSecretWithDefault(t *testing.T) { + tests := []struct { + name string + eventstore func(*testing.T) *eventstore.Eventstore + wantLen int + wantErr bool + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: true, + }, + { + name: "default config", + eventstore: expectEventstore( + expectFilter(), + ), + wantLen: 32, + }, + { + name: "instance config", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSecretGeneratorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecretGeneratorTypeAppSecret, + 24, + time.Hour*1, + true, + true, + true, + true, + ), + ), + ), + ), + wantLen: 24, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hasher := &crypto.Hasher{ + Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)), + } + defaultConfig := &crypto.GeneratorConfig{ + Length: 32, + Expiry: time.Minute, + IncludeLowerLetters: true, + } + generate := newHashedSecretWithDefault(hasher, defaultConfig) + encodedHash, plain, err := generate(context.Background(), tt.eventstore(t).Filter) //nolint:staticcheck + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Len(t, plain, tt.wantLen) + _, err = hasher.Verify(encodedHash, plain) + require.NoError(t, err) + }) + } +} diff --git a/internal/command/custom_login_text.go b/internal/command/custom_login_text.go index 21147c616d..9c22e1e298 100644 --- a/internal/command/custom_login_text.go +++ b/internal/command/custom_login_text.go @@ -42,7 +42,8 @@ func (c *Commands) createAllLoginTextEvents(ctx context.Context, agg *eventstore events = append(events, c.createRegistrationUserEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createExternalRegistrationUserOverviewEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createRegistrationOrgEvents(ctx, agg, existingText, text, defaultText)...) - events = append(events, c.createLinkingUserEvents(ctx, agg, existingText, text, defaultText)...) + events = append(events, c.createLinkingUserPromptEvents(ctx, agg, existingText, text, defaultText)...) + events = append(events, c.createLinkingUserDoneEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createExternalUserNotFoundEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createSuccessLoginEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createLogoutDoneEvents(ctx, agg, existingText, text, defaultText)...) @@ -979,7 +980,28 @@ func (c *Commands) createRegistrationOrgEvents(ctx context.Context, agg *eventst return events } -func (c *Commands) createLinkingUserEvents(ctx context.Context, agg *eventstore.Aggregate, existingText *CustomLoginTextReadModel, text *domain.CustomLoginText, defaultText bool) []eventstore.Command { +func (c *Commands) createLinkingUserPromptEvents(ctx context.Context, agg *eventstore.Aggregate, existingText *CustomLoginTextReadModel, text *domain.CustomLoginText, defaultText bool) []eventstore.Command { + events := make([]eventstore.Command, 0) + event := c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptTitle, existingText.LinkingUserPromptTitle, text.LinkingUserPrompt.Title, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptDescription, existingText.LinkingUserPromptDescription, text.LinkingUserPrompt.Description, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptLinkButtonText, existingText.LinkingUserPromptLinkButtonText, text.LinkingUserPrompt.LinkButtonText, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptOtherButtonText, existingText.LinkingUserPromptOtherButtonText, text.LinkingUserPrompt.OtherButtonText, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + return events +} + +func (c *Commands) createLinkingUserDoneEvents(ctx context.Context, agg *eventstore.Aggregate, existingText *CustomLoginTextReadModel, text *domain.CustomLoginText, defaultText bool) []eventstore.Command { events := make([]eventstore.Command, 0) event := c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserDoneTitle, existingText.LinkingUserDoneTitle, text.LinkingUsersDone.Title, text.Language, defaultText) if event != nil { diff --git a/internal/command/custom_login_text_model.go b/internal/command/custom_login_text_model.go index 9815293b94..b2147efefc 100644 --- a/internal/command/custom_login_text_model.go +++ b/internal/command/custom_login_text_model.go @@ -262,6 +262,11 @@ type CustomLoginTextReadModel struct { RegisterOrgPrivacyLinkText string RegisterOrgSaveButtonText string + LinkingUserPromptTitle string + LinkingUserPromptDescription string + LinkingUserPromptLinkButtonText string + LinkingUserPromptOtherButtonText string + LinkingUserDoneTitle string LinkingUserDoneDescription string LinkingUserDoneCancelButtonText string @@ -416,6 +421,10 @@ func (wm *CustomLoginTextReadModel) Reduce() error { wm.handleRegistrationOrgScreenSetEvent(e) continue } + if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserPrompt) { + wm.handleLinkingUserPromptScreenSetEvent(e) + continue + } if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserDone) { wm.handleLinkingUserDoneScreenSetEvent(e) continue @@ -556,6 +565,10 @@ func (wm *CustomLoginTextReadModel) Reduce() error { wm.handleRegistrationOrgScreenRemoveEvent(e) continue } + if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserPrompt) { + wm.handleLinkingUserPromptRemoveEvent(e) + continue + } if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserDone) { wm.handleLinkingUserDoneRemoveEvent(e) continue @@ -2323,6 +2336,25 @@ func (wm *CustomLoginTextReadModel) handleRegistrationOrgScreenRemoveEvent(e *po } } +func (wm *CustomLoginTextReadModel) handleLinkingUserPromptScreenSetEvent(e *policy.CustomTextSetEvent) { + if e.Key == domain.LoginKeyLinkingUserPromptTitle { + wm.LinkingUserPromptTitle = e.Text + return + } + if e.Key == domain.LoginKeyLinkingUserPromptDescription { + wm.LinkingUserPromptDescription = e.Text + return + } + if e.Key == domain.LoginKeyLinkingUserPromptLinkButtonText { + wm.LinkingUserPromptLinkButtonText = e.Text + return + } + if e.Key == domain.LoginKeyLinkingUserPromptOtherButtonText { + wm.LinkingUserPromptOtherButtonText = e.Text + return + } +} + func (wm *CustomLoginTextReadModel) handleLinkingUserDoneScreenSetEvent(e *policy.CustomTextSetEvent) { if e.Key == domain.LoginKeyLinkingUserDoneTitle { wm.LinkingUserDoneTitle = e.Text @@ -2342,6 +2374,25 @@ func (wm *CustomLoginTextReadModel) handleLinkingUserDoneScreenSetEvent(e *polic } } +func (wm *CustomLoginTextReadModel) handleLinkingUserPromptRemoveEvent(e *policy.CustomTextRemovedEvent) { + if e.Key == domain.LoginKeyLinkingUserPromptTitle { + wm.LinkingUserPromptTitle = "" + return + } + if e.Key == domain.LoginKeyLinkingUserPromptDescription { + wm.LinkingUserPromptDescription = "" + return + } + if e.Key == domain.LoginKeyLinkingUserPromptLinkButtonText { + wm.LinkingUserPromptLinkButtonText = "" + return + } + if e.Key == domain.LoginKeyLinkingUserPromptOtherButtonText { + wm.LinkingUserPromptOtherButtonText = "" + return + } +} + func (wm *CustomLoginTextReadModel) handleLinkingUserDoneRemoveEvent(e *policy.CustomTextRemovedEvent) { if e.Key == domain.LoginKeyLinkingUserDoneTitle { wm.LinkingUserDoneTitle = "" diff --git a/internal/command/device_auth.go b/internal/command/device_auth.go index 7abe858017..fe8b131071 100644 --- a/internal/command/device_auth.go +++ b/internal/command/device_auth.go @@ -11,7 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes []string) (*domain.ObjectDetails, error) { +func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes, audience []string) (*domain.ObjectDetails, error) { aggr := deviceauth.NewAggregate(deviceCode, authz.GetInstance(ctx).InstanceID()) model := NewDeviceAuthWriteModel(deviceCode, aggr.ResourceOwner) @@ -23,6 +23,7 @@ func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, user userCode, expires, scopes, + audience, )) if err != nil { return nil, err diff --git a/internal/command/device_auth_test.go b/internal/command/device_auth_test.go index 584f44f134..a6f3ff9389 100644 --- a/internal/command/device_auth_test.go +++ b/internal/command/device_auth_test.go @@ -34,6 +34,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { userCode string expires time.Time scopes []string + audience []string } tests := []struct { name string @@ -51,6 +52,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, ), )), }, @@ -61,6 +63,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { userCode: "456", expires: now, scopes: []string{"a", "b", "c"}, + audience: []string{"projectID", "clientID"}, }, wantDetails: &domain.ObjectDetails{ ResourceOwner: "instance1", @@ -75,6 +78,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, )), ), }, @@ -85,6 +89,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { userCode: "456", expires: now, scopes: []string{"a", "b", "c"}, + audience: []string{"projectID", "clientID"}, }, wantErr: pushErr, }, @@ -94,7 +99,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) { c := &Commands{ eventstore: tt.fields.eventstore, } - gotDetails, err := c.AddDeviceAuth(tt.args.ctx, tt.args.clientID, tt.args.deviceCode, tt.args.userCode, tt.args.expires, tt.args.scopes) + gotDetails, err := c.AddDeviceAuth(tt.args.ctx, tt.args.clientID, tt.args.deviceCode, tt.args.userCode, tt.args.expires, tt.args.scopes, tt.args.audience) require.ErrorIs(t, err, tt.wantErr) assert.Equal(t, tt.wantDetails, gotDetails) }) @@ -148,6 +153,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, ), )), expectPushFailed(pushErr, @@ -177,6 +183,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, ), )), expectPush( @@ -251,6 +258,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, ), )), expectPushFailed(pushErr, @@ -275,6 +283,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, ), )), expectPush( @@ -301,6 +310,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) { deviceauth.NewAggregate("123", "instance1"), "client_id", "123", "456", now, []string{"a", "b", "c"}, + []string{"projectID", "clientID"}, ), )), expectPush( diff --git a/internal/command/email.go b/internal/command/email.go index 519887af61..bb7dfc3f7d 100644 --- a/internal/command/email.go +++ b/internal/command/email.go @@ -23,6 +23,6 @@ func (e *Email) Validate() error { return e.Address.Validate() } -func (c *Commands) newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg) +func (c *Commands) newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg) } diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go index a71391d511..12bccd39f7 100644 --- a/internal/command/idp_model.go +++ b/internal/command/idp_model.go @@ -108,7 +108,7 @@ func (wm *OAuthIDPWriteModel) NewChanges( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint, @@ -278,7 +278,7 @@ func (wm *OIDCIDPWriteModel) NewChanges( issuer, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, idTokenMapping bool, options idp.Options, @@ -636,7 +636,7 @@ func (wm *AzureADIDPWriteModel) NewChanges( name string, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, tenant string, isEmailVerified bool, @@ -772,7 +772,7 @@ func (wm *GitHubIDPWriteModel) NewChanges( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GitHubIDPChanges, error) { @@ -904,7 +904,7 @@ func (wm *GitHubEnterpriseIDPWriteModel) NewChanges( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint string, @@ -1037,7 +1037,7 @@ func (wm *GitLabIDPWriteModel) NewChanges( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GitLabIDPChanges, error) { @@ -1161,7 +1161,7 @@ func (wm *GitLabSelfHostedIDPWriteModel) NewChanges( issuer string, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GitLabSelfHostedIDPChanges, error) { @@ -1285,7 +1285,7 @@ func (wm *GoogleIDPWriteModel) NewChanges( name string, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GoogleIDPChanges, error) { @@ -1460,7 +1460,7 @@ func (wm *LDAPIDPWriteModel) NewChanges( userObjectClasses []string, userFilters []string, timeout time.Duration, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, attributes idp.LDAPAttributes, options idp.Options, ) ([]idp.LDAPIDPChanges, error) { @@ -1653,7 +1653,7 @@ func (wm *AppleIDPWriteModel) NewChanges( teamID string, keyID string, privateKey []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.AppleIDPChanges, error) { @@ -1790,7 +1790,7 @@ func (wm *SAMLIDPWriteModel) NewChanges( metadata, key, certificate []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, binding string, withSignedRequest bool, options idp.Options, diff --git a/internal/command/instance.go b/internal/command/instance.go index e8d2a1078c..4bd3194706 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -101,7 +101,8 @@ type InstanceSetup struct { ThemeMode domain.LabelPolicyThemeMode } LockoutPolicy struct { - MaxAttempts uint64 + MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShouldShowLockoutFailure bool } EmailTemplate []byte @@ -126,7 +127,6 @@ type SetQuotas struct { } type SecretGenerators struct { - PasswordSaltCost uint ClientSecret *crypto.GeneratorConfig InitializeUserCode *crypto.GeneratorConfig EmailVerificationCode *crypto.GeneratorConfig @@ -272,7 +272,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail), prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange), - prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), + prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxPasswordAttempts, setup.LockoutPolicy.MaxOTPAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), prepareAddDefaultLabelPolicy( instanceAgg, @@ -391,6 +391,7 @@ func setupSMTPSettings(commands *Commands, validations *[]preparation.Validation *validations = append(*validations, commands.prepareAddSMTPConfig( instanceAgg, + smtpConfig.Description, smtpConfig.From, smtpConfig.FromName, smtpConfig.ReplyToAddress, @@ -457,7 +458,6 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), commands.AddAPIAppCommand( @@ -469,7 +469,6 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), commands.AddAPIAppCommand( @@ -481,10 +480,9 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), - commands.AddOIDCAppCommand(cnsl, nil), + commands.AddOIDCAppCommand(cnsl), SetIAMConsoleID(instanceAgg, &cnsl.ClientID, &ids.consoleAppID), ) } diff --git a/internal/command/instance_converter.go b/internal/command/instance_converter.go index cf1a10b199..1ed1cc123a 100644 --- a/internal/command/instance_converter.go +++ b/internal/command/instance_converter.go @@ -109,6 +109,7 @@ func writeModelToLockoutPolicy(wm *LockoutPolicyWriteModel) *domain.LockoutPolic return &domain.LockoutPolicy{ ObjectRoot: writeModelToObjectRoot(wm.WriteModel), MaxPasswordAttempts: wm.MaxPasswordAttempts, + MaxOTPAttempts: wm.MaxOTPAttempts, ShowLockOutFailures: wm.ShowLockOutFailures, } } diff --git a/internal/command/instance_custom_login_text_model.go b/internal/command/instance_custom_login_text_model.go index 7b73e1da7f..5cc6b8dc6f 100644 --- a/internal/command/instance_custom_login_text_model.go +++ b/internal/command/instance_custom_login_text_model.go @@ -3,9 +3,9 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/api/authz" "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" ) diff --git a/internal/command/instance_custom_login_text_test.go b/internal/command/instance_custom_login_text_test.go index 4ec29acab4..abdaf7307d 100644 --- a/internal/command/instance_custom_login_text_test.go +++ b/internal/command/instance_custom_login_text_test.go @@ -676,6 +676,18 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, ), @@ -1009,6 +1021,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", @@ -2233,6 +2251,30 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextSetEvent(context.Background(), @@ -2967,6 +3009,18 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { instance.NewCustomTextRemovedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), instance.NewCustomTextRemovedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, language.English, ), @@ -3075,6 +3129,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { RegistrationUser: domain.RegistrationUserScreenText{}, ExternalRegistrationUserOverview: domain.ExternalRegistrationUserOverviewScreenText{}, RegistrationOrg: domain.RegistrationOrgScreenText{}, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{}, LinkingUsersDone: domain.LinkingUserDoneScreenText{}, ExternalNotFound: domain.ExternalUserNotFoundScreenText{}, LoginSuccess: domain.SuccessLoginScreenText{}, @@ -4271,6 +4326,30 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextSetEvent(context.Background(), @@ -5592,6 +5671,30 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextRemovedEvent(context.Background(), @@ -6326,6 +6429,18 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, ), @@ -6659,6 +6774,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", diff --git a/internal/command/instance_custom_message_text_model.go b/internal/command/instance_custom_message_text_model.go index 4d222c0d70..675b57f6aa 100644 --- a/internal/command/instance_custom_message_text_model.go +++ b/internal/command/instance_custom_message_text_model.go @@ -3,9 +3,9 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/api/authz" "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" ) diff --git a/internal/command/instance_domain_test.go b/internal/command/instance_domain_test.go index 97129ec4ee..aa844cefa3 100644 --- a/internal/command/instance_domain_test.go +++ b/internal/command/instance_domain_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" @@ -141,12 +140,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) { domain.OIDCVersionV1, "consoleApplicationID", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, diff --git a/internal/command/instance_features.go b/internal/command/instance_features.go index abcdb439b4..35ef57da37 100644 --- a/internal/command/instance_features.go +++ b/internal/command/instance_features.go @@ -17,6 +17,7 @@ type InstanceFeatures struct { LegacyIntrospection *bool UserSchema *bool TokenExchange *bool + Actions *bool } func (m *InstanceFeatures) isEmpty() bool { @@ -24,7 +25,8 @@ func (m *InstanceFeatures) isEmpty() bool { m.TriggerIntrospectionProjections == nil && m.LegacyIntrospection == nil && m.UserSchema == nil && - m.TokenExchange == nil + m.TokenExchange == nil && + m.Actions == nil } func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) { diff --git a/internal/command/instance_features_model.go b/internal/command/instance_features_model.go index 02c9266c6f..55ce5ea3da 100644 --- a/internal/command/instance_features_model.go +++ b/internal/command/instance_features_model.go @@ -56,6 +56,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder { feature_v2.InstanceLegacyIntrospectionEventType, feature_v2.InstanceUserSchemaEventType, feature_v2.InstanceTokenExchangeEventType, + feature_v2.InstanceActionsEventType, ). Builder().ResourceOwner(m.ResourceOwner) } @@ -66,6 +67,7 @@ func (m *InstanceFeaturesWriteModel) reduceReset() { m.LegacyIntrospection = nil m.UserSchema = nil m.TokenExchange = nil + m.Actions = nil } func (m *InstanceFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEvent[bool]) error { @@ -86,6 +88,8 @@ func (m *InstanceFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEven m.TokenExchange = &event.Value case feature.KeyUserSchema: m.UserSchema = &event.Value + case feature.KeyActions: + m.Actions = &event.Value } return nil } @@ -98,5 +102,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LegacyIntrospection, f.LegacyIntrospection, feature_v2.InstanceLegacyIntrospectionEventType) cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.TokenExchange, f.TokenExchange, feature_v2.InstanceTokenExchangeEventType) cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.UserSchema, f.UserSchema, feature_v2.InstanceUserSchemaEventType) + cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.Actions, f.Actions, feature_v2.InstanceActionsEventType) return cmds } diff --git a/internal/command/instance_features_test.go b/internal/command/instance_features_test.go index 0130d91de9..4765fc2832 100644 --- a/internal/command/instance_features_test.go +++ b/internal/command/instance_features_test.go @@ -149,6 +149,24 @@ func TestCommands_SetInstanceFeatures(t *testing.T) { ResourceOwner: "instance1", }, }, + { + name: "set Actions", + eventstore: expectEventstore( + expectFilter(), + expectPush( + feature_v2.NewSetEvent[bool]( + ctx, aggregate, + feature_v2.InstanceActionsEventType, true, + ), + ), + ), + args: args{ctx, &InstanceFeatures{ + Actions: gu.Ptr(true), + }}, + want: &domain.ObjectDetails{ + ResourceOwner: "instance1", + }, + }, { name: "push error", eventstore: expectEventstore( @@ -186,6 +204,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) { ctx, aggregate, feature_v2.InstanceUserSchemaEventType, true, ), + feature_v2.NewSetEvent[bool]( + ctx, aggregate, + feature_v2.InstanceActionsEventType, true, + ), ), ), args: args{ctx, &InstanceFeatures{ @@ -193,6 +215,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) { TriggerIntrospectionProjections: gu.Ptr(false), LegacyIntrospection: gu.Ptr(true), UserSchema: gu.Ptr(true), + Actions: gu.Ptr(true), }}, want: &domain.ObjectDetails{ ResourceOwner: "instance1", diff --git a/internal/command/instance_idp_config.go b/internal/command/instance_idp_config.go index 31a302ff66..26f231a30b 100644 --- a/internal/command/instance_idp_config.go +++ b/internal/command/instance_idp_config.go @@ -178,17 +178,6 @@ func (c *Commands) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idp return writeModelToObjectDetails(&existingIDP.IDPConfigWriteModel.WriteModel), nil } -func (c *Commands) getInstanceIDPConfigByID(ctx context.Context, idpID string) (*domain.IDPConfig, error) { - config, err := c.instanceIDPConfigWriteModelByID(ctx, idpID) - if err != nil { - return nil, err - } - if !config.State.Exists() { - return nil, zerrors.ThrowNotFound(nil, "INSTANCE-p0pFF", "Errors.IDPConfig.NotExisting") - } - return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil -} - func (c *Commands) instanceIDPConfigWriteModelByID(ctx context.Context, idpID string) (policy *InstanceIDPConfigWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/command/instance_idp_config_model.go b/internal/command/instance_idp_config_model.go index d41b68d981..9439efc331 100644 --- a/internal/command/instance_idp_config_model.go +++ b/internal/command/instance_idp_config_model.go @@ -4,9 +4,8 @@ import ( "context" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/instance" ) diff --git a/internal/command/instance_idp_jwt_config_model.go b/internal/command/instance_idp_jwt_config_model.go index 8b557800f7..122f818297 100644 --- a/internal/command/instance_idp_jwt_config_model.go +++ b/internal/command/instance_idp_jwt_config_model.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/instance" ) diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go index 87bd8de3c8..6759442553 100644 --- a/internal/command/instance_idp_model.go +++ b/internal/command/instance_idp_model.go @@ -61,7 +61,7 @@ func (wm *InstanceOAuthIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint, @@ -171,7 +171,7 @@ func (wm *InstanceOIDCIDPWriteModel) NewChangedEvent( issuer, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, idTokenMapping bool, options idp.Options, @@ -344,7 +344,7 @@ func (wm *InstanceAzureADIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, tenant string, isEmailVerified bool, @@ -420,7 +420,7 @@ func (wm *InstanceGitHubIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GitHubIDPChangedEvent, error) { @@ -485,7 +485,7 @@ func (wm *InstanceGitHubEnterpriseIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint string, @@ -563,7 +563,7 @@ func (wm *InstanceGitLabIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GitLabIDPChangedEvent, error) { @@ -629,7 +629,7 @@ func (wm *InstanceGitLabSelfHostedIDPWriteModel) NewChangedEvent( issuer, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GitLabSelfHostedIDPChangedEvent, error) { @@ -695,7 +695,7 @@ func (wm *InstanceGoogleIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GoogleIDPChangedEvent, error) { @@ -767,7 +767,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent( userObjectClasses []string, userFilters []string, timeout time.Duration, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, attributes idp.LDAPAttributes, options idp.Options, ) (*instance.LDAPIDPChangedEvent, error) { @@ -848,7 +848,7 @@ func (wm *InstanceAppleIDPWriteModel) NewChangedEvent( teamID, keyID string, privateKey []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.AppleIDPChangedEvent, error) { @@ -912,7 +912,7 @@ func (wm *InstanceSAMLIDPWriteModel) NewChangedEvent( metadata, key, certificate []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, binding string, withSignedRequest bool, options idp.Options, diff --git a/internal/command/instance_idp_oidc_config_model.go b/internal/command/instance_idp_oidc_config_model.go index 2bfec8832c..d0f81e86aa 100644 --- a/internal/command/instance_idp_oidc_config_model.go +++ b/internal/command/instance_idp_oidc_config_model.go @@ -5,10 +5,9 @@ import ( "reflect" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/instance" ) @@ -94,7 +93,7 @@ func (wm *InstanceIDPOIDCConfigWriteModel) NewChangedEvent( authorizationEndpoint, tokenEndpoint, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, scopes ...string, diff --git a/internal/command/instance_model.go b/internal/command/instance_model.go index 98d49926bf..59223936b5 100644 --- a/internal/command/instance_model.go +++ b/internal/command/instance_model.go @@ -82,5 +82,11 @@ func (wm *InstanceWriteModel) Query() *eventstore.SearchQueryBuilder { } func InstanceAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate { - return eventstore.AggregateFromWriteModel(wm, instance.AggregateType, instance.AggregateVersion) + return &eventstore.Aggregate{ + ID: wm.AggregateID, + Type: instance.AggregateType, + ResourceOwner: wm.ResourceOwner, + InstanceID: wm.InstanceID, + Version: instance.AggregateVersion, + } } diff --git a/internal/command/instance_policy_login_model.go b/internal/command/instance_policy_login_model.go index 8d854d2b05..a94240f059 100644 --- a/internal/command/instance_policy_login_model.go +++ b/internal/command/instance_policy_login_model.go @@ -5,9 +5,8 @@ import ( "time" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/instance_policy_mail_template_model.go b/internal/command/instance_policy_mail_template_model.go index 489d6ebd2b..ce7a902dd4 100644 --- a/internal/command/instance_policy_mail_template_model.go +++ b/internal/command/instance_policy_mail_template_model.go @@ -6,7 +6,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/instance_policy_mail_template_test.go b/internal/command/instance_policy_mail_template_test.go index 4a5d9cf9a0..e7003fd384 100644 --- a/internal/command/instance_policy_mail_template_test.go +++ b/internal/command/instance_policy_mail_template_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" diff --git a/internal/command/instance_policy_password_age_model.go b/internal/command/instance_policy_password_age_model.go index ca88016121..77a2ed129b 100644 --- a/internal/command/instance_policy_password_age_model.go +++ b/internal/command/instance_policy_password_age_model.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/instance_policy_password_complexity_model.go b/internal/command/instance_policy_password_complexity_model.go index c4d895e798..56e2f64804 100644 --- a/internal/command/instance_policy_password_complexity_model.go +++ b/internal/command/instance_policy_password_complexity_model.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/instance_policy_password_lockout.go b/internal/command/instance_policy_password_lockout.go index 2ed88b997f..59766ae38f 100644 --- a/internal/command/instance_policy_password_lockout.go +++ b/internal/command/instance_policy_password_lockout.go @@ -12,9 +12,15 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) (*domain.ObjectDetails, error) { +func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLockoutPolicy(instanceAgg, maxAttempts, showLockoutFailure)) + //nolint:staticcheck + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLockoutPolicy( + instanceAgg, + maxPasswordAttempts, + maxOTPAttempts, + showLockoutFailure, + )) if err != nil { return nil, err } @@ -35,7 +41,13 @@ func (c *Commands) ChangeDefaultLockoutPolicy(ctx context.Context, policy *domai } instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel) - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, instanceAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures) + changedEvent, hasChanged := existingPolicy.NewChangedEvent( + ctx, + instanceAgg, + policy.MaxPasswordAttempts, + policy.MaxOTPAttempts, + policy.ShowLockOutFailures, + ) if !hasChanged { return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-0psjF", "Errors.IAM.LockoutPolicy.NotChanged") } @@ -65,7 +77,8 @@ func (c *Commands) defaultLockoutPolicyWriteModelByID(ctx context.Context) (poli func prepareAddDefaultLockoutPolicy( a *instance.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool, ) preparation.Validation { return func() (preparation.CreateCommands, error) { @@ -83,7 +96,7 @@ func prepareAddDefaultLockoutPolicy( return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-0olDf", "Errors.Instance.LockoutPolicy.AlreadyExists") } return []eventstore.Command{ - instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxAttempts, showLockoutFailure), + instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxPasswordAttempts, maxOTPAttempts, showLockoutFailure), }, nil }, nil } diff --git a/internal/command/instance_policy_password_lockout_model.go b/internal/command/instance_policy_password_lockout_model.go index 328fa29afb..4d529169bb 100644 --- a/internal/command/instance_policy_password_lockout_model.go +++ b/internal/command/instance_policy_password_lockout_model.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -55,11 +54,15 @@ func (wm *InstanceLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilde func (wm *InstanceLockoutPolicyWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool) (*instance.LockoutPolicyChangedEvent, bool) { changes := make([]policy.LockoutPolicyChanges, 0) - if wm.MaxPasswordAttempts != maxAttempts { - changes = append(changes, policy.ChangeMaxAttempts(maxAttempts)) + if wm.MaxPasswordAttempts != maxPasswordAttempts { + changes = append(changes, policy.ChangeMaxPasswordAttempts(maxPasswordAttempts)) + } + if wm.MaxOTPAttempts != maxOTPAttempts { + changes = append(changes, policy.ChangeMaxOTPAttempts(maxOTPAttempts)) } if wm.ShowLockOutFailures != showLockoutFailure { changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure)) diff --git a/internal/command/instance_policy_password_lockout_test.go b/internal/command/instance_policy_password_lockout_test.go index 02d5ab488d..866399caec 100644 --- a/internal/command/instance_policy_password_lockout_test.go +++ b/internal/command/instance_policy_password_lockout_test.go @@ -22,6 +22,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { type args struct { ctx context.Context maxPasswordAttempts uint64 + maxOTPAttempts uint64 showLockOutFailures bool } type res struct { @@ -44,6 +45,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), @@ -69,6 +71,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), @@ -77,6 +80,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), maxPasswordAttempts: 10, + maxOTPAttempts: 10, showLockOutFailures: true, }, res: res{ @@ -91,7 +95,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } - got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.maxPasswordAttempts, tt.args.showLockOutFailures) + got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.maxPasswordAttempts, tt.args.maxOTPAttempts, tt.args.showLockOutFailures) if tt.res.err == nil { assert.NoError(t, err) } @@ -135,6 +139,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -152,6 +157,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), @@ -162,6 +168,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -179,12 +186,13 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), ), expectPush( - newDefaultLockoutPolicyChangedEvent(context.Background(), 20, false), + newDefaultLockoutPolicyChangedEvent(context.Background(), 20, 20, false), ), ), }, @@ -192,6 +200,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 20, + MaxOTPAttempts: 20, ShowLockOutFailures: false, }, }, @@ -203,6 +212,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { InstanceID: "INSTANCE", }, MaxPasswordAttempts: 20, + MaxOTPAttempts: 20, ShowLockOutFailures: false, }, }, @@ -227,11 +237,12 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { } } -func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) *instance.LockoutPolicyChangedEvent { +func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) *instance.LockoutPolicyChangedEvent { event, _ := instance.NewLockoutPolicyChangedEvent(ctx, &instance.NewAggregate("INSTANCE").Aggregate, []policy.LockoutPolicyChanges{ - policy.ChangeMaxAttempts(maxAttempts), + policy.ChangeMaxPasswordAttempts(maxPasswordAttempts), + policy.ChangeMaxOTPAttempts(maxOTPAttempts), policy.ChangeShowLockOutFailures(showLockoutFailure), }, ) diff --git a/internal/command/instance_smtp_config_model.go b/internal/command/instance_smtp_config_model.go index ee2c20700a..e165419093 100644 --- a/internal/command/instance_smtp_config_model.go +++ b/internal/command/instance_smtp_config_model.go @@ -9,16 +9,18 @@ import ( "github.com/zitadel/zitadel/internal/repository/instance" ) -type InstanceSMTPConfigWriteModel struct { +type IAMSMTPConfigWriteModel struct { eventstore.WriteModel - SenderAddress string - SenderName string - ReplyToAddress string + ID string + Description string TLS bool Host string User string Password *crypto.CryptoValue + SenderAddress string + SenderName string + ReplyToAddress string State domain.SMTPConfigState domain string @@ -26,17 +28,19 @@ type InstanceSMTPConfigWriteModel struct { smtpSenderAddressMatchesInstanceDomain bool } -func NewInstanceSMTPConfigWriteModel(instanceID, domain string) *InstanceSMTPConfigWriteModel { - return &InstanceSMTPConfigWriteModel{ +func NewIAMSMTPConfigWriteModel(instanceID, id, domain string) *IAMSMTPConfigWriteModel { + return &IAMSMTPConfigWriteModel{ WriteModel: eventstore.WriteModel{ AggregateID: instanceID, ResourceOwner: instanceID, + InstanceID: instanceID, }, + ID: id, domain: domain, } } -func (wm *InstanceSMTPConfigWriteModel) AppendEvents(events ...eventstore.Event) { +func (wm *IAMSMTPConfigWriteModel) AppendEvents(events ...eventstore.Event) { for _, event := range events { switch e := event.(type) { case *instance.DomainAddedEvent: @@ -56,46 +60,34 @@ func (wm *InstanceSMTPConfigWriteModel) AppendEvents(events ...eventstore.Event) } } -func (wm *InstanceSMTPConfigWriteModel) Reduce() error { +func (wm *IAMSMTPConfigWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { case *instance.SMTPConfigAddedEvent: - wm.TLS = e.TLS - wm.SenderAddress = e.SenderAddress - wm.SenderName = e.SenderName - wm.ReplyToAddress = e.ReplyToAddress - wm.Host = e.Host - wm.User = e.User - wm.Password = e.Password - wm.State = domain.SMTPConfigStateActive + if wm.ID != e.ID { + continue + } + wm.reduceSMTPConfigAddedEvent(e) case *instance.SMTPConfigChangedEvent: - if e.TLS != nil { - wm.TLS = *e.TLS - } - if e.FromAddress != nil { - wm.SenderAddress = *e.FromAddress - } - if e.FromName != nil { - wm.SenderName = *e.FromName - } - if e.ReplyToAddress != nil { - wm.ReplyToAddress = *e.ReplyToAddress - } - if e.Host != nil { - wm.Host = *e.Host - } - if e.User != nil { - wm.User = *e.User + if wm.ID != e.ID { + continue } + wm.reduceSMTPConfigChangedEvent(e) case *instance.SMTPConfigRemovedEvent: - wm.State = domain.SMTPConfigStateRemoved - wm.TLS = false - wm.SenderName = "" - wm.SenderAddress = "" - wm.ReplyToAddress = "" - wm.Host = "" - wm.User = "" - wm.Password = nil + if wm.ID != e.ID { + continue + } + wm.reduceSMTPConfigRemovedEvent(e) + case *instance.SMTPConfigActivatedEvent: + if wm.ID != e.ID { + continue + } + wm.State = domain.SMTPConfigStateActive + case *instance.SMTPConfigDeactivatedEvent: + if wm.ID != e.ID { + continue + } + wm.State = domain.SMTPConfigStateInactive case *instance.DomainAddedEvent: wm.domainState = domain.InstanceDomainStateActive case *instance.DomainRemovedEvent: @@ -111,7 +103,13 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error { return wm.WriteModel.Reduce() } -func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder { +func (wm *IAMSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder { + // If ID equals ResourceOwner we're dealing with the old and unique smtp settings + // Let's set the empty ID for the query + if wm.ID == wm.ResourceOwner { + wm.ID = "" + } + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). ResourceOwner(wm.ResourceOwner). AddQuery(). @@ -122,6 +120,9 @@ func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder { instance.SMTPConfigRemovedEventType, instance.SMTPConfigChangedEventType, instance.SMTPConfigPasswordChangedEventType, + instance.SMTPConfigActivatedEventType, + instance.SMTPConfigDeactivatedEventType, + instance.SMTPConfigRemovedEventType, instance.InstanceDomainAddedEventType, instance.InstanceDomainRemovedEventType, instance.DomainPolicyAddedEventType, @@ -129,10 +130,16 @@ func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder { Builder() } -func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) { +func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id, description string, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string, smtpPassword *crypto.CryptoValue) (*instance.SMTPConfigChangedEvent, bool, error) { changes := make([]instance.SMTPConfigChanges, 0) var err error + if wm.ID != id { + changes = append(changes, instance.ChangeSMTPConfigID(id)) + } + if wm.Description != description { + changes = append(changes, instance.ChangeSMTPConfigDescription(description)) + } if wm.TLS != tls { changes = append(changes, instance.ChangeSMTPConfigTLS(tls)) } @@ -151,13 +158,87 @@ func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, agg if wm.User != smtpUser { changes = append(changes, instance.ChangeSMTPConfigSMTPUser(smtpUser)) } - + if smtpPassword != nil { + changes = append(changes, instance.ChangeSMTPConfigSMTPPassword(smtpPassword)) + } if len(changes) == 0 { return nil, false, nil } - changeEvent, err := instance.NewSMTPConfigChangeEvent(ctx, aggregate, changes) + changeEvent, err := instance.NewSMTPConfigChangeEvent(ctx, aggregate, id, changes) if err != nil { return nil, false, err } return changeEvent, true, nil } + +func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigAddedEvent(e *instance.SMTPConfigAddedEvent) { + wm.Description = e.Description + wm.TLS = e.TLS + wm.Host = e.Host + wm.User = e.User + wm.Password = e.Password + wm.SenderAddress = e.SenderAddress + wm.SenderName = e.SenderName + wm.ReplyToAddress = e.ReplyToAddress + wm.State = domain.SMTPConfigStateInactive + // If ID has empty value we're dealing with the old and unique smtp settings + // These would be the default values for ID and State + if e.ID == "" { + wm.Description = "generic" + wm.ID = e.Aggregate().ResourceOwner + wm.State = domain.SMTPConfigStateActive + } +} + +func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigChangedEvent(e *instance.SMTPConfigChangedEvent) { + if e.Description != nil { + wm.Description = *e.Description + } + if e.TLS != nil { + wm.TLS = *e.TLS + } + if e.Host != nil { + wm.Host = *e.Host + } + if e.User != nil { + wm.User = *e.User + } + if e.Password != nil { + wm.Password = e.Password + } + if e.FromAddress != nil { + wm.SenderAddress = *e.FromAddress + } + if e.FromName != nil { + wm.SenderName = *e.FromName + } + if e.ReplyToAddress != nil { + wm.ReplyToAddress = *e.ReplyToAddress + } + + // If ID has empty value we're dealing with the old and unique smtp settings + // These would be the default values for ID and State + if e.ID == "" { + wm.Description = "generic" + wm.ID = e.Aggregate().ResourceOwner + wm.State = domain.SMTPConfigStateActive + } +} + +func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigRemovedEvent(e *instance.SMTPConfigRemovedEvent) { + wm.Description = "" + wm.TLS = false + wm.SenderName = "" + wm.SenderAddress = "" + wm.ReplyToAddress = "" + wm.Host = "" + wm.User = "" + wm.Password = nil + wm.State = domain.SMTPConfigStateRemoved + + // If ID has empty value we're dealing with the old and unique smtp settings + // These would be the default values for ID and State + if e.ID == "" { + wm.ID = e.Aggregate().ResourceOwner + } +} diff --git a/internal/command/main_test.go b/internal/command/main_test.go index cbfcc42382..a5991dd16f 100644 --- a/internal/command/main_test.go +++ b/internal/command/main_test.go @@ -279,8 +279,8 @@ func (h plainHasher) Verify(encoded, password string) (verifier.Result, error) { // // With `x` set to "foo", the following encoded string would be produced by Hash: // $plain$foo$password -func mockPasswordHasher(x string) *crypto.PasswordHasher { - return &crypto.PasswordHasher{ +func mockPasswordHasher(x string) *crypto.Hasher { + return &crypto.Hasher{ Swapper: passwap.NewSwapper(plainHasher{x: x}), Prefixes: []string{"$plain$"}, } diff --git a/internal/command/org_custom_login_text_test.go b/internal/command/org_custom_login_text_test.go index ede20ef5c0..0411a533a4 100644 --- a/internal/command/org_custom_login_text_test.go +++ b/internal/command/org_custom_login_text_test.go @@ -1083,6 +1083,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, @@ -1464,6 +1484,12 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", @@ -2487,6 +2513,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, @@ -3194,6 +3240,18 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, language.English, ), @@ -3303,6 +3361,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { ExternalRegistrationUserOverview: domain.ExternalRegistrationUserOverviewScreenText{}, RegistrationUser: domain.RegistrationUserScreenText{}, RegistrationOrg: domain.RegistrationOrgScreenText{}, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{}, LinkingUsersDone: domain.LinkingUserDoneScreenText{}, ExternalNotFound: domain.ExternalUserNotFoundScreenText{}, LoginSuccess: domain.SuccessLoginScreenText{}, @@ -4297,6 +4356,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, @@ -5392,6 +5471,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), + ), eventFromEventPusher( org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, language.English, @@ -6099,6 +6198,18 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, ), @@ -6432,6 +6543,12 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", diff --git a/internal/command/org_idp_config_model.go b/internal/command/org_idp_config_model.go index 41b3950717..e4d5e56ca8 100644 --- a/internal/command/org_idp_config_model.go +++ b/internal/command/org_idp_config_model.go @@ -3,9 +3,8 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/org" ) diff --git a/internal/command/org_idp_jwt_config_model.go b/internal/command/org_idp_jwt_config_model.go index 18ef166376..b5144e4a8d 100644 --- a/internal/command/org_idp_jwt_config_model.go +++ b/internal/command/org_idp_jwt_config_model.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/org" ) diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go index 04eeeb8ac2..2d8bc8f8f1 100644 --- a/internal/command/org_idp_model.go +++ b/internal/command/org_idp_model.go @@ -63,7 +63,7 @@ func (wm *OrgOAuthIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint, @@ -173,7 +173,7 @@ func (wm *OrgOIDCIDPWriteModel) NewChangedEvent( issuer, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, idTokenMapping bool, options idp.Options, @@ -350,7 +350,7 @@ func (wm *OrgAzureADIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, tenant string, isEmailVerified bool, @@ -426,7 +426,7 @@ func (wm *OrgGitHubIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GitHubIDPChangedEvent, error) { @@ -492,7 +492,7 @@ func (wm *OrgGitHubEnterpriseIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint string, @@ -571,7 +571,7 @@ func (wm *OrgGitLabIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GitLabIDPChangedEvent, error) { @@ -637,7 +637,7 @@ func (wm *OrgGitLabSelfHostedIDPWriteModel) NewChangedEvent( issuer, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GitLabSelfHostedIDPChangedEvent, error) { @@ -705,7 +705,7 @@ func (wm *OrgGoogleIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GoogleIDPChangedEvent, error) { @@ -777,7 +777,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent( userObjectClasses []string, userFilters []string, timeout time.Duration, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, attributes idp.LDAPAttributes, options idp.Options, ) (*org.LDAPIDPChangedEvent, error) { @@ -858,7 +858,7 @@ func (wm *OrgAppleIDPWriteModel) NewChangedEvent( teamID, keyID string, privateKey []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.AppleIDPChangedEvent, error) { @@ -924,7 +924,7 @@ func (wm *OrgSAMLIDPWriteModel) NewChangedEvent( metadata, key, certificate []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, binding string, withSignedRequest bool, options idp.Options, diff --git a/internal/command/org_idp_oidc_config_model.go b/internal/command/org_idp_oidc_config_model.go index 6d19b39f21..571f0aa91b 100644 --- a/internal/command/org_idp_oidc_config_model.go +++ b/internal/command/org_idp_oidc_config_model.go @@ -4,10 +4,9 @@ import ( "context" "reflect" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/org" ) @@ -93,7 +92,7 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent( authorizationEndpoint, tokenEndpoint, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, scopes ...string, diff --git a/internal/command/org_policy_label_model.go b/internal/command/org_policy_label_model.go index d8e510f425..2806c14e0a 100644 --- a/internal/command/org_policy_label_model.go +++ b/internal/command/org_policy_label_model.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/org_policy_lockout.go b/internal/command/org_policy_lockout.go index 47f98d9770..052fd9e239 100644 --- a/internal/command/org_policy_lockout.go +++ b/internal/command/org_policy_lockout.go @@ -21,7 +21,13 @@ func (c *Commands) AddLockoutPolicy(ctx context.Context, resourceOwner string, p } orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, org.NewLockoutPolicyAddedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures)) + pushedEvents, err := c.eventstore.Push(ctx, org.NewLockoutPolicyAddedEvent( + ctx, + orgAgg, + policy.MaxPasswordAttempts, + policy.MaxOTPAttempts, + policy.ShowLockOutFailures, + )) if err != nil { return nil, err } @@ -45,7 +51,7 @@ func (c *Commands) ChangeLockoutPolicy(ctx context.Context, resourceOwner string } orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel) - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.MaxOTPAttempts, policy.ShowLockOutFailures) if !hasChanged { return nil, zerrors.ThrowPreconditionFailed(nil, "ORG-0JFSr", "Errors.Org.LockoutPolicy.NotChanged") } @@ -106,3 +112,20 @@ func (c *Commands) orgLockoutPolicyWriteModelByID(ctx context.Context, orgID str } return policy, nil } + +func (c *Commands) getLockoutPolicy(ctx context.Context, orgID string) (*domain.LockoutPolicy, error) { + orgWm, err := c.orgLockoutPolicyWriteModelByID(ctx, orgID) + if err != nil { + return nil, err + } + if orgWm.State == domain.PolicyStateActive { + return writeModelToLockoutPolicy(&orgWm.LockoutPolicyWriteModel), nil + } + instanceWm, err := c.defaultLockoutPolicyWriteModelByID(ctx) + if err != nil { + return nil, err + } + policy := writeModelToLockoutPolicy(&instanceWm.LockoutPolicyWriteModel) + policy.Default = true + return policy, nil +} diff --git a/internal/command/org_policy_lockout_model.go b/internal/command/org_policy_lockout_model.go index 1ce78055fb..9749183e5a 100644 --- a/internal/command/org_policy_lockout_model.go +++ b/internal/command/org_policy_lockout_model.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -56,11 +55,15 @@ func (wm *OrgLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder { func (wm *OrgLockoutPolicyWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool) (*org.LockoutPolicyChangedEvent, bool) { changes := make([]policy.LockoutPolicyChanges, 0) - if wm.MaxPasswordAttempts != maxAttempts { - changes = append(changes, policy.ChangeMaxAttempts(maxAttempts)) + if wm.MaxPasswordAttempts != maxPasswordAttempts { + changes = append(changes, policy.ChangeMaxPasswordAttempts(maxPasswordAttempts)) + } + if wm.MaxOTPAttempts != maxOTPAttempts { + changes = append(changes, policy.ChangeMaxOTPAttempts(maxOTPAttempts)) } if wm.ShowLockOutFailures != showLockoutFailure { changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure)) diff --git a/internal/command/org_policy_lockout_test.go b/internal/command/org_policy_lockout_test.go index 1eda5f348c..89444d8dc2 100644 --- a/internal/command/org_policy_lockout_test.go +++ b/internal/command/org_policy_lockout_test.go @@ -44,6 +44,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -61,6 +62,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -72,6 +74,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -89,6 +92,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -99,6 +103,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -109,6 +114,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { ResourceOwner: "org1", }, MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -163,6 +169,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -183,6 +190,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -200,6 +208,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -211,6 +220,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -228,12 +238,13 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), ), expectPush( - newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, false), + newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, 5, false), ), ), }, @@ -242,6 +253,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 5, + MaxOTPAttempts: 5, ShowLockOutFailures: false, }, }, @@ -252,6 +264,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { ResourceOwner: "org1", }, MaxPasswordAttempts: 5, + MaxOTPAttempts: 5, ShowLockOutFailures: false, }, }, @@ -334,6 +347,7 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -371,11 +385,12 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) { } } -func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent { +func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent { event, _ := org.NewLockoutPolicyChangedEvent(ctx, &org.NewAggregate(orgID).Aggregate, []policy.LockoutPolicyChanges{ - policy.ChangeMaxAttempts(maxAttempts), + policy.ChangeMaxPasswordAttempts(maxPasswordAttempts), + policy.ChangeMaxOTPAttempts(maxOTPAttempts), policy.ChangeShowLockOutFailures(showLockoutFailure), }, ) diff --git a/internal/command/org_policy_login.go b/internal/command/org_policy_login.go index 96251ad565..cf5bb7d1e5 100644 --- a/internal/command/org_policy_login.go +++ b/internal/command/org_policy_login.go @@ -416,7 +416,7 @@ func prepareAddLoginPolicy(a *org.Aggregate, policy *AddLoginPolicy) preparation return nil, zerrors.ThrowAlreadyExists(nil, "Org-Dgfb2", "Errors.Org.LoginPolicy.AlreadyExists") } for _, idp := range policy.IDPProviders { - exists, err := idpExists(ctx, filter, idp) + exists, err := ExistsIDP(ctx, filter, idp.ConfigID, authz.GetCtxData(ctx).OrgID) if !exists || err != nil { return nil, zerrors.ThrowPreconditionFailed(err, "Org-FEd32", "Errors.IDPConfig.NotExisting") } @@ -493,10 +493,3 @@ func prepareChangeLoginPolicy(a *org.Aggregate, policy *ChangeLoginPolicy) prepa }, nil } } - -func idpExists(ctx context.Context, filter preparation.FilterToQueryReducer, idp *AddLoginPolicyIDP) (bool, error) { - if idp.Type == domain.IdentityProviderTypeSystem { - return exists(ctx, filter, NewInstanceIDPConfigWriteModel(ctx, idp.ConfigID)) - } - return exists(ctx, filter, NewOrgIDPConfigWriteModel(idp.ConfigID, authz.GetCtxData(ctx).ResourceOwner)) -} diff --git a/internal/command/org_policy_login_model.go b/internal/command/org_policy_login_model.go index 6a7c24a9b9..1ab9f0cd42 100644 --- a/internal/command/org_policy_login_model.go +++ b/internal/command/org_policy_login_model.go @@ -4,9 +4,8 @@ import ( "context" "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go index 38026f72dc..62b418d029 100644 --- a/internal/command/org_policy_login_test.go +++ b/internal/command/org_policy_login_test.go @@ -266,8 +266,9 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, - expectFilter(), - expectFilter(), + expectFilter(), // reduce login policy + expectFilter(), // check if is org idp + expectFilter(), // check if is instance idp ), }, args: args{ @@ -304,11 +305,12 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { }, }, { - name: "add policy idp, ok", + name: "add policy instance idp, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter(), + expectFilter(), expectFilter( eventFromEventPusher( instance.NewIDPConfigAddedEvent(context.Background(), @@ -385,6 +387,88 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { }, }, }, + { + name: "add policy org idp, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("ORG").Aggregate, + "config1", + "name1", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeGoogle, + true, + ), + ), + ), + expectPush( + org.NewLoginPolicyAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + domain.PasswordlessTypeAllowed, + "https://example.com/redirect", + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, + ), + org.NewIdentityProviderAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + domain.IdentityProviderTypeOrg, + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + policy: &AddLoginPolicy{ + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + ForceMFALocalOnly: true, + HidePasswordReset: true, + IgnoreUnknownUsernames: true, + AllowDomainDiscovery: true, + DisableLoginWithEmail: true, + DisableLoginWithPhone: true, + PasswordlessType: domain.PasswordlessTypeAllowed, + DefaultRedirectURI: "https://example.com/redirect", + PasswordCheckLifetime: time.Hour * 1, + ExternalLoginCheckLifetime: time.Hour * 2, + MFAInitSkipLifetime: time.Hour * 3, + SecondFactorCheckLifetime: time.Hour * 4, + MultiFactorCheckLifetime: time.Hour * 5, + IDPProviders: []*AddLoginPolicyIDP{ + { + Type: domain.IdentityProviderTypeOrg, + ConfigID: "config1", + }, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/command/org_policy_mail_template_model.go b/internal/command/org_policy_mail_template_model.go index 3586362a62..502aa8dc46 100644 --- a/internal/command/org_policy_mail_template_model.go +++ b/internal/command/org_policy_mail_template_model.go @@ -5,7 +5,6 @@ import ( "reflect" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/org_policy_password_age_model.go b/internal/command/org_policy_password_age_model.go index 8d62a5d688..88ba44a5f6 100644 --- a/internal/command/org_policy_password_age_model.go +++ b/internal/command/org_policy_password_age_model.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/org_policy_password_complexity_model.go b/internal/command/org_policy_password_complexity_model.go index 252acf32bb..6491e4d2ef 100644 --- a/internal/command/org_policy_password_complexity_model.go +++ b/internal/command/org_policy_password_complexity_model.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) diff --git a/internal/command/org_test.go b/internal/command/org_test.go index 12199a998f..919c86b80f 100644 --- a/internal/command/org_test.go +++ b/internal/command/org_test.go @@ -1260,7 +1260,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator - newCode cryptoCodeFunc + newCode encrypedCodeFunc keyAlgorithm crypto.EncryptionAlgorithm } type args struct { @@ -1437,7 +1437,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ), ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: authz.WithRequestedDomain(context.Background(), "iam-domain"), @@ -1616,7 +1616,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ), ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID", "tokenID"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ @@ -1669,10 +1669,10 @@ func TestCommandSide_SetUpOrg(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore(t), - idGenerator: tt.fields.idGenerator, - newCode: tt.fields.newCode, - keyAlgorithm: tt.fields.keyAlgorithm, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + keyAlgorithm: tt.fields.keyAlgorithm, zitadelRoles: []authz.RoleMapping{ { Role: domain.RoleOrgOwner, diff --git a/internal/command/phone.go b/internal/command/phone.go index 9b0a422b26..0770231969 100644 --- a/internal/command/phone.go +++ b/internal/command/phone.go @@ -16,6 +16,6 @@ type Phone struct { ReturnCode bool } -func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg) +func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg) } diff --git a/internal/command/policy_password_lockout_model.go b/internal/command/policy_password_lockout_model.go index a931b63b65..a772cd9828 100644 --- a/internal/command/policy_password_lockout_model.go +++ b/internal/command/policy_password_lockout_model.go @@ -10,6 +10,7 @@ type LockoutPolicyWriteModel struct { eventstore.WriteModel MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShowLockOutFailures bool State domain.PolicyState } @@ -19,12 +20,16 @@ func (wm *LockoutPolicyWriteModel) Reduce() error { switch e := event.(type) { case *policy.LockoutPolicyAddedEvent: wm.MaxPasswordAttempts = e.MaxPasswordAttempts + wm.MaxOTPAttempts = e.MaxOTPAttempts wm.ShowLockOutFailures = e.ShowLockOutFailures wm.State = domain.PolicyStateActive case *policy.LockoutPolicyChangedEvent: if e.MaxPasswordAttempts != nil { wm.MaxPasswordAttempts = *e.MaxPasswordAttempts } + if e.MaxOTPAttempts != nil { + wm.MaxOTPAttempts = *e.MaxOTPAttempts + } if e.ShowLockOutFailures != nil { wm.ShowLockOutFailures = *e.ShowLockOutFailures } diff --git a/internal/command/preparation_test.go b/internal/command/preparation_test.go index ddfc7b5d8f..9ffd815b39 100644 --- a/internal/command/preparation_test.go +++ b/internal/command/preparation_test.go @@ -11,7 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" ) -//Want represents the expected values for each step +// Want represents the expected values for each step type Want struct { ValidationErr error CreateErr error @@ -22,7 +22,7 @@ type CommandVerifier interface { Validate(eventstore.Command) bool } -//AssertValidation checks if the validation works as inteded +// AssertValidation checks if the validation works as intended func AssertValidation(t *testing.T, ctx context.Context, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) { t.Helper() @@ -51,7 +51,7 @@ func AssertValidation(t *testing.T, ctx context.Context, validation preparation. for i, cmd := range want.Commands { if v, ok := cmd.(CommandVerifier); ok { if verified := v.Validate(cmds[i]); !verified { - t.Errorf("verification failed on command: = %v, want %v", cmds[i], cmd) + t.Errorf("verification failed on command: =\n%v\nwant\n%v", cmds[i], cmd) } continue } diff --git a/internal/command/project_application.go b/internal/command/project_application.go index 700f3f68eb..07bfa837b1 100644 --- a/internal/command/project_application.go +++ b/internal/command/project_application.go @@ -3,8 +3,6 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/repository/project" "github.com/zitadel/zitadel/internal/zerrors" @@ -16,10 +14,6 @@ type AddApp struct { Name string } -func (c *Commands) newAppClientSecret(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.HashAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeAppSecret, alg) -} - func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) { if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid") diff --git a/internal/command/project_application_api.go b/internal/command/project_application_api.go index cbbfb87ea3..b1af6c1e5d 100644 --- a/internal/command/project_application_api.go +++ b/internal/command/project_application_api.go @@ -4,10 +4,7 @@ import ( "context" "strings" - "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" project_repo "github.com/zitadel/zitadel/internal/repository/project" @@ -20,11 +17,11 @@ type addAPIApp struct { AuthMethodType domain.APIAuthMethodType ClientID string - ClientSecret *crypto.CryptoValue + EncodedHash string ClientSecretPlain string } -func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { +func (c *Commands) AddAPIAppCommand(app *addAPIApp) preparation.Validation { return func() (preparation.CreateCommands, error) { if app.ID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument") @@ -44,11 +41,10 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA } if app.AuthMethodType == domain.APIAuthMethodTypeBasic { - code, err := c.newAppClientSecret(ctx, filter, clientSecretAlg) + app.EncodedHash, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter) if err != nil { return nil, err } - app.ClientSecret, app.ClientSecretPlain = code.Crypted, code.Plain } return []eventstore.Command{ @@ -63,7 +59,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA &app.Aggregate.Aggregate, app.ID, app.ClientID, - app.ClientSecret, + app.EncodedHash, app.AuthMethodType, ), }, nil @@ -71,7 +67,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA } } -func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { +func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string) (_ *domain.APIApp, err error) { existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, appID, resourceOwner) if err != nil { return nil, err @@ -84,10 +80,10 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound") } - return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator) + return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID) } -func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { +func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) { if apiApp == nil || apiApp.AggregateID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid") } @@ -105,10 +101,10 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, return nil, err } - return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator) + return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID) } -func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { +func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.APIApp, err error) { apiApp.AppID = appID addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner) @@ -118,12 +114,14 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiApp.AppID, apiApp.AppName), } - var stringPw string + var plain string err = domain.SetNewClientID(apiApp, c.idGenerator, project) if err != nil { return nil, err } - stringPw, err = domain.SetNewClientSecretIfNeeded(apiApp, appSecretGenerator) + plain, err = domain.SetNewClientSecretIfNeeded(apiApp, func() (string, string, error) { + return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck + }) if err != nil { return nil, err } @@ -131,7 +129,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A projectAgg, apiApp.AppID, apiApp.ClientID, - apiApp.ClientSecret, + apiApp.EncodedHash, apiApp.AuthMethodType)) addedApplication.AppID = apiApp.AppID @@ -144,7 +142,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A return nil, err } result := apiWriteModelToAPIConfig(addedApplication) - result.ClientSecretString = stringPw + result.ClientSecretString = plain return result, nil } @@ -188,7 +186,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA return apiWriteModelToAPIConfig(existingAPI), nil } -func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) { +func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.APIApp, error) { if projectID == "" || appID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") } @@ -203,14 +201,14 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap if !existingAPI.IsAPI() { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI") } - cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator) + encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck if err != nil { return nil, err } projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret)) + pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash)) if err != nil { return nil, err } @@ -220,7 +218,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap } result := apiWriteModelToAPIConfig(existingAPI) - result.ClientSecretString = stringPW + result.ClientSecretString = plain return result, err } @@ -233,26 +231,35 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID, return err } if !app.State.Exists() { - return zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting") + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting") } if !app.IsAPI() { return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI") } - if app.ClientSecret == nil { + if app.HashedSecret == "" { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid") } projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel) - ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") - err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg) + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := c.secretHasher.Verify(app.HashedSecret, secret) spanPasswordComparison.EndWithError(err) if err == nil { - _, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID)) + c.apiSecretCheckSucceeded(ctx, projectAgg, app.AppID, updated) return err } - _, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) - logging.Log("COMMAND-g3f12").OnError(err).Error("could not push event APIClientSecretCheckFailed") - return zerrors.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid") + c.apiSecretCheckFailed(ctx, projectAgg, app.AppID) + return zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid") +} + +func (c *Commands) APISecretCheckSucceeded(ctx context.Context, appID, projectID, resourceOwner, updated string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.apiSecretCheckSucceeded(ctx, &agg.Aggregate, appID, updated) +} + +func (c *Commands) APISecretCheckFailed(ctx context.Context, appID, projectID, resourceOwner string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.apiSecretCheckFailed(ctx, &agg.Aggregate, appID) } func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) { @@ -263,3 +270,18 @@ func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, re } return appWriteModel, nil } + +func (c *Commands) apiSecretCheckSucceeded(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) { + cmds := append( + make([]eventstore.Command, 0, 2), + project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, agg, appID), + ) + if updated != "" { + cmds = append(cmds, project_repo.NewAPIConfigSecretHashUpdatedEvent(ctx, agg, appID, updated)) + } + c.asyncPush(ctx, cmds...) +} + +func (c *Commands) apiSecretCheckFailed(ctx context.Context, agg *eventstore.Aggregate, appID string) { + c.asyncPush(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, agg, appID)) +} diff --git a/internal/command/project_application_api_model.go b/internal/command/project_application_api_model.go index f7c1ef3914..a6df8d77e4 100644 --- a/internal/command/project_application_api_model.go +++ b/internal/command/project_application_api_model.go @@ -3,10 +3,9 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/project" ) @@ -16,7 +15,7 @@ type APIApplicationWriteModel struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + HashedSecret string ClientSecretString string AuthMethodType domain.APIAuthMethodType State domain.AppState @@ -84,6 +83,11 @@ func (wm *APIApplicationWriteModel) AppendEvents(events ...eventstore.Event) { continue } wm.WriteModel.AppendEvents(e) + case *project.APIConfigSecretHashUpdatedEvent: + if e.AppID != wm.AppID { + continue + } + wm.WriteModel.AppendEvents(e) case *project.ProjectRemovedEvent: wm.WriteModel.AppendEvents(e) } @@ -115,7 +119,9 @@ func (wm *APIApplicationWriteModel) Reduce() error { case *project.APIConfigChangedEvent: wm.appendChangeAPIEvent(e) case *project.APIConfigSecretChangedEvent: - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) + case *project.APIConfigSecretHashUpdatedEvent: + wm.HashedSecret = e.HashedSecret case *project.ProjectRemovedEvent: wm.State = domain.AppStateRemoved } @@ -126,7 +132,7 @@ func (wm *APIApplicationWriteModel) Reduce() error { func (wm *APIApplicationWriteModel) appendAddAPIEvent(e *project.APIConfigAddedEvent) { wm.api = true wm.ClientID = e.ClientID - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) wm.AuthMethodType = e.AuthMethodType } @@ -151,8 +157,9 @@ func (wm *APIApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { project.APIConfigAddedType, project.APIConfigChangedType, project.APIConfigSecretChangedType, - project.ProjectRemovedType). - Builder() + project.APIConfigSecretHashUpdatedType, + project.ProjectRemovedType, + ).Builder() } func (wm *APIApplicationWriteModel) NewChangedEvent( diff --git a/internal/command/project_application_api_test.go b/internal/command/project_application_api_test.go index f5d8fb7836..7f3d249bbc 100644 --- a/internal/command/project_application_api_test.go +++ b/internal/command/project_application_api_test.go @@ -2,9 +2,13 @@ package command import ( "context" + "io" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/passwap" + "github.com/zitadel/passwap/bcrypt" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" @@ -114,7 +118,7 @@ func TestAddAPIConfig(t *testing.T) { project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate, "appID", "clientID@project", - nil, + "", domain.APIAuthMethodTypePrivateKeyJWT, ), }, @@ -137,7 +141,6 @@ func TestAddAPIConfig(t *testing.T) { }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), tt.args.filter, tt.want) }) } @@ -145,14 +148,13 @@ func TestAddAPIConfig(t *testing.T) { func TestCommandSide_AddAPIApplication(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator } type args struct { - ctx context.Context - apiApp *domain.APIApp - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + apiApp *domain.APIApp + resourceOwner string } type res struct { want *domain.APIApp @@ -167,9 +169,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "no aggregate id, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -183,8 +183,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "project not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -206,8 +205,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "invalid app, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -236,8 +234,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "create api app basic, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -256,12 +253,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -276,8 +268,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { AppName: "app", AuthMethodType: domain.APIAuthMethodTypeBasic, }, - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + resourceOwner: "org1", }, res: res{ want: &domain.APIApp{ @@ -288,7 +279,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.APIAuthMethodTypeBasic, State: domain.AppStateActive, }, @@ -297,8 +288,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "create api app jwt, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -317,7 +307,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - nil, + "", domain.APIAuthMethodTypePrivateKeyJWT), ), ), @@ -352,10 +342,14 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -371,7 +365,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { func TestCommandSide_ChangeAPIApplication(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore } type args struct { ctx context.Context @@ -391,9 +385,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "missing appid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -414,9 +406,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "missing aggregateid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -437,8 +427,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "app not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -460,8 +449,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "no changes, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -475,7 +463,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - nil, + "", domain.APIAuthMethodTypePrivateKeyJWT), ), ), @@ -500,8 +488,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "change api app, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -515,12 +502,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -563,7 +545,11 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner) if tt.res.err == nil { @@ -581,14 +567,13 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type args struct { - ctx context.Context - appID string - projectID string - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + appID string + projectID string + resourceOwner string } type res struct { want *domain.APIApp @@ -603,9 +588,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "no projectid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -619,9 +602,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "no appid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -636,8 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "app not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -654,8 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "change secret, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -669,12 +648,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -682,22 +656,16 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { project.NewAPIConfigSecretChangedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, "app1", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), }, args: args{ - ctx: context.Background(), - projectID: "project1", - appID: "app1", - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + ctx: context.Background(), + projectID: "project1", + appID: "app1", + resourceOwner: "org1", }, res: res{ want: &domain.APIApp{ @@ -708,7 +676,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.APIAuthMethodTypeBasic, State: domain.AppStateActive, }, @@ -718,9 +686,13 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -745,3 +717,105 @@ func newAPIAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner ) return event } + +func TestCommands_VerifyAPIClientSecret(t *testing.T) { + hasher := &crypto.Hasher{ + Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)), + } + hashedSecret, err := hasher.Hash("secret") + require.NoError(t, err) + agg := project.NewAggregate("projectID", "orgID") + + tests := []struct { + name string + secret string + eventstore func(*testing.T) *eventstore.Eventstore + wantErr error + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: io.ErrClosedPipe, + }, + { + name: "app not exists", + eventstore: expectEventstore( + expectFilter(), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting"), + }, + { + name: "wrong app type", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI"), + }, + { + name: "no secret set", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", "", domain.APIAuthMethodTypePrivateKeyJWT), + ), + ), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid"), + }, + { + name: "check succeeded", + secret: "secret", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT), + ), + ), + expectPush( + project.NewAPIConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + }, + { + name: "check failed", + secret: "wrong!", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT), + ), + ), + expectPush( + project.NewAPIConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.eventstore(t), + secretHasher: hasher, + } + err := c.VerifyAPIClientSecret(context.Background(), "projectID", "appID", tt.secret) + c.jobs.Wait() + require.ErrorIs(t, err, tt.wantErr) + }) + } +} diff --git a/internal/command/project_application_key_model.go b/internal/command/project_application_key_model.go index 75319e1436..057af9ee98 100644 --- a/internal/command/project_application_key_model.go +++ b/internal/command/project_application_key_model.go @@ -3,9 +3,8 @@ package command import ( "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/project" ) diff --git a/internal/command/project_application_key_test.go b/internal/command/project_application_key_test.go index 907b352a7a..9fd46c75f3 100644 --- a/internal/command/project_application_key_test.go +++ b/internal/command/project_application_key_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -116,12 +116,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -162,12 +157,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go index 0ece1bf364..b4a9eaae9b 100644 --- a/internal/command/project_application_oidc.go +++ b/internal/command/project_application_oidc.go @@ -5,11 +5,8 @@ import ( "strings" "time" - "github.com/zitadel/logging" - http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" project_repo "github.com/zitadel/zitadel/internal/repository/project" @@ -36,12 +33,12 @@ type addOIDCApp struct { SkipSuccessPageForNativeApp bool ClientID string - ClientSecret *crypto.CryptoValue + ClientSecret string ClientSecretPlain string } // AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands -func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { +func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation { return func() (preparation.CreateCommands, error) { if app.ID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument") @@ -77,11 +74,10 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has } if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost { - code, err := c.newAppClientSecret(ctx, filter, clientSecretAlg) + app.ClientSecret, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter) if err != nil { return nil, err } - app.ClientSecret, app.ClientSecretPlain = code.Crypted, code.Plain } return []eventstore.Command{ @@ -118,7 +114,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has } } -func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { +func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) { existingApp, err := c.getOIDCAppWriteModel(ctx, oidcApp.AggregateID, appID, resourceOwner) if err != nil { return nil, err @@ -132,10 +128,10 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound") } - return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator) + return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID) } -func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { +func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) { if oidcApp == nil || oidcApp.AggregateID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid") } @@ -153,11 +149,10 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA return nil, err } - return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator) + return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID) } -func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { - +func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.OIDCApp, err error) { addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner) projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel) @@ -167,12 +162,14 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName), } - var stringPw string + var plain string err = domain.SetNewClientID(oidcApp, c.idGenerator, project) if err != nil { return nil, err } - stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator) + plain, err = domain.SetNewClientSecretIfNeeded(oidcApp, func() (string, string, error) { + return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck + }) if err != nil { return nil, err } @@ -181,7 +178,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain oidcApp.OIDCVersion, oidcApp.AppID, oidcApp.ClientID, - oidcApp.ClientSecret, + oidcApp.EncodedHash, trimStringSliceWhiteSpaces(oidcApp.RedirectUris), oidcApp.ResponseTypes, oidcApp.GrantTypes, @@ -208,7 +205,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain return nil, err } result := oidcWriteModelToOIDCConfig(addedApplication) - result.ClientSecretString = stringPw + result.ClientSecretString = plain result.FillCompliance() return result, nil } @@ -270,7 +267,7 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA return result, nil } -func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.OIDCApp, error) { +func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.OIDCApp, error) { if projectID == "" || appID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") } @@ -285,14 +282,14 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a if !existingOIDC.IsOIDC() { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC") } - cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator) + encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck if err != nil { return nil, err } projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret)) + pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash)) if err != nil { return nil, err } @@ -302,7 +299,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a } result := oidcWriteModelToOIDCConfig(existingOIDC) - result.ClientSecretString = stringPW + result.ClientSecretString = plain return result, err } @@ -315,26 +312,35 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID, return err } if !app.State.Exists() { - return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NotExisting") + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting") } if !app.IsOIDC() { return zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC") } - if app.ClientSecret == nil { + if app.HashedSecret == "" { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid") } projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel) - ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") - err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg) + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := c.secretHasher.Verify(app.HashedSecret, secret) spanPasswordComparison.EndWithError(err) if err == nil { - _, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID)) - return err + c.oidcSecretCheckSucceeded(ctx, projectAgg, appID, updated) + return nil } - _, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) - logging.OnError(err).Error("could not push event OIDCClientSecretCheckFailed") - return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid") + c.oidcSecretCheckFailed(ctx, projectAgg, appID) + return zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid") +} + +func (c *Commands) OIDCSecretCheckSucceeded(ctx context.Context, appID, projectID, resourceOwner, updated string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.oidcSecretCheckSucceeded(ctx, &agg.Aggregate, appID, updated) +} + +func (c *Commands) OIDCSecretCheckFailed(ctx context.Context, appID, projectID, resourceOwner string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.oidcSecretCheckFailed(ctx, &agg.Aggregate, appID) } func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) { @@ -366,3 +372,18 @@ func trimStringSliceWhiteSpaces(slice []string) []string { } return slice } + +func (c *Commands) oidcSecretCheckSucceeded(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) { + cmds := append( + make([]eventstore.Command, 0, 2), + project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, agg, appID), + ) + if updated != "" { + cmds = append(cmds, project_repo.NewOIDCConfigSecretHashUpdatedEvent(ctx, agg, appID, updated)) + } + c.asyncPush(ctx, cmds...) +} + +func (c *Commands) oidcSecretCheckFailed(ctx context.Context, agg *eventstore.Aggregate, appID string) { + c.asyncPush(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, agg, appID)) +} diff --git a/internal/command/project_application_oidc_model.go b/internal/command/project_application_oidc_model.go index 7b70d89554..585fdf6c1d 100644 --- a/internal/command/project_application_oidc_model.go +++ b/internal/command/project_application_oidc_model.go @@ -17,7 +17,7 @@ type OIDCApplicationWriteModel struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + HashedSecret string ClientSecretString string RedirectUris []string ResponseTypes []domain.OIDCResponseType @@ -100,6 +100,11 @@ func (wm *OIDCApplicationWriteModel) AppendEvents(events ...eventstore.Event) { continue } wm.WriteModel.AppendEvents(e) + case *project.OIDCConfigSecretHashUpdatedEvent: + if e.AppID != wm.AppID { + continue + } + wm.WriteModel.AppendEvents(e) case *project.ProjectRemovedEvent: wm.WriteModel.AppendEvents(e) } @@ -131,7 +136,9 @@ func (wm *OIDCApplicationWriteModel) Reduce() error { case *project.OIDCConfigChangedEvent: wm.appendChangeOIDCEvent(e) case *project.OIDCConfigSecretChangedEvent: - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) + case *project.OIDCConfigSecretHashUpdatedEvent: + wm.HashedSecret = e.HashedSecret case *project.ProjectRemovedEvent: wm.State = domain.AppStateRemoved } @@ -142,7 +149,7 @@ func (wm *OIDCApplicationWriteModel) Reduce() error { func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAddedEvent) { wm.oidc = true wm.ClientID = e.ClientID - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) wm.RedirectUris = e.RedirectUris wm.ResponseTypes = e.ResponseTypes wm.GrantTypes = e.GrantTypes @@ -223,8 +230,9 @@ func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { project.OIDCConfigAddedType, project.OIDCConfigChangedType, project.OIDCConfigSecretChangedType, - project.ProjectRemovedType). - Builder() + project.OIDCConfigSecretHashUpdatedType, + project.ProjectRemovedType, + ).Builder() } func (wm *OIDCApplicationWriteModel) NewChangedEvent( diff --git a/internal/command/project_application_oidc_test.go b/internal/command/project_application_oidc_test.go index 55a3f70515..73cb837057 100644 --- a/internal/command/project_application_oidc_test.go +++ b/internal/command/project_application_oidc_test.go @@ -2,10 +2,14 @@ package command import ( "context" + "io" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/passwap" + "github.com/zitadel/passwap/bcrypt" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" @@ -23,9 +27,8 @@ func TestAddOIDCApp(t *testing.T) { idGenerator id.Generator } type args struct { - app *addOIDCApp - clientSecretAlg crypto.HashAlgorithm - filter preparation.FilterToQueryReducer + app *addOIDCApp + filter preparation.FilterToQueryReducer } ctx := context.Background() @@ -156,7 +159,7 @@ func TestAddOIDCApp(t *testing.T) { domain.OIDCVersionV1, "id", "clientID@project", - nil, + "", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -221,7 +224,7 @@ func TestAddOIDCApp(t *testing.T) { domain.OIDCVersionV1, "id", "clientID@project", - nil, + "", nil, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -240,17 +243,85 @@ func TestAddOIDCApp(t *testing.T) { }, }, }, + { + name: "with secret", + fields: fields{ + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "clientID"), + }, + args: args{ + app: &addOIDCApp{ + AddApp: AddApp{ + Aggregate: *agg, + ID: "id", + Name: "name", + }, + GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + Version: domain.OIDCVersionV1, + + ApplicationType: domain.OIDCApplicationTypeWeb, + AuthMethodType: domain.OIDCAuthMethodTypeBasic, + AccessTokenType: domain.OIDCTokenTypeBearer, + }, + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewProjectAddedEvent( + ctx, + &agg.Aggregate, + "project", + false, + false, + false, + domain.PrivateLabelingSettingUnspecified, + ), + }, nil + }). + Filter(), + }, + want: Want{ + Commands: []eventstore.Command{ + project.NewApplicationAddedEvent(ctx, &agg.Aggregate, + "id", + "name", + ), + project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate, + domain.OIDCVersionV1, + "id", + "clientID@project", + "secret", + nil, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypeBasic, + nil, + false, + domain.OIDCTokenTypeBearer, + false, + false, + false, + 0, + nil, + false, + ), + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := Commands{ - idGenerator: tt.fields.idGenerator, + idGenerator: tt.fields.idGenerator, + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } AssertValidation(t, context.Background(), c.AddOIDCAppCommand( tt.args.app, - tt.args.clientSecretAlg, ), tt.args.filter, tt.want) }) } @@ -258,14 +329,13 @@ func TestAddOIDCApp(t *testing.T) { func TestCommandSide_AddOIDCApplication(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator } type args struct { - ctx context.Context - oidcApp *domain.OIDCApp - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + oidcApp *domain.OIDCApp + resourceOwner string } type res struct { want *domain.OIDCApp @@ -280,9 +350,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "no aggregate id, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -296,8 +364,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "project not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -319,8 +386,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "invalid app, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -349,8 +415,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "create oidc app basic using whitespaces in uris, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -370,12 +435,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -418,8 +478,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AdditionalOrigins: []string{" https://sub.test.ch "}, SkipNativeAppSuccessPage: true, }, - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + resourceOwner: "org1", }, res: res{ want: &domain.OIDCApp{ @@ -430,7 +489,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.OIDCAuthMethodTypePost, OIDCVersion: domain.OIDCVersionV1, RedirectUris: []string{"https://test.ch"}, @@ -454,8 +513,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "create oidc app basic, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -475,12 +533,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -523,8 +576,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AdditionalOrigins: []string{"https://sub.test.ch"}, SkipNativeAppSuccessPage: true, }, - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + resourceOwner: "org1", }, res: res{ want: &domain.OIDCApp{ @@ -535,7 +587,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.OIDCAuthMethodTypePost, OIDCVersion: domain.OIDCVersionV1, RedirectUris: []string{"https://test.ch"}, @@ -560,10 +612,14 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -709,12 +765,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -783,12 +834,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -857,12 +903,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -965,14 +1006,13 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type args struct { - ctx context.Context - appID string - projectID string - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + appID string + projectID string + resourceOwner string } type res struct { want *domain.OIDCApp @@ -987,9 +1027,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "no projectid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -1003,9 +1041,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "no appid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -1020,8 +1056,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "app not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -1038,8 +1073,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "change secret, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -1054,12 +1088,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -1081,22 +1110,16 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { project.NewOIDCConfigSecretChangedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, "app1", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), }, args: args{ - ctx: context.Background(), - projectID: "project1", - appID: "app1", - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + ctx: context.Background(), + projectID: "project1", + appID: "app1", + resourceOwner: "org1", }, res: res{ want: &domain.OIDCApp{ @@ -1107,7 +1130,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.OIDCAuthMethodTypePost, OIDCVersion: domain.OIDCVersionV1, RedirectUris: []string{"https://test.ch"}, @@ -1129,11 +1152,15 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(*testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -1165,3 +1192,165 @@ func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner ) return event } + +func TestCommands_VerifyOIDCClientSecret(t *testing.T) { + hasher := &crypto.Hasher{ + Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)), + } + hashedSecret, err := hasher.Hash("secret") + require.NoError(t, err) + agg := project.NewAggregate("projectID", "orgID") + + tests := []struct { + name string + secret string + eventstore func(*testing.T) *eventstore.Eventstore + wantErr error + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: io.ErrClosedPipe, + }, + { + name: "app not exists", + eventstore: expectEventstore( + expectFilter(), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting"), + }, + { + name: "wrong app type", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC"), + }, + { + name: "no secret set", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewOIDCConfigAddedEvent(context.Background(), + &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "client1@project", + "", + []string{"https://test.ch"}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypePost, + []string{"https://test.ch/logout"}, + true, + domain.OIDCTokenTypeBearer, + true, + true, + true, + time.Second*1, + []string{"https://sub.test.ch"}, + false, + ), + ), + ), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid"), + }, + { + name: "check succeeded", + secret: "secret", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewOIDCConfigAddedEvent(context.Background(), + &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "client1@project", + hashedSecret, + []string{"https://test.ch"}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypePost, + []string{"https://test.ch/logout"}, + true, + domain.OIDCTokenTypeBearer, + true, + true, + true, + time.Second*1, + []string{"https://sub.test.ch"}, + false, + ), + ), + ), + expectPush( + project.NewOIDCConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + }, + { + name: "check failed", + secret: "wrong!", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewOIDCConfigAddedEvent(context.Background(), + &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "client1@project", + hashedSecret, + []string{"https://test.ch"}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypePost, + []string{"https://test.ch/logout"}, + true, + domain.OIDCTokenTypeBearer, + true, + true, + true, + time.Second*1, + []string{"https://sub.test.ch"}, + false, + ), + ), + ), + expectPush( + project.NewOIDCConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.eventstore(t), + secretHasher: hasher, + } + err := c.VerifyOIDCClientSecret(context.Background(), "projectID", "appID", tt.secret) + c.jobs.Wait() + require.ErrorIs(t, err, tt.wantErr) + }) + } +} diff --git a/internal/command/restrictions.go b/internal/command/restrictions.go index e47a0e8c02..2e609029c6 100644 --- a/internal/command/restrictions.go +++ b/internal/command/restrictions.go @@ -37,7 +37,7 @@ func (s *SetRestrictions) Validate(defaultLanguage language.Tag) error { return nil } -// SetRestrictions creates new restrictions or updates existing restrictions. +// SetInstanceRestrictions creates new restrictions or updates existing restrictions. func (c *Commands) SetInstanceRestrictions( ctx context.Context, setRestrictions *SetRestrictions, diff --git a/internal/command/session.go b/internal/command/session.go index ae63f3d6ec..f7e0f889cd 100644 --- a/internal/command/session.go +++ b/internal/command/session.go @@ -31,11 +31,11 @@ type SessionCommands struct { eventstore *eventstore.Eventstore eventCommands []eventstore.Command - hasher *crypto.PasswordHasher + hasher *crypto.Hasher intentAlg crypto.EncryptionAlgorithm totpAlg crypto.EncryptionAlgorithm otpAlg crypto.EncryptionAlgorithm - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc createToken func(sessionID string) (id string, token string, err error) now func() time.Time } @@ -49,7 +49,7 @@ func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWri intentAlg: c.idpConfigEncryption, totpAlg: c.multifactors.OTP.CryptoMFA, otpAlg: c.userEncryption, - createCode: c.newCodeWithDefault, + createCode: c.newEncryptedCodeWithDefault, createToken: c.sessionTokenCreator, now: time.Now, } diff --git a/internal/command/session_otp.go b/internal/command/session_otp.go index 8afee5d2d1..859f893400 100644 --- a/internal/command/session_otp.go +++ b/internal/command/session_otp.go @@ -120,7 +120,7 @@ func CheckOTPSMS(code string) SessionCommand { if challenge == nil { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3tv", "Errors.User.Code.NotFound") } - err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) + err = crypto.VerifyCode(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) if err != nil { return err } @@ -138,7 +138,7 @@ func CheckOTPEmail(code string) SessionCommand { if challenge == nil { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-zF3g3", "Errors.User.Code.NotFound") } - err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) + err = crypto.VerifyCode(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) if err != nil { return err } diff --git a/internal/command/session_otp_test.go b/internal/command/session_otp_test.go index f4db8241ce..c5be0aecae 100644 --- a/internal/command/session_otp_test.go +++ b/internal/command/session_otp_test.go @@ -20,7 +20,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -65,7 +65,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ returnCode: "1234567", @@ -122,7 +122,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -166,7 +166,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ commands: []eventstore.Command{ @@ -292,7 +292,7 @@ func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type args struct { urlTmpl string @@ -361,7 +361,7 @@ func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ commands: []eventstore.Command{ @@ -421,7 +421,7 @@ func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -465,7 +465,7 @@ func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ returnCode: "1234567", @@ -523,7 +523,7 @@ func TestCommands_CreateOTPEmailChallenge(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -566,7 +566,7 @@ func TestCommands_CreateOTPEmailChallenge(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ commands: []eventstore.Command{ diff --git a/internal/command/smtp.go b/internal/command/smtp.go index c52dd98f0c..fc51cabd59 100644 --- a/internal/command/smtp.go +++ b/internal/command/smtp.go @@ -15,51 +15,153 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) { - instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) +func (c *Commands) AddSMTPConfig(ctx context.Context, instanceID string, config *smtp.Config) (string, *domain.ObjectDetails, error) { + id, err := c.idGenerator.Next() if err != nil { - return nil, err + return "", nil, err } - events, err := c.eventstore.Push(ctx, cmds...) + + from := strings.TrimSpace(config.From) + if from == "" { + return "", nil, zerrors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument") + } + fromSplitted := strings.Split(from, "@") + senderDomain := fromSplitted[len(fromSplitted)-1] + description := strings.TrimSpace(config.Description) + replyTo := strings.TrimSpace(config.ReplyToAddress) + hostAndPort := strings.TrimSpace(config.SMTP.Host) + + if _, _, err := net.SplitHostPort(hostAndPort); err != nil { + return "", nil, zerrors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument") + } + + var smtpPassword *crypto.CryptoValue + if config.SMTP.Password != "" { + smtpPassword, err = crypto.Encrypt([]byte(config.SMTP.Password), c.smtpEncryption) + if err != nil { + return "", nil, err + } + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, senderDomain) if err != nil { - return nil, err + return "", nil, err } - return &domain.ObjectDetails{ - Sequence: events[len(events)-1].Sequence(), - EventDate: events[len(events)-1].CreatedAt(), - ResourceOwner: events[len(events)-1].Aggregate().InstanceID, - }, nil + + err = checkSenderAddress(smtpConfigWriteModel) + if err != nil { + return "", nil, err + } + + iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) + pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigAddedEvent( + ctx, + iamAgg, + id, + description, + config.Tls, + config.From, + config.FromName, + replyTo, + hostAndPort, + config.SMTP.User, + smtpPassword, + )) + if err != nil { + return "", nil, err + } + + err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + if err != nil { + return "", nil, err + } + return id, writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil } -func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) { - instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, config.Tls) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) +func (c *Commands) ChangeSMTPConfig(ctx context.Context, instanceID string, id string, config *smtp.Config) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-x8vo9", "Errors.IDMissing") + } + + from := strings.TrimSpace(config.From) + if from == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "INST-HSv2d", "Errors.Invalid.Argument") + } + fromSplitted := strings.Split(from, "@") + senderDomain := fromSplitted[len(fromSplitted)-1] + description := strings.TrimSpace(config.Description) + replyTo := strings.TrimSpace(config.ReplyToAddress) + hostAndPort := strings.TrimSpace(config.SMTP.Host) + if _, _, err := net.SplitHostPort(hostAndPort); err != nil { + return nil, zerrors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument") + } + + var smtpPassword *crypto.CryptoValue + var err error + if config.SMTP.Password != "" { + smtpPassword, err = crypto.Encrypt([]byte(config.SMTP.Password), c.smtpEncryption) + if err != nil { + return nil, err + } + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, senderDomain) if err != nil { return nil, err } - events, err := c.eventstore.Push(ctx, cmds...) + + if !smtpConfigWriteModel.State.Exists() { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-7j8gv", "Errors.SMTPConfig.NotFound") + } + + err = checkSenderAddress(smtpConfigWriteModel) if err != nil { return nil, err } - return &domain.ObjectDetails{ - Sequence: events[len(events)-1].Sequence(), - EventDate: events[len(events)-1].CreatedAt(), - ResourceOwner: events[len(events)-1].Aggregate().InstanceID, - }, nil + + iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) + + changedEvent, hasChanged, err := smtpConfigWriteModel.NewChangedEvent( + ctx, + iamAgg, + id, + description, + config.Tls, + from, + config.FromName, + replyTo, + hostAndPort, + config.SMTP.User, + smtpPassword, + ) + if err != nil { + return nil, err + } + if !hasChanged { + return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-lh3op", "Errors.NoChangesFound") + } + + pushedEvents, err := c.eventstore.Push(ctx, changedEvent) + if err != nil { + return nil, err + } + err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil } -func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, password string) (*domain.ObjectDetails, error) { +func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, instanceID, id string, password string) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - smtpConfigWriteModel, err := getSMTPConfigWriteModel(ctx, c.eventstore.Filter, "") + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") if err != nil { return nil, err } if smtpConfigWriteModel.State != domain.SMTPConfigStateActive { return nil, zerrors.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound") } + var smtpPassword *crypto.CryptoValue if password != "" { smtpPassword, err = crypto.Encrypt([]byte(password), c.smtpEncryption) @@ -67,39 +169,144 @@ func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, password string return nil, err } } - events, err := c.eventstore.Push(ctx, instance.NewSMTPConfigPasswordChangedEvent( + + pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigPasswordChangedEvent( ctx, &instanceAgg.Aggregate, + id, smtpPassword)) if err != nil { return nil, err } - return &domain.ObjectDetails{ - Sequence: events[len(events)-1].Sequence(), - EventDate: events[len(events)-1].CreatedAt(), - ResourceOwner: events[len(events)-1].Aggregate().InstanceID, - }, nil -} - -func (c *Commands) RemoveSMTPConfig(ctx context.Context) (*domain.ObjectDetails, error) { - instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - validation := c.prepareRemoveSMTPConfig(instanceAgg) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) + err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) if err != nil { return nil, err } - events, err := c.eventstore.Push(ctx, cmds...) + + return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil +} + +func (c *Commands) ActivateSMTPConfig(ctx context.Context, instanceID, id, activatedId string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-nm56k", "Errors.IDMissing") + } + + if len(activatedId) > 0 { + _, err := c.DeactivateSMTPConfig(ctx, instanceID, activatedId) + if err != nil { + return nil, err + } + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") if err != nil { return nil, err } - return &domain.ObjectDetails{ - Sequence: events[len(events)-1].Sequence(), - EventDate: events[len(events)-1].CreatedAt(), - ResourceOwner: events[len(events)-1].Aggregate().InstanceID, - }, nil + + if !smtpConfigWriteModel.State.Exists() { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-kg8yr", "Errors.SMTPConfig.NotFound") + } + + if smtpConfigWriteModel.State == domain.SMTPConfigStateActive { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-ed3lr", "Errors.SMTPConfig.AlreadyActive") + } + + iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) + pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigActivatedEvent( + ctx, + iamAgg, + id)) + if err != nil { + return nil, err + } + err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil } -func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, replyTo, hostAndPort, user string, password []byte, tls bool) preparation.Validation { +func (c *Commands) DeactivateSMTPConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-98ikl", "Errors.IDMissing") + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") + if err != nil { + return nil, err + } + if !smtpConfigWriteModel.State.Exists() { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-k39PJ", "Errors.SMTPConfig.NotFound") + } + if smtpConfigWriteModel.State == domain.SMTPConfigStateInactive { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-km8g3", "Errors.SMTPConfig.AlreadyDeactivated") + } + + iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) + pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigDeactivatedEvent( + ctx, + iamAgg, + id)) + if err != nil { + return nil, err + } + err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil +} + +func (c *Commands) RemoveSMTPConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-7f5cv", "Errors.IDMissing") + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") + if err != nil { + return nil, err + } + if !smtpConfigWriteModel.State.Exists() { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-kg8rt", "Errors.SMTPConfig.NotFound") + } + + iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) + pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigRemovedEvent( + ctx, + iamAgg, + id)) + if err != nil { + return nil, err + } + err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil +} + +func checkSenderAddress(writeModel *IAMSMTPConfigWriteModel) error { + if !writeModel.smtpSenderAddressMatchesInstanceDomain { + return nil + } + if !writeModel.domainState.Exists() { + return zerrors.ThrowInvalidArgument(nil, "INST-83nl8", "Errors.SMTPConfig.SenderAdressNotCustomDomain") + } + return nil +} + +func (c *Commands) getSMTPConfig(ctx context.Context, instanceID, id, domain string) (writeModel *IAMSMTPConfigWriteModel, err error) { + writeModel = NewIAMSMTPConfigWriteModel(instanceID, id, domain) + err = c.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + + return writeModel, nil +} + +// TODO: SetUpInstance still uses this and would be removed as soon as deprecated PrepareCommands is removed +func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, description, from, name, replyTo, hostAndPort, user string, password []byte, tls bool) preparation.Validation { return func() (preparation.CreateCommands, error) { if from = strings.TrimSpace(from); from == "" { return nil, zerrors.ThrowInvalidArgument(nil, "INST-mruNY", "Errors.Invalid.Argument") @@ -112,9 +319,14 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, reply return nil, zerrors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument") } return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + id, err := c.idGenerator.Next() + if err != nil { + return nil, zerrors.ThrowInternal(nil, "INST-9JdRe", "Errors.Invalid.Argument") + } + fromSplitted := strings.Split(from, "@") senderDomain := fromSplitted[len(fromSplitted)-1] - writeModel, err := getSMTPConfigWriteModel(ctx, filter, senderDomain) + writeModel, err := getSMTPConfigWriteModel(ctx, filter, id, senderDomain) if err != nil { return nil, err } @@ -136,6 +348,8 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, reply instance.NewSMTPConfigAddedEvent( ctx, &a.Aggregate, + id, + description, tls, from, name, @@ -149,83 +363,8 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, reply } } -func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, replyTo, hostAndPort, user string, tls bool) preparation.Validation { - return func() (preparation.CreateCommands, error) { - if from = strings.TrimSpace(from); from == "" { - return nil, zerrors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument") - } - - replyTo = strings.TrimSpace(replyTo) - hostAndPort = strings.TrimSpace(hostAndPort) - if _, _, err := net.SplitHostPort(hostAndPort); err != nil { - return nil, zerrors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument") - } - return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { - fromSplitted := strings.Split(from, "@") - senderDomain := fromSplitted[len(fromSplitted)-1] - writeModel, err := getSMTPConfigWriteModel(ctx, filter, senderDomain) - if err != nil { - return nil, err - } - if writeModel.State != domain.SMTPConfigStateActive { - return nil, zerrors.ThrowNotFound(nil, "INST-Svq1a", "Errors.SMTPConfig.NotFound") - } - err = checkSenderAddress(writeModel) - if err != nil { - return nil, err - } - changedEvent, hasChanged, err := writeModel.NewChangedEvent( - ctx, - &a.Aggregate, - tls, - from, - name, - replyTo, - hostAndPort, - user, - ) - if err != nil { - return nil, err - } - if !hasChanged { - return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound") - } - return []eventstore.Command{ - changedEvent, - }, nil - }, nil - } -} - -func (c *Commands) prepareRemoveSMTPConfig(a *instance.Aggregate) preparation.Validation { - return func() (preparation.CreateCommands, error) { - return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { - writeModel, err := getSMTPConfigWriteModel(ctx, filter, "") - if err != nil { - return nil, err - } - if writeModel.State != domain.SMTPConfigStateActive { - return nil, zerrors.ThrowNotFound(nil, "INST-Sfefg", "Errors.SMTPConfig.NotFound") - } - return []eventstore.Command{ - instance.NewSMTPConfigRemovedEvent(ctx, &a.Aggregate), - }, nil - }, nil - } -} - -func checkSenderAddress(writeModel *InstanceSMTPConfigWriteModel) error { - if !writeModel.smtpSenderAddressMatchesInstanceDomain { - return nil - } - if !writeModel.domainState.Exists() { - return zerrors.ThrowInvalidArgument(nil, "INST-83nl8", "Errors.SMTPConfig.SenderAdressNotCustomDomain") - } - return nil -} - -func getSMTPConfigWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, domain string) (_ *InstanceSMTPConfigWriteModel, err error) { - writeModel := NewInstanceSMTPConfigWriteModel(authz.GetInstance(ctx).InstanceID(), domain) +func getSMTPConfigWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, id, domain string) (_ *IAMSMTPConfigWriteModel, err error) { + writeModel := NewIAMSMTPConfigWriteModel(authz.GetInstance(ctx).InstanceID(), id, domain) events, err := filter(ctx, writeModel.Query()) if err != nil { return nil, err diff --git a/internal/command/smtp_test.go b/internal/command/smtp_test.go index bb46968da6..204614632f 100644 --- a/internal/command/smtp_test.go +++ b/internal/command/smtp_test.go @@ -11,6 +11,8 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/id" + id_mock "github.com/zitadel/zitadel/internal/id/mock" "github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/zerrors" @@ -18,12 +20,14 @@ import ( func TestCommandSide_AddSMTPConfig(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore - alg crypto.EncryptionAlgorithm + eventstore *eventstore.Eventstore + idGenerator id.Generator + alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context - smtp *smtp.Config + ctx context.Context + instanceID string + smtp *smtp.Config } type res struct { want *domain.ObjectDetails @@ -51,13 +55,13 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { ), ), ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + From: "from@domain.ch", SMTP: smtp.SMTP{ Host: "host:587", User: "user", @@ -69,58 +73,6 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { err: zerrors.IsErrorInvalidArgument, }, }, - { - name: "smtp config, error already exists", - fields: fields{ - eventstore: eventstoreExpect( - t, - expectFilter( - eventFromEventPusher( - instance.NewDomainAddedEvent(context.Background(), - &instance.NewAggregate("INSTANCE").Aggregate, - "domain.ch", - false, - ), - ), - eventFromEventPusher( - instance.NewDomainPolicyAddedEvent(context.Background(), - &instance.NewAggregate("INSTANCE").Aggregate, - true, true, false, - ), - ), - eventFromEventPusher( - instance.NewSMTPConfigAddedEvent(context.Background(), - &instance.NewAggregate("INSTANCE").Aggregate, - true, - "from@domain.ch", - "name", - "", - "host:587", - "user", - &crypto.CryptoValue{}, - ), - ), - ), - ), - }, - args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", - ReplyToAddress: "", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - Password: "password", - }, - }, - }, - res: res{ - err: zerrors.IsErrorAlreadyExists, - }, - }, { name: "add smtp config, ok", fields: fields{ @@ -145,6 +97,8 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "configid", + "test", true, "from@domain.ch", "name", @@ -160,14 +114,16 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { ), ), ), - alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "host:587", User: "user", @@ -205,6 +161,8 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "configid", + "test", true, "from@domain.ch", "name", @@ -220,11 +178,13 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { ), ), ), - alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ + Description: "test", Tls: true, From: "from@domain.ch", FromName: "name", @@ -245,14 +205,17 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { { name: "smtp config, port is missing", fields: fields{ - eventstore: eventstoreExpect(t), + eventstore: eventstoreExpect(t), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "host", User: "user", @@ -267,14 +230,17 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { { name: "smtp config, host is empty", fields: fields{ - eventstore: eventstoreExpect(t), + eventstore: eventstoreExpect(t), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: " ", User: "user", @@ -310,6 +276,8 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "configid", + "test", true, "from@domain.ch", "name", @@ -325,14 +293,16 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { ), ), ), - alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "[2001:db8::1]:2525", User: "user", @@ -351,9 +321,10 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, smtpEncryption: tt.fields.alg, } - got, err := r.AddSMTPConfig(tt.args.ctx, tt.args.smtp) + _, got, err := r.AddSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.smtp) if tt.res.err == nil { assert.NoError(t, err) } @@ -372,8 +343,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { eventstore *eventstore.Eventstore } type args struct { - ctx context.Context - smtp *smtp.Config + ctx context.Context + instanceID string + id string + smtp *smtp.Config } type res struct { want *domain.ObjectDetails @@ -385,6 +358,21 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args args res res }{ + { + name: "id empty, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.Config{}, + }, + res: res{ + err: zerrors.IsErrorInvalidArgument, + }, + }, { name: "empty config, invalid argument error", fields: fields{ @@ -395,6 +383,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{}, + id: "configID", }, res: res{ err: zerrors.IsErrorInvalidArgument, @@ -411,14 +400,17 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "host:587", User: "user", }, }, + instanceID: "INSTANCE", + id: "ID", }, res: res{ err: zerrors.IsNotFound, @@ -447,6 +439,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", true, "from@domain.ch", "name", @@ -460,11 +454,14 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + instanceID: "INSTANCE", + id: "ID", smtp: &smtp.Config{ - Tls: true, - From: "from@wrongdomain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@wrongdomain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "host:587", User: "user", @@ -498,6 +495,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", true, "from@domain.ch", "name", @@ -513,14 +512,17 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "host:587", User: "user", }, }, + instanceID: "INSTANCE", + id: "ID", }, res: res{ err: zerrors.IsPreconditionFailed, @@ -549,6 +551,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "", true, "from@domain.ch", "name", @@ -562,6 +566,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { expectPush( newSMTPConfigChangedEvent( context.Background(), + "ID", + "test", false, "from2@domain.ch", "name2", @@ -575,6 +581,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ + Description: "test", Tls: false, From: "from2@domain.ch", FromName: "name2", @@ -584,6 +591,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { User: "user2", }, }, + id: "ID", + instanceID: "INSTANCE", }, res: res{ want: &domain.ObjectDetails{ @@ -599,15 +608,18 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: "host", User: "user", Password: "password", }, }, + instanceID: "INSTANCE", + id: "ID", }, res: res{ err: zerrors.IsErrorInvalidArgument, @@ -621,15 +633,18 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ - Tls: true, - From: "from@domain.ch", - FromName: "name", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", SMTP: smtp.SMTP{ Host: " ", User: "user", Password: "password", }, }, + instanceID: "INSTANCE", + id: "ID", }, res: res{ err: zerrors.IsErrorInvalidArgument, @@ -658,6 +673,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "", true, "from@domain.ch", "name", @@ -671,6 +688,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { expectPush( newSMTPConfigChangedEvent( context.Background(), + "ID", + "test", false, "from2@domain.ch", "name2", @@ -684,6 +703,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.Config{ + Description: "test", Tls: false, From: "from2@domain.ch", FromName: "name2", @@ -693,6 +713,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { User: "user2", }, }, + instanceID: "INSTANCE", + id: "ID", }, res: res{ want: &domain.ObjectDetails{ @@ -706,7 +728,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } - got, err := r.ChangeSMTPConfig(tt.args.ctx, tt.args.smtp) + got, err := r.ChangeSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.smtp) if tt.res.err == nil { assert.NoError(t, err) } @@ -726,8 +748,10 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context - password string + ctx context.Context + instanceID string + id string + password string } type res struct { want *domain.ObjectDetails @@ -750,6 +774,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { args: args{ ctx: context.Background(), password: "", + id: "ID", }, res: res{ err: zerrors.IsNotFound, @@ -765,6 +790,8 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", true, "from", "name", @@ -774,11 +801,19 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { &crypto.CryptoValue{}, ), ), + eventFromEventPusher( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), ), expectPush( instance.NewSMTPConfigPasswordChangedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -791,8 +826,10 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - password: "password", + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + password: "password", + id: "ID", + instanceID: "INSTANCE", }, res: res{ want: &domain.ObjectDetails{ @@ -807,7 +844,246 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { eventstore: tt.fields.eventstore, smtpEncryption: tt.fields.alg, } - got, err := r.ChangeSMTPConfigPassword(tt.args.ctx, tt.args.password) + got, err := r.ChangeSMTPConfigPassword(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.password) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_ActivateSMTPConfig(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + alg crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + instanceID string + id string + activatedId string + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "id empty, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + }, + res: res{ + err: zerrors.IsErrorInvalidArgument, + }, + }, + { + name: "smtp not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + instanceID: "INSTANCE", + id: "id", + }, + res: res{ + err: zerrors.IsNotFound, + }, + }, + { + name: "activate smtp config, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + true, + "from", + "name", + "", + "host:587", + "user", + &crypto.CryptoValue{}, + ), + ), + ), + expectPush( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", + activatedId: "", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + smtpEncryption: tt.fields.alg, + } + got, err := r.ActivateSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.activatedId) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + alg crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + instanceID string + id string + activatedId string + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "id empty, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + }, + res: res{ + err: zerrors.IsErrorInvalidArgument, + }, + }, + { + name: "smtp not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + instanceID: "INSTANCE", + id: "id", + }, + res: res{ + err: zerrors.IsNotFound, + }, + }, + { + name: "deactivate smtp config, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + true, + "from", + "name", + "", + "host:587", + "user", + &crypto.CryptoValue{}, + ), + ), + eventFromEventPusher( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + expectPush( + instance.NewSMTPConfigDeactivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", + activatedId: "", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + smtpEncryption: tt.fields.alg, + } + got, err := r.DeactivateSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id) if tt.res.err == nil { assert.NoError(t, err) } @@ -827,7 +1103,9 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context + ctx context.Context + instanceID string + id string } type res struct { want *domain.ObjectDetails @@ -849,6 +1127,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { }, args: args{ ctx: context.Background(), + id: "ID", }, res: res{ err: zerrors.IsNotFound, @@ -864,6 +1143,8 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { instance.NewSMTPConfigAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", true, "from", "name", @@ -878,12 +1159,15 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { instance.NewSMTPConfigRemovedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, + "ID", ), ), ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", }, res: res{ want: &domain.ObjectDetails{ @@ -898,7 +1182,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { eventstore: tt.fields.eventstore, smtpEncryption: tt.fields.alg, } - got, err := r.RemoveSMTPConfig(tt.args.ctx) + got, err := r.RemoveSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id) if tt.res.err == nil { assert.NoError(t, err) } @@ -912,8 +1196,9 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { } } -func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, replyTo, host, user string) *instance.SMTPConfigChangedEvent { +func newSMTPConfigChangedEvent(ctx context.Context, id, description string, tls bool, fromAddress, fromName, replyTo, host, user string) *instance.SMTPConfigChangedEvent { changes := []instance.SMTPConfigChanges{ + instance.ChangeSMTPConfigDescription(description), instance.ChangeSMTPConfigTLS(tls), instance.ChangeSMTPConfigFromAddress(fromAddress), instance.ChangeSMTPConfigFromName(fromName), @@ -923,6 +1208,7 @@ func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromN } event, _ := instance.NewSMTPConfigChangeEvent(ctx, &instance.NewAggregate("INSTANCE").Aggregate, + id, changes, ) return event diff --git a/internal/command/system_features.go b/internal/command/system_features.go index 70ec66dbe4..838fef5b9d 100644 --- a/internal/command/system_features.go +++ b/internal/command/system_features.go @@ -14,6 +14,7 @@ type SystemFeatures struct { LegacyIntrospection *bool TokenExchange *bool UserSchema *bool + Actions *bool } func (m *SystemFeatures) isEmpty() bool { @@ -21,7 +22,8 @@ func (m *SystemFeatures) isEmpty() bool { m.TriggerIntrospectionProjections == nil && m.LegacyIntrospection == nil && m.UserSchema == nil && - m.TokenExchange == nil + m.TokenExchange == nil && + m.Actions == nil } func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) { diff --git a/internal/command/system_features_model.go b/internal/command/system_features_model.go index 9b223d7f0e..d21aa85cbc 100644 --- a/internal/command/system_features_model.go +++ b/internal/command/system_features_model.go @@ -51,6 +51,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder { feature_v2.SystemLegacyIntrospectionEventType, feature_v2.SystemUserSchemaEventType, feature_v2.SystemTokenExchangeEventType, + feature_v2.SystemActionsEventType, ). Builder().ResourceOwner(m.ResourceOwner) } @@ -61,6 +62,7 @@ func (m *SystemFeaturesWriteModel) reduceReset() { m.LegacyIntrospection = nil m.TokenExchange = nil m.UserSchema = nil + m.Actions = nil } func (m *SystemFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEvent[bool]) error { @@ -81,6 +83,8 @@ func (m *SystemFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEvent[ m.UserSchema = &event.Value case feature.KeyTokenExchange: m.TokenExchange = &event.Value + case feature.KeyActions: + m.Actions = &event.Value } return nil } @@ -93,6 +97,7 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LegacyIntrospection, f.LegacyIntrospection, feature_v2.SystemLegacyIntrospectionEventType) cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.UserSchema, f.UserSchema, feature_v2.SystemUserSchemaEventType) cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.TokenExchange, f.TokenExchange, feature_v2.SystemTokenExchangeEventType) + cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.Actions, f.Actions, feature_v2.SystemActionsEventType) return cmds } diff --git a/internal/command/system_features_test.go b/internal/command/system_features_test.go index e426721e36..83d8c8550b 100644 --- a/internal/command/system_features_test.go +++ b/internal/command/system_features_test.go @@ -117,6 +117,24 @@ func TestCommands_SetSystemFeatures(t *testing.T) { ResourceOwner: "SYSTEM", }, }, + { + name: "set Actions", + eventstore: expectEventstore( + expectFilter(), + expectPush( + feature_v2.NewSetEvent[bool]( + context.Background(), aggregate, + feature_v2.SystemActionsEventType, true, + ), + ), + ), + args: args{context.Background(), &SystemFeatures{ + Actions: gu.Ptr(true), + }}, + want: &domain.ObjectDetails{ + ResourceOwner: "SYSTEM", + }, + }, { name: "push error", eventstore: expectEventstore( @@ -154,6 +172,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) { context.Background(), aggregate, feature_v2.SystemUserSchemaEventType, true, ), + feature_v2.NewSetEvent[bool]( + context.Background(), aggregate, + feature_v2.SystemActionsEventType, true, + ), ), ), args: args{context.Background(), &SystemFeatures{ @@ -161,6 +183,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) { TriggerIntrospectionProjections: gu.Ptr(false), LegacyIntrospection: gu.Ptr(true), UserSchema: gu.Ptr(true), + Actions: gu.Ptr(true), }}, want: &domain.ObjectDetails{ ResourceOwner: "SYSTEM", @@ -205,6 +228,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) { context.Background(), aggregate, feature_v2.SystemUserSchemaEventType, true, ), + feature_v2.NewSetEvent[bool]( + context.Background(), aggregate, + feature_v2.SystemActionsEventType, false, + ), ), ), args: args{context.Background(), &SystemFeatures{ @@ -212,6 +239,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) { TriggerIntrospectionProjections: gu.Ptr(false), LegacyIntrospection: gu.Ptr(true), UserSchema: gu.Ptr(true), + Actions: gu.Ptr(false), }}, want: &domain.ObjectDetails{ ResourceOwner: "SYSTEM", diff --git a/internal/command/user.go b/internal/command/user.go index 6d22b212e0..9253da5719 100644 --- a/internal/command/user.go +++ b/internal/command/user.go @@ -476,8 +476,8 @@ func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id return exists, nil } -func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg) +func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg) } func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) { diff --git a/internal/command/user_grant.go b/internal/command/user_grant.go index bc22e1af1f..f2a8a3ddbe 100644 --- a/internal/command/user_grant.go +++ b/internal/command/user_grant.go @@ -4,12 +4,11 @@ import ( "context" "reflect" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/zerrors" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/usergrant" "github.com/zitadel/zitadel/internal/telemetry/tracing" + "github.com/zitadel/zitadel/internal/zerrors" ) func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (_ *domain.UserGrant, err error) { diff --git a/internal/command/user_human.go b/internal/command/user_human.go index f8993ae6b4..a0cfff5bd3 100644 --- a/internal/command/user_human.go +++ b/internal/command/user_human.go @@ -12,6 +12,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/repository/user" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -77,7 +78,7 @@ type AddLink struct { IDPExternalID string } -func (h *AddHuman) Validate(hasher *crypto.PasswordHasher) (err error) { +func (h *AddHuman) Validate(hasher *crypto.Hasher) (err error) { if err := h.Email.Validate(); err != nil { return err } @@ -164,7 +165,7 @@ type humanCreationCommand interface { } //nolint:gocognit -func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.PasswordHasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { +func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.Hasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { return func() (_ preparation.CreateCommands, err error) { if err := human.Validate(hasher); err != nil { return nil, err @@ -329,17 +330,19 @@ func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparatio return nil } -func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.PasswordHasher) (err error) { +func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.Hasher) (err error) { if human.Password != "" { if err = humanValidatePassword(ctx, filter, human.Password); err != nil { return err } - secret, err := hasher.Hash(human.Password) + _, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash") + encodedHash, err := hasher.Hash(human.Password) + spanHash.EndWithError(err) if err != nil { return err } - createCmd.AddPasswordData(secret, human.PasswordChangeRequired) + createCmd.AddPasswordData(encodedHash, human.PasswordChangeRequired) return nil } @@ -589,7 +592,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain. human.EnsureDisplayName() if human.Password != nil { - if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil { + if err := human.HashPasswordIfExisting(ctx, pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil { return nil, nil, err } } diff --git a/internal/command/user_human_access_token_model.go b/internal/command/user_human_access_token_model.go index 5256e81c0f..9a4c6cd2c8 100644 --- a/internal/command/user_human_access_token_model.go +++ b/internal/command/user_human_access_token_model.go @@ -3,9 +3,8 @@ package command import ( "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_human_email.go b/internal/command/user_human_email.go index c98b396dbf..4607b1294e 100644 --- a/internal/command/user_human_email.go +++ b/internal/command/user_human_email.go @@ -81,7 +81,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) - err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator.Alg()) if err == nil { pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailVerifiedEvent(ctx, userAgg)) if err != nil { diff --git a/internal/command/user_human_init.go b/internal/command/user_human_init.go index af807486d4..f3ec27a199 100644 --- a/internal/command/user_human_init.go +++ b/internal/command/user_human_init.go @@ -67,7 +67,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) - err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator.Alg()) if err != nil { _, err = c.eventstore.Push(ctx, user.NewHumanInitializedCheckFailedEvent(ctx, userAgg)) logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed") diff --git a/internal/command/user_human_init_model.go b/internal/command/user_human_init_model.go index 3ee4801bf4..c1276ec7ca 100644 --- a/internal/command/user_human_init_model.go +++ b/internal/command/user_human_init_model.go @@ -4,10 +4,9 @@ import ( "context" "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_human_init_test.go b/internal/command/user_human_init_test.go index eeffcf7201..d89671d768 100644 --- a/internal/command/user_human_init_test.go +++ b/internal/command/user_human_init_test.go @@ -292,7 +292,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) { func TestCommandSide_VerifyInitCode(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go index 38f360649d..fd717c0de4 100644 --- a/internal/command/user_human_otp.go +++ b/internal/command/user_human_otp.go @@ -158,14 +158,37 @@ func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resource return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady") } userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) - err = domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA) - if err == nil { + verifyErr := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA) + + // recheck for additional events (failed OTP checks or locks) + recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP) + if recheckErr != nil { + return recheckErr + } + if existingOTP.UserLocked { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked") + } + + // the OTP check succeeded and the user was not locked in the meantime + if verifyErr == nil { _, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) return err } - _, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + + // the OTP check failed, therefore check if the limit was reached and the user must additionally be locked + commands := make([]eventstore.Command, 0, 2) + commands = append(commands, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner) + if err != nil { + return err + } + if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts { + commands = append(commands, user.NewUserLockedEvent(ctx, userAgg)) + } + + _, pushErr := c.eventstore.Push(ctx, commands...) logging.OnError(pushErr).Error("error create password check failed event") - return err + return verifyErr } func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) { @@ -455,7 +478,7 @@ func (c *Commands) sendHumanOTP( if !existingOTP.OTPAdded() { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady") } - config, err := secretGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) + config, err := cryptoGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) //nolint:staticcheck if err != nil { return err } @@ -515,14 +538,37 @@ func (c *Commands) humanCheckOTP( return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound") } userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate - err = crypto.VerifyCodeWithAlgorithm(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) - if err == nil { + verifyErr := crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) + + // recheck for additional events (failed OTP checks or locks) + recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP) + if recheckErr != nil { + return recheckErr + } + if existingOTP.UserLocked() { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked") + } + + // the OTP check succeeded and the user was not locked in the meantime + if verifyErr == nil { _, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) return err } - _, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + + // the OTP check failed, therefore check if the limit was reached and the user must additionally be locked + commands := make([]eventstore.Command, 0, 2) + commands = append(commands, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner) + if err != nil { + return err + } + if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts { + commands = append(commands, user.NewUserLockedEvent(ctx, userAgg)) + } + + _, pushErr := c.eventstore.Push(ctx, commands...) logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed") - return err + return verifyErr } func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) { diff --git a/internal/command/user_human_otp_model.go b/internal/command/user_human_otp_model.go index 25bd4c9ef5..43abc14349 100644 --- a/internal/command/user_human_otp_model.go +++ b/internal/command/user_human_otp_model.go @@ -12,8 +12,10 @@ import ( type HumanTOTPWriteModel struct { eventstore.WriteModel - State domain.MFAState - Secret *crypto.CryptoValue + State domain.MFAState + Secret *crypto.CryptoValue + CheckFailedCount uint64 + UserLocked bool } func NewHumanTOTPWriteModel(userID, resourceOwner string) *HumanTOTPWriteModel { @@ -33,6 +35,16 @@ func (wm *HumanTOTPWriteModel) Reduce() error { wm.State = domain.MFAStateNotReady case *user.HumanOTPVerifiedEvent: wm.State = domain.MFAStateReady + wm.CheckFailedCount = 0 + case *user.HumanOTPCheckSucceededEvent: + wm.CheckFailedCount = 0 + case *user.HumanOTPCheckFailedEvent: + wm.CheckFailedCount++ + case *user.UserLockedEvent: + wm.UserLocked = true + case *user.UserUnlockedEvent: + wm.CheckFailedCount = 0 + wm.UserLocked = false case *user.HumanOTPRemovedEvent: wm.State = domain.MFAStateRemoved case *user.UserRemovedEvent: @@ -50,6 +62,10 @@ func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes(user.HumanMFAOTPAddedType, user.HumanMFAOTPVerifiedType, user.HumanMFAOTPRemovedType, + user.HumanMFAOTPCheckSucceededType, + user.HumanMFAOTPCheckFailedType, + user.UserLockedType, + user.UserUnlockedType, user.UserRemovedType, user.UserV1MFAOTPAddedType, user.UserV1MFAOTPVerifiedType, @@ -72,6 +88,9 @@ type OTPCodeWriteModel interface { CodeCreationDate() time.Time CodeExpiry() time.Duration Code() *crypto.CryptoValue + CheckFailedCount() uint64 + UserLocked() bool + eventstore.QueryReducer } type HumanOTPSMSWriteModel struct { @@ -141,6 +160,9 @@ type HumanOTPSMSCodeWriteModel struct { code *crypto.CryptoValue codeCreationDate time.Time codeExpiry time.Duration + + checkFailedCount uint64 + userLocked bool } func (wm *HumanOTPSMSCodeWriteModel) CodeCreationDate() time.Time { @@ -155,6 +177,14 @@ func (wm *HumanOTPSMSCodeWriteModel) Code() *crypto.CryptoValue { return wm.code } +func (wm *HumanOTPSMSCodeWriteModel) CheckFailedCount() uint64 { + return wm.checkFailedCount +} + +func (wm *HumanOTPSMSCodeWriteModel) UserLocked() bool { + return wm.userLocked +} + func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCodeWriteModel { return &HumanOTPSMSCodeWriteModel{ HumanOTPSMSWriteModel: NewHumanOTPSMSWriteModel(userID, resourceOwner), @@ -163,10 +193,20 @@ func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCode func (wm *HumanOTPSMSCodeWriteModel) Reduce() error { for _, event := range wm.Events { - if e, ok := event.(*user.HumanOTPSMSCodeAddedEvent); ok { + switch e := event.(type) { + case *user.HumanOTPSMSCodeAddedEvent: wm.code = e.Code wm.codeCreationDate = e.CreationDate() wm.codeExpiry = e.Expiry + case *user.HumanOTPSMSCheckSucceededEvent: + wm.checkFailedCount = 0 + case *user.HumanOTPSMSCheckFailedEvent: + wm.checkFailedCount++ + case *user.UserLockedEvent: + wm.userLocked = true + case *user.UserUnlockedEvent: + wm.checkFailedCount = 0 + wm.userLocked = false } } return wm.HumanOTPSMSWriteModel.Reduce() @@ -179,6 +219,10 @@ func (wm *HumanOTPSMSCodeWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateIDs(wm.AggregateID). EventTypes( user.HumanOTPSMSCodeAddedType, + user.HumanOTPSMSCheckSucceededType, + user.HumanOTPSMSCheckFailedType, + user.UserLockedType, + user.UserUnlockedType, user.HumanPhoneVerifiedType, user.HumanOTPSMSAddedType, user.HumanOTPSMSRemovedType, @@ -259,6 +303,9 @@ type HumanOTPEmailCodeWriteModel struct { code *crypto.CryptoValue codeCreationDate time.Time codeExpiry time.Duration + + checkFailedCount uint64 + userLocked bool } func (wm *HumanOTPEmailCodeWriteModel) CodeCreationDate() time.Time { @@ -273,6 +320,14 @@ func (wm *HumanOTPEmailCodeWriteModel) Code() *crypto.CryptoValue { return wm.code } +func (wm *HumanOTPEmailCodeWriteModel) CheckFailedCount() uint64 { + return wm.checkFailedCount +} + +func (wm *HumanOTPEmailCodeWriteModel) UserLocked() bool { + return wm.userLocked +} + func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmailCodeWriteModel { return &HumanOTPEmailCodeWriteModel{ HumanOTPEmailWriteModel: NewHumanOTPEmailWriteModel(userID, resourceOwner), @@ -281,10 +336,20 @@ func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmail func (wm *HumanOTPEmailCodeWriteModel) Reduce() error { for _, event := range wm.Events { - if e, ok := event.(*user.HumanOTPEmailCodeAddedEvent); ok { + switch e := event.(type) { + case *user.HumanOTPEmailCodeAddedEvent: wm.code = e.Code wm.codeCreationDate = e.CreationDate() wm.codeExpiry = e.Expiry + case *user.HumanOTPEmailCheckSucceededEvent: + wm.checkFailedCount = 0 + case *user.HumanOTPEmailCheckFailedEvent: + wm.checkFailedCount++ + case *user.UserLockedEvent: + wm.userLocked = true + case *user.UserUnlockedEvent: + wm.checkFailedCount = 0 + wm.userLocked = false } } return wm.HumanOTPEmailWriteModel.Reduce() @@ -297,6 +362,10 @@ func (wm *HumanOTPEmailCodeWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateIDs(wm.AggregateID). EventTypes( user.HumanOTPEmailCodeAddedType, + user.HumanOTPEmailCheckSucceededType, + user.HumanOTPEmailCheckFailedType, + user.UserLockedType, + user.UserUnlockedType, user.HumanEmailVerifiedType, user.HumanOTPEmailAddedType, user.HumanOTPEmailRemovedType, diff --git a/internal/command/user_human_otp_test.go b/internal/command/user_human_otp_test.go index 490751610d..838e2357c6 100644 --- a/internal/command/user_human_otp_test.go +++ b/internal/command/user_human_otp_test.go @@ -1671,6 +1671,15 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { ), ), ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 3, 3, true, + ), + ), + ), expectPush( user.NewHumanOTPSMSCheckFailedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -1707,6 +1716,86 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), }, }, + { + name: "invalid code, max attempts reached, error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPSMSAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPSMSCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("other-code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 1, 1, true, + ), + ), + ), + expectPush( + user.NewHumanOTPSMSCheckFailedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), + }, + }, { name: "code ok", fields: fields{ @@ -1739,6 +1828,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { ), ), ), + expectFilter(), // recheck expectPush( user.NewHumanOTPSMSCheckSucceededEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -1777,6 +1867,65 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { }, }, }, + { + name: "code ok, locked in the meantime", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPSMSAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPSMSCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter( // recheck + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -2616,6 +2765,15 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { ), ), ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 3, 3, true, + ), + ), + ), expectPush( user.NewHumanOTPEmailCheckFailedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -2652,6 +2810,86 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), }, }, + { + name: "invalid code, max attempts reached, error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPEmailAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPEmailCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("other-code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 1, 1, true, + ), + ), + ), + expectPush( + user.NewHumanOTPEmailCheckFailedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), + }, + }, { name: "code ok", fields: fields{ @@ -2684,6 +2922,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { ), ), ), + expectFilter(), // recheck expectPush( user.NewHumanOTPEmailCheckSucceededEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -2722,6 +2961,65 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { }, }, }, + { + name: "code ok, locked in the meantime", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPEmailAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPEmailCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter( // recheck + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/command/user_human_password.go b/internal/command/user_human_password.go index 3c8282409d..8831bff907 100644 --- a/internal/command/user_human_password.go +++ b/internal/command/user_human_password.go @@ -53,7 +53,7 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound") } - err = crypto.VerifyCodeWithAlgorithm(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption) + err = crypto.VerifyCode(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption) if err != nil { return nil, err } diff --git a/internal/command/user_human_password_model.go b/internal/command/user_human_password_model.go index ec4d11da24..ce23769812 100644 --- a/internal/command/user_human_password_model.go +++ b/internal/command/user_human_password_model.go @@ -3,10 +3,9 @@ package command import ( "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) @@ -37,11 +36,11 @@ func (wm *HumanPasswordWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { case *user.HumanAddedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanRegisteredEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanInitialCodeAddedEvent: @@ -49,7 +48,7 @@ func (wm *HumanPasswordWriteModel) Reduce() error { case *user.HumanInitializedCheckSucceededEvent: wm.UserState = domain.UserStateActive case *user.HumanPasswordChangedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.Code = nil wm.PasswordCheckFailedCount = 0 diff --git a/internal/command/user_human_password_test.go b/internal/command/user_human_password_test.go index 970033a2f8..0c2b21b3d8 100644 --- a/internal/command/user_human_password_test.go +++ b/internal/command/user_human_password_test.go @@ -23,7 +23,7 @@ import ( func TestCommandSide_SetOneTimePassword(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher checkPermission domain.PermissionCheck } type args struct { @@ -270,7 +270,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore userEncryption crypto.EncryptionAlgorithm - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -598,7 +598,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) { func TestCommandSide_ChangePassword(t *testing.T) { type fields struct { - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -1202,7 +1202,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) { func TestCommandSide_CheckPassword(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -1643,6 +1643,7 @@ func TestCommandSide_CheckPassword(t *testing.T) { }, lockoutPolicy: &domain.LockoutPolicy{ MaxPasswordAttempts: 1, + MaxOTPAttempts: 1, }, }, res: res{ diff --git a/internal/command/user_human_phone.go b/internal/command/user_human_phone.go index 67a25f9bcf..7003c0530c 100644 --- a/internal/command/user_human_phone.go +++ b/internal/command/user_human_phone.go @@ -79,7 +79,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) - err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator.Alg()) if err == nil { pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg)) if err != nil { @@ -115,7 +115,7 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, if existingPhone.IsPhoneVerified { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified") } - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } diff --git a/internal/command/user_human_phone_model.go b/internal/command/user_human_phone_model.go index 0bbe16497c..ddac65f4bb 100644 --- a/internal/command/user_human_phone_model.go +++ b/internal/command/user_human_phone_model.go @@ -4,10 +4,9 @@ import ( "context" "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_human_profile_model.go b/internal/command/user_human_profile_model.go index de462dfdf5..608ac13ca3 100644 --- a/internal/command/user_human_profile_model.go +++ b/internal/command/user_human_profile_model.go @@ -3,11 +3,10 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/eventstore" - "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_human_refresh_token_model.go b/internal/command/user_human_refresh_token_model.go index e731ad2f6b..45ba54e73d 100644 --- a/internal/command/user_human_refresh_token_model.go +++ b/internal/command/user_human_refresh_token_model.go @@ -3,9 +3,8 @@ package command import ( "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go index 009c0ec994..689d8419b9 100644 --- a/internal/command/user_human_test.go +++ b/internal/command/user_human_test.go @@ -28,9 +28,9 @@ func TestCommandSide_AddHuman(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher codeAlg crypto.EncryptionAlgorithm - newCode cryptoCodeFunc + newCode encrypedCodeFunc } type args struct { ctx context.Context @@ -245,7 +245,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -312,7 +312,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -380,7 +380,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -450,7 +450,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -521,7 +521,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -593,7 +593,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -996,7 +996,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("phonecode", time.Hour), + newCode: mockEncryptedCode("phonecode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1061,7 +1061,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1136,7 +1136,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1204,7 +1204,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1242,7 +1242,7 @@ func TestCommandSide_AddHuman(t *testing.T) { userPasswordHasher: tt.fields.userPasswordHasher, userEncryption: tt.fields.codeAlg, idGenerator: tt.fields.idGenerator, - newCode: tt.fields.newCode, + newEncryptedCode: tt.fields.newCode, } err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail) if tt.res.err == nil { @@ -1266,7 +1266,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -2483,7 +2483,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -4328,7 +4328,7 @@ func TestAddHumanCommand(t *testing.T) { type args struct { human *AddHuman orgID string - hasher *crypto.PasswordHasher + hasher *crypto.Hasher filter preparation.FilterToQueryReducer codeAlg crypto.EncryptionAlgorithm allowInitMail bool diff --git a/internal/command/user_human_webauthn.go b/internal/command/user_human_webauthn.go index 3328e95455..5588ed7286 100644 --- a/internal/command/user_human_webauthn.go +++ b/internal/command/user_human_webauthn.go @@ -575,7 +575,7 @@ func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID, if err != nil { return err } - err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator) + err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator.Alg()) if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive { userAgg := UserAggregateFromWriteModel(&initCode.WriteModel) _, err = c.eventstore.Push(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID)) diff --git a/internal/command/user_machine_key_model.go b/internal/command/user_machine_key_model.go index 9220a7f875..d9ba628658 100644 --- a/internal/command/user_machine_key_model.go +++ b/internal/command/user_machine_key_model.go @@ -3,9 +3,8 @@ package command import ( "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_machine_model.go b/internal/command/user_machine_model.go index 7fea305a5f..b7dfb02d32 100644 --- a/internal/command/user_machine_model.go +++ b/internal/command/user_machine_model.go @@ -4,9 +4,8 @@ import ( "context" "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) @@ -19,8 +18,7 @@ type MachineWriteModel struct { Description string UserState domain.UserState AccessTokenType domain.OIDCTokenType - - ClientSecret *crypto.CryptoValue + HashedSecret string } func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel { @@ -72,9 +70,11 @@ func (wm *MachineWriteModel) Reduce() error { case *user.UserRemovedEvent: wm.UserState = domain.UserStateDeleted case *user.MachineSecretSetEvent: - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) case *user.MachineSecretRemovedEvent: - wm.ClientSecret = nil + wm.HashedSecret = "" + case *user.MachineSecretHashUpdatedEvent: + wm.HashedSecret = e.HashedSecret } } return wm.WriteModel.Reduce() @@ -95,8 +95,9 @@ func (wm *MachineWriteModel) Query() *eventstore.SearchQueryBuilder { user.UserReactivatedType, user.UserRemovedType, user.MachineSecretSetType, - user.MachineSecretRemovedType). - Builder() + user.MachineSecretRemovedType, + user.MachineSecretHashUpdatedType, + ).Builder() } func (wm *MachineWriteModel) NewChangedEvent( diff --git a/internal/command/user_machine_secret.go b/internal/command/user_machine_secret.go index fbfd63441f..3349fc90a5 100644 --- a/internal/command/user_machine_secret.go +++ b/internal/command/user_machine_secret.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" @@ -15,9 +14,9 @@ type GenerateMachineSecret struct { ClientSecret string } -func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, generator crypto.Generator, set *GenerateMachineSecret) (*domain.ObjectDetails, error) { +func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, set *GenerateMachineSecret) (*domain.ObjectDetails, error) { agg := user.NewAggregate(userID, resourceOwner) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareGenerateMachineSecret(agg, generator, set)) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareGenerateMachineSecret(agg, set)) //nolint:staticcheck if err != nil { return nil, err } @@ -34,7 +33,7 @@ func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, res }, nil } -func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator, set *GenerateMachineSecret) preparation.Validation { +func (c *Commands) prepareGenerateMachineSecret(a *user.Aggregate, set *GenerateMachineSecret) preparation.Validation { return func() (_ preparation.CreateCommands, err error) { if a.ResourceOwner == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing") @@ -50,15 +49,14 @@ func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator, if !isUserStateExists(writeModel.UserState) { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.NotExisting") } - - clientSecret, secretString, err := domain.NewMachineClientSecret(generator) + encodedHash, plain, err := c.newHashedSecret(ctx, filter) if err != nil { return nil, err } - set.ClientSecret = secretString + set.ClientSecret = plain return []eventstore.Command{ - user.NewMachineSecretSetEvent(ctx, &a.Aggregate, clientSecret), + user.NewMachineSecretSetEvent(ctx, &a.Aggregate, encodedHash), }, nil }, nil } @@ -99,7 +97,7 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation { if !isUserStateExists(writeModel.UserState) { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x7s802", "Errors.User.NotExisting") } - if writeModel.ClientSecret == nil { + if writeModel.HashedSecret == "" { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-coi82n", "Errors.User.Machine.Secret.NotExisting") } return []eventstore.Command{ @@ -109,9 +107,16 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation { } } -func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner string) { +func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner, updated string) { agg := user.NewAggregate(userID, resourceOwner) - c.asyncPush(ctx, user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate)) + cmds := append( + make([]eventstore.Command, 0, 2), + user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate), + ) + if updated != "" { + cmds = append(cmds, user.NewMachineSecretHashUpdatedEvent(ctx, &agg.Aggregate, updated)) + } + c.asyncPush(ctx, cmds...) } func (c *Commands) MachineSecretCheckFailed(ctx context.Context, userID, resourceOwner string) { diff --git a/internal/command/user_machine_secret_test.go b/internal/command/user_machine_secret_test.go index ee3d869391..ee58b42d82 100644 --- a/internal/command/user_machine_secret_test.go +++ b/internal/command/user_machine_secret_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" @@ -17,13 +16,12 @@ import ( func TestCommandSide_GenerateMachineSecret(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type args struct { ctx context.Context userID string resourceOwner string - generator crypto.Generator set *GenerateMachineSecret } type res struct { @@ -40,15 +38,12 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "user invalid, invalid argument error userID", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), userID: "", resourceOwner: "org1", - generator: GetMockSecretGenerator(t), set: nil, }, res: res{ @@ -58,15 +53,12 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "user invalid, invalid argument error resourceowner", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), userID: "user1", resourceOwner: "", - generator: GetMockSecretGenerator(t), set: nil, }, res: res{ @@ -76,8 +68,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "user not existing, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -85,7 +76,6 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { ctx: context.Background(), userID: "user1", resourceOwner: "org1", - generator: GetMockSecretGenerator(t), set: nil, }, res: res{ @@ -95,8 +85,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "add machine secret, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewMachineAddedEvent(context.Background(), @@ -112,12 +101,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { expectPush( user.NewMachineSecretSetEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), @@ -126,7 +110,6 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { ctx: context.Background(), userID: "user1", resourceOwner: "org1", - generator: GetMockSecretGenerator(t), set: &GenerateMachineSecret{}, }, res: res{ @@ -134,7 +117,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { ResourceOwner: "org1", }, secret: &GenerateMachineSecret{ - ClientSecret: "a", + ClientSecret: "secret", }, }, }, @@ -142,9 +125,13 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.generator, tt.args.set) + got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.set) if tt.res.err == nil { assert.NoError(t, err) } @@ -274,12 +261,7 @@ func TestCommandSide_RemoveMachineSecret(t *testing.T) { eventFromEventPusher( user.NewMachineSecretSetEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), @@ -333,7 +315,7 @@ func TestCommands_MachineSecretCheckSucceeded(t *testing.T) { expectPushSlow(time.Second/100, cmd), ), } - c.MachineSecretCheckSucceeded(ctx, "userID", "orgID") + c.MachineSecretCheckSucceeded(ctx, "userID", "orgID", "") require.NoError(t, c.Close(ctx)) } diff --git a/internal/command/user_model.go b/internal/command/user_model.go index 7edfacfebf..10c6dd2cf8 100644 --- a/internal/command/user_model.go +++ b/internal/command/user_model.go @@ -1,9 +1,8 @@ package command import ( - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_personal_access_token_model.go b/internal/command/user_personal_access_token_model.go index 8053965a4b..7f19b98e4f 100644 --- a/internal/command/user_personal_access_token_model.go +++ b/internal/command/user_personal_access_token_model.go @@ -3,9 +3,8 @@ package command import ( "time" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/command/user_v2_email.go b/internal/command/user_v2_email.go index 6dec15e2b2..9053b577dd 100644 --- a/internal/command/user_v2_email.go +++ b/internal/command/user_v2_email.go @@ -76,7 +76,7 @@ func (c *Commands) ChangeUserEmailVerified(ctx context.Context, userID, email st } func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck if err != nil { return nil, err } @@ -85,7 +85,7 @@ func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email st } func (c *Commands) resendUserEmailCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck if err != nil { return nil, err } @@ -152,7 +152,7 @@ func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, u } func (c *Commands) VerifyUserEmail(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck if err != nil { return nil, err } @@ -262,7 +262,7 @@ func (c *UserEmailEvents) VerifyCode(ctx context.Context, code string, gen crypt return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty") } - err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen) + err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen.Alg()) if err == nil { c.events = append(c.events, user.NewHumanEmailVerifiedEvent(ctx, c.aggregate)) return nil diff --git a/internal/command/user_v2_human.go b/internal/command/user_v2_human.go index 524f90a83b..ab05492b51 100644 --- a/internal/command/user_v2_human.go +++ b/internal/command/user_v2_human.go @@ -51,7 +51,7 @@ type Password struct { ChangeRequired bool } -func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) { +func (h *ChangeHuman) Validate(hasher *crypto.Hasher) (err error) { if h.Email != nil && h.Email.Address != "" { if err := h.Email.Validate(); err != nil { return err @@ -72,7 +72,7 @@ func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) { return nil } -func (p *Password) Validate(hasher *crypto.PasswordHasher) error { +func (p *Password) Validate(hasher *crypto.Hasher) error { if p.EncodedPasswordHash != nil { if !hasher.EncodingSupported(*p.EncodedPasswordHash) { return zerrors.ThrowInvalidArgument(nil, "USER-oz74onzvqr", "Errors.User.Password.NotSupported") @@ -373,7 +373,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com // Either have a code to set the password if password.PasswordCode != nil { - if err := crypto.VerifyCodeWithAlgorithm(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil { + if err := crypto.VerifyCode(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil { return cmds, err } } diff --git a/internal/command/user_v2_human_test.go b/internal/command/user_v2_human_test.go index e0f99034bb..32a838b27e 100644 --- a/internal/command/user_v2_human_test.go +++ b/internal/command/user_v2_human_test.go @@ -25,8 +25,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher - newCode cryptoCodeFunc + userPasswordHasher *crypto.Hasher + newCode encrypedCodeFunc checkPermission domain.PermissionCheck } type args struct { @@ -247,7 +247,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -283,7 +283,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckNotAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -349,7 +349,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -420,7 +420,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -492,7 +492,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -565,7 +565,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -974,7 +974,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("phonecode", time.Hour), + newCode: mockEncryptedCode("phonecode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1040,7 +1040,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1116,7 +1116,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1185,7 +1185,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1223,7 +1223,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { eventstore: tt.fields.eventstore(t), userPasswordHasher: tt.fields.userPasswordHasher, idGenerator: tt.fields.idGenerator, - newCode: tt.fields.newCode, + newEncryptedCode: tt.fields.newCode, checkPermission: tt.fields.checkPermission, } err := r.AddUserHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail, tt.args.codeAlg) @@ -1247,8 +1247,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) { func TestCommandSide_ChangeUserHuman(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher - newCode cryptoCodeFunc + userPasswordHasher *crypto.Hasher + newCode encrypedCodeFunc checkPermission domain.PermissionCheck } type args struct { @@ -1562,7 +1562,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1741,7 +1741,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1791,7 +1791,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1939,7 +1939,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -2546,7 +2546,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore(t), userPasswordHasher: tt.fields.userPasswordHasher, - newCode: tt.fields.newCode, + newEncryptedCode: tt.fields.newCode, checkPermission: tt.fields.checkPermission, } err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg) diff --git a/internal/command/user_v2_model.go b/internal/command/user_v2_model.go index 381a463884..d31c3e6676 100644 --- a/internal/command/user_v2_model.go +++ b/internal/command/user_v2_model.go @@ -266,7 +266,7 @@ func (wm *UserV2WriteModel) Reduce() error { case *user.HumanPasswordCheckSucceededEvent: wm.PasswordCheckFailedCount = 0 case *user.HumanPasswordChangedEvent: - wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordChangeRequired = e.ChangeRequired wm.EmptyPasswordCode() case *user.HumanPasswordCodeAddedEvent: @@ -470,7 +470,7 @@ func (wm *UserV2WriteModel) reduceHumanAddedEvent(e *user.HumanAddedEvent) { wm.Email = e.EmailAddress wm.Phone = e.PhoneNumber wm.UserState = domain.UserStateActive - wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordChangeRequired = e.ChangeRequired } @@ -485,7 +485,7 @@ func (wm *UserV2WriteModel) reduceHumanRegisteredEvent(e *user.HumanRegisteredEv wm.Email = e.EmailAddress wm.Phone = e.PhoneNumber wm.UserState = domain.UserStateActive - wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordChangeRequired = e.ChangeRequired } diff --git a/internal/command/user_v2_passkey.go b/internal/command/user_v2_passkey.go index 698b805d76..4d42d105dd 100644 --- a/internal/command/user_v2_passkey.go +++ b/internal/command/user_v2_passkey.go @@ -48,7 +48,7 @@ func (c *Commands) verifyUserPasskeyCode(ctx context.Context, userID, resourceOw if err != nil { return nil, err } - err = verifyCryptoCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) + err = verifyEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) //nolint:staticcheck if err != nil || wm.State != domain.PasswordlessInitCodeStateActive { c.verifyUserPasskeyCodeFailed(ctx, wm) return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid") @@ -156,6 +156,6 @@ func (c *Commands) addUserPasskeyCode(ctx context.Context, userID, resourceOwner }, nil } -func (c *Commands) newPasskeyCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) +func (c *Commands) newPasskeyCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) } diff --git a/internal/command/user_v2_passkey_test.go b/internal/command/user_v2_passkey_test.go index 1e972bbfd6..15286bea11 100644 --- a/internal/command/user_v2_passkey_test.go +++ b/internal/command/user_v2_passkey_test.go @@ -138,7 +138,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) { es := eventstoreExpect(t, expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), ) - code, err := newCryptoCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) + code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck require.NoError(t, err) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { @@ -236,7 +236,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) { es := eventstoreExpect(t, expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), ) - code, err := newCryptoCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) + code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck require.NoError(t, err) userAgg := &user.NewAggregate("user1", "org1").Aggregate @@ -457,7 +457,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator } @@ -475,7 +475,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: mockCode("passkey1", time.Hour), + newCode: mockEncryptedCode("passkey1", time.Hour), eventstore: expectEventstore(), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -488,7 +488,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: expectEventstore( expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -530,9 +530,9 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore(t), - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg) require.ErrorIs(t, err, tt.wantErr) @@ -546,7 +546,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore *eventstore.Eventstore idGenerator id.Generator } @@ -565,7 +565,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { { name: "template error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), }, args: args{ @@ -578,7 +578,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -592,7 +592,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -638,9 +638,9 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCodeURLTemplate(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, tt.args.urlTmpl) require.ErrorIs(t, err, tt.wantErr) @@ -653,7 +653,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore *eventstore.Eventstore idGenerator id.Generator } @@ -671,7 +671,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -684,7 +684,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -730,9 +730,9 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCodeReturn(context.Background(), tt.args.userID, tt.args.resourceOwner, alg) require.ErrorIs(t, err, tt.wantErr) @@ -745,7 +745,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore *eventstore.Eventstore idGenerator id.Generator } @@ -763,7 +763,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -776,7 +776,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "crypto error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"), }, @@ -789,7 +789,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "filter query error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), expectFilterError(io.ErrClosedPipe), @@ -805,7 +805,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "push error", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -844,7 +844,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -890,9 +890,9 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, } got, err := c.addUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, "", false) require.ErrorIs(t, err, tt.wantErr) diff --git a/internal/command/user_v2_password.go b/internal/command/user_v2_password.go index d94bc0286c..3cddf956a1 100644 --- a/internal/command/user_v2_password.go +++ b/internal/command/user_v2_password.go @@ -55,7 +55,7 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu return nil, nil, err } } - code, err := c.newCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) + code, err := c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck if err != nil { return nil, nil, err } diff --git a/internal/command/user_v2_password_test.go b/internal/command/user_v2_password_test.go index 0b72c49220..b07fbb3de7 100644 --- a/internal/command/user_v2_password_test.go +++ b/internal/command/user_v2_password_test.go @@ -330,7 +330,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { checkPermission domain.PermissionCheck eventstore func(t *testing.T) *eventstore.Eventstore userEncryption crypto.EncryptionAlgorithm - newCode cryptoCodeFunc + newCode encrypedCodeFunc } type args struct { ctx context.Context @@ -452,7 +452,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -492,7 +492,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -533,7 +533,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -575,7 +575,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -593,10 +593,10 @@ func TestCommands_requestPasswordReset(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - checkPermission: tt.fields.checkPermission, - eventstore: tt.fields.eventstore(t), - userEncryption: tt.fields.userEncryption, - newCode: tt.fields.newCode, + checkPermission: tt.fields.checkPermission, + eventstore: tt.fields.eventstore(t), + userEncryption: tt.fields.userEncryption, + newEncryptedCode: tt.fields.newCode, } got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType) require.ErrorIs(t, err, tt.res.err) diff --git a/internal/command/user_v2_phone.go b/internal/command/user_v2_phone.go index d2582b17c8..b53946c22e 100644 --- a/internal/command/user_v2_phone.go +++ b/internal/command/user_v2_phone.go @@ -55,7 +55,7 @@ func (c *Commands) ResendUserPhoneCodeReturnCode(ctx context.Context, userID str } func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } @@ -64,7 +64,7 @@ func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone st } func (c *Commands) resendUserPhoneCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (c *Commands) resendUserPhoneCodeWithGenerator(ctx context.Context, userID } func (c *Commands) VerifyUserPhone(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } @@ -216,7 +216,7 @@ func (c *UserPhoneEvents) VerifyCode(ctx context.Context, code string, gen crypt return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty") } - err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen) + err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen.Alg()) if err == nil { c.events = append(c.events, user.NewHumanPhoneVerifiedEvent(ctx, c.aggregate)) return nil diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 012c37ac07..1d89115c8e 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -8,7 +8,8 @@ import ( type SystemDefaults struct { SecretGenerators SecretGenerators - PasswordHasher crypto.PasswordHashConfig + PasswordHasher crypto.HashConfig + SecretHasher crypto.HashConfig Multifactors MultifactorConfig DomainVerification DomainVerification Notifications Notifications @@ -16,7 +17,6 @@ type SystemDefaults struct { } type SecretGenerators struct { - PasswordSaltCost int MachineKeySize uint32 ApplicationKeySize uint32 } diff --git a/internal/crypto/aes_test.go b/internal/crypto/aes_test.go index 87af88d0be..5731f320eb 100644 --- a/internal/crypto/aes_test.go +++ b/internal/crypto/aes_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -//TODO: refactor test style +// TODO: refactor test style func TestDecrypt_OK(t *testing.T) { encryptedpw, err := EncryptAESString("ThisIsMySecretPw", "passphrasewhichneedstobe32bytes!") assert.NoError(t, err) diff --git a/internal/crypto/bcrypt.go b/internal/crypto/bcrypt.go deleted file mode 100644 index d9b172478f..0000000000 --- a/internal/crypto/bcrypt.go +++ /dev/null @@ -1,27 +0,0 @@ -package crypto - -import ( - "golang.org/x/crypto/bcrypt" -) - -var _ HashAlgorithm = (*BCrypt)(nil) - -type BCrypt struct { - cost int -} - -func NewBCrypt(cost int) *BCrypt { - return &BCrypt{cost: cost} -} - -func (b *BCrypt) Algorithm() string { - return "bcrypt" -} - -func (b *BCrypt) Hash(value []byte) ([]byte, error) { - return bcrypt.GenerateFromPassword(value, b.cost) -} - -func (b *BCrypt) CompareHash(hashed, value []byte) error { - return bcrypt.CompareHashAndPassword(hashed, value) -} diff --git a/internal/crypto/code.go b/internal/crypto/code.go index 2c67c39cd6..d017a974f2 100644 --- a/internal/crypto/code.go +++ b/internal/crypto/code.go @@ -26,7 +26,7 @@ type GeneratorConfig struct { type Generator interface { Length() uint Expiry() time.Duration - Alg() Crypto + Alg() EncryptionAlgorithm Runes() []rune } @@ -53,7 +53,7 @@ type encryptionGenerator struct { alg EncryptionAlgorithm } -func (g *encryptionGenerator) Alg() Crypto { +func (g *encryptionGenerator) Alg() EncryptionAlgorithm { return g.alg } @@ -64,22 +64,30 @@ func NewEncryptionGenerator(config GeneratorConfig, algorithm EncryptionAlgorith } } -type hashGenerator struct { +type HashGenerator struct { generator - alg HashAlgorithm + hasher *Hasher } -func (g *hashGenerator) Alg() Crypto { - return g.alg -} - -func NewHashGenerator(config GeneratorConfig, algorithm HashAlgorithm) Generator { - return &hashGenerator{ +func NewHashGenerator(config GeneratorConfig, hasher *Hasher) *HashGenerator { + return &HashGenerator{ newGenerator(config), - algorithm, + hasher, } } +func (g *HashGenerator) NewCode() (encoded, plain string, err error) { + plain, err = GenerateRandomString(g.Length(), g.Runes()) + if err != nil { + return "", "", err + } + encoded, err = g.hasher.Hash(plain) + if err != nil { + return "", "", err + } + return encoded, plain, nil +} + func newGenerator(config GeneratorConfig) generator { var runes []rune if config.IncludeLowerLetters { @@ -120,21 +128,11 @@ func IsCodeExpired(creationDate time.Time, expiry time.Duration) bool { return creationDate.Add(expiry).Before(time.Now().UTC()) } -func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, g Generator) error { - return VerifyCodeWithAlgorithm(creationDate, expiry, cryptoCode, verificationCode, g.Alg()) -} - -func VerifyCodeWithAlgorithm(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, algorithm Crypto) error { +func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, algorithm EncryptionAlgorithm) error { if IsCodeExpired(creationDate, expiry) { return zerrors.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "Errors.User.Code.Expired") } - switch alg := algorithm.(type) { - case EncryptionAlgorithm: - return verifyEncryptedCode(cryptoCode, verificationCode, alg) - case HashAlgorithm: - return verifyHashedCode(cryptoCode, verificationCode, alg) - } - return zerrors.ThrowInvalidArgument(nil, "CODE-fW2gNa", "Errors.User.Code.GeneratorAlgNotSupported") + return verifyEncryptedCode(cryptoCode, verificationCode, algorithm) } func GenerateRandomString(length uint, chars []rune) (string, error) { @@ -173,10 +171,3 @@ func verifyEncryptedCode(cryptoCode *CryptoValue, verificationCode string, alg E } return nil } - -func verifyHashedCode(cryptoCode *CryptoValue, verificationCode string, alg HashAlgorithm) error { - if cryptoCode == nil { - return zerrors.ThrowInvalidArgument(nil, "CRYPT-2q3r", "cryptoCode must not be nil") - } - return CompareHash(cryptoCode, []byte(verificationCode), alg) -} diff --git a/internal/crypto/code_mock.go b/internal/crypto/code_mock.go index 2a353fb4f9..3ed342c7d4 100644 --- a/internal/crypto/code_mock.go +++ b/internal/crypto/code_mock.go @@ -5,6 +5,7 @@ // // mockgen -source code.go -destination ./code_mock.go -package crypto // + // Package crypto is a generated GoMock package. package crypto @@ -39,10 +40,10 @@ func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder { } // Alg mocks base method. -func (m *MockGenerator) Alg() Crypto { +func (m *MockGenerator) Alg() EncryptionAlgorithm { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Alg") - ret0, _ := ret[0].(Crypto) + ret0, _ := ret[0].(EncryptionAlgorithm) return ret0 } diff --git a/internal/crypto/code_mocker.go b/internal/crypto/code_mocker.go index 66071ccab4..59cece2af2 100644 --- a/internal/crypto/code_mocker.go +++ b/internal/crypto/code_mocker.go @@ -60,32 +60,13 @@ func createMockEncryptionAlgorithm(ctrl *gomock.Controller, encryptFunction func return mCrypto } -func CreateMockHashAlg(ctrl *gomock.Controller) HashAlgorithm { - mCrypto := NewMockHashAlgorithm(ctrl) - mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash") - mCrypto.EXPECT().Hash(gomock.Any()).AnyTimes().DoAndReturn( - func(code []byte) ([]byte, error) { - return code, nil - }, - ) - mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( - func(hashed, comparer []byte) error { - if string(hashed) != string(comparer) { - return zerrors.ThrowInternal(nil, "id", "invalid") - } - return nil - }, - ) - return mCrypto -} - -func createMockCrypto(t *testing.T) Crypto { - mCrypto := NewMockCrypto(gomock.NewController(t)) +func createMockCrypto(t *testing.T) EncryptionAlgorithm { + mCrypto := NewMockEncryptionAlgorithm(gomock.NewController(t)) mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto") return mCrypto } -func createMockGenerator(t *testing.T, crypto Crypto) Generator { +func createMockGenerator(t *testing.T, crypto EncryptionAlgorithm) Generator { mGenerator := NewMockGenerator(gomock.NewController(t)) mGenerator.EXPECT().Alg().AnyTimes().Return(crypto) return mGenerator diff --git a/internal/crypto/code_test.go b/internal/crypto/code_test.go index 2b4e3d36c9..e766871a30 100644 --- a/internal/crypto/code_test.go +++ b/internal/crypto/code_test.go @@ -102,25 +102,10 @@ func TestVerifyCode(t *testing.T) { }, false, }, - { - "hash alg ok", - args{ - creationDate: time.Now(), - expiry: 5 * time.Minute, - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash", - Crypted: []byte("code"), - }, - verificationCode: "code", - g: createMockGenerator(t, CreateMockHashAlg(gomock.NewController(t))), - }, - false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g); (err != nil) != tt.wantErr { + if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g.Alg()); (err != nil) != tt.wantErr { t.Errorf("VerifyCode() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -222,85 +207,3 @@ func Test_verifyEncryptedCode(t *testing.T) { }) } } - -func Test_verifyHashedCode(t *testing.T) { - type args struct { - cryptoCode *CryptoValue - verificationCode string - alg HashAlgorithm - } - tests := []struct { - name string - args args - wantErr bool - }{ - - { - "nil error", - args{ - cryptoCode: nil, - verificationCode: "", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "wrong cryptotype error", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeEncryption, - Crypted: nil, - }, - verificationCode: "", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "wrong algorithm error", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash2", - Crypted: nil, - }, - verificationCode: "", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "wrong verification code error", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash", - Crypted: []byte("code"), - }, - verificationCode: "wrong", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "verification code ok", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash", - Crypted: []byte("code"), - }, - verificationCode: "code", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := verifyHashedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr { - t.Errorf("verifyHashedCode() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index 7a4907100c..2e8e4a71b0 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -13,12 +13,8 @@ const ( TypeHash // Depcrecated: use [passwap.Swapper] instead ) -type Crypto interface { - Algorithm() string -} - type EncryptionAlgorithm interface { - Crypto + Algorithm() string EncryptionKeyID() string DecryptionKeyIDs() []string Encrypt(value []byte) ([]byte, error) @@ -26,13 +22,6 @@ type EncryptionAlgorithm interface { DecryptString(hashed []byte, keyID string) (string, error) } -// Depcrecated: use [passwap.Swapper] instead -type HashAlgorithm interface { - Crypto - Hash(value []byte) ([]byte, error) - CompareHash(hashed, comparer []byte) error -} - type CryptoValue struct { CryptoType CryptoType Algorithm string @@ -59,14 +48,8 @@ func (c *CryptoValue) Scan(src interface{}) error { type CryptoType int -func Crypt(value []byte, c Crypto) (*CryptoValue, error) { - switch alg := c.(type) { - case EncryptionAlgorithm: - return Encrypt(value, alg) - case HashAlgorithm: - return Hash(value, alg) - } - return nil, zerrors.ThrowInternal(nil, "CRYPT-r4IaHZ", "algorithm not supported") +func Crypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) { + return Encrypt(value, alg) } func Encrypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) { @@ -108,36 +91,6 @@ func checkEncryptionAlgorithm(value *CryptoValue, alg EncryptionAlgorithm) error return zerrors.ThrowInvalidArgument(nil, "CRYPT-Kq12vn", "value was encrypted with a different key") } -func Hash(value []byte, alg HashAlgorithm) (*CryptoValue, error) { - hashed, err := alg.Hash(value) - if err != nil { - return nil, zerrors.ThrowInternal(err, "CRYPT-rBVaJU", "error hashing value") - } - return &CryptoValue{ - CryptoType: TypeHash, - Algorithm: alg.Algorithm(), - Crypted: hashed, - }, nil -} - -func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error { - if value == nil { - return zerrors.ThrowPreconditionFailed(nil, "CRYPT-Zei4o", "Errors.Project.App.ClientSecretNotSet") - } - if value.Algorithm != alg.Algorithm() { - return zerrors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hashed with a different algorithm") - } - return alg.CompareHash(value.Crypted, comparer) -} - -func FillHash(value []byte, alg HashAlgorithm) *CryptoValue { - return &CryptoValue{ - CryptoType: TypeHash, - Algorithm: alg.Algorithm(), - Crypted: value, - } -} - func CheckToken(alg EncryptionAlgorithm, token string, content string) error { if token == "" { return zerrors.ThrowPermissionDenied(nil, "CRYPTO-Sfefs", "Errors.Intent.InvalidToken") @@ -155,3 +108,12 @@ func CheckToken(alg EncryptionAlgorithm, token string, content string) error { } return nil } + +// SecretOrEncodedHash returns the Crypted value from legacy [CryptoValue] if it is not nil. +// otherwise it will returns the encoded hash string. +func SecretOrEncodedHash(secret *CryptoValue, encoded string) string { + if secret != nil { + return string(secret.Crypted) + } + return encoded +} diff --git a/internal/crypto/crypto_mock.go b/internal/crypto/crypto_mock.go index 60795e4b25..4f2adea475 100644 --- a/internal/crypto/crypto_mock.go +++ b/internal/crypto/crypto_mock.go @@ -5,6 +5,7 @@ // // mockgen -source crypto.go -destination ./crypto_mock.go -package crypto // + // Package crypto is a generated GoMock package. package crypto @@ -14,43 +15,6 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockCrypto is a mock of Crypto interface. -type MockCrypto struct { - ctrl *gomock.Controller - recorder *MockCryptoMockRecorder -} - -// MockCryptoMockRecorder is the mock recorder for MockCrypto. -type MockCryptoMockRecorder struct { - mock *MockCrypto -} - -// NewMockCrypto creates a new mock instance. -func NewMockCrypto(ctrl *gomock.Controller) *MockCrypto { - mock := &MockCrypto{ctrl: ctrl} - mock.recorder = &MockCryptoMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockCrypto) EXPECT() *MockCryptoMockRecorder { - return m.recorder -} - -// Algorithm mocks base method. -func (m *MockCrypto) Algorithm() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Algorithm") - ret0, _ := ret[0].(string) - return ret0 -} - -// Algorithm indicates an expected call of Algorithm. -func (mr *MockCryptoMockRecorder) Algorithm() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockCrypto)(nil).Algorithm)) -} - // MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface. type MockEncryptionAlgorithm struct { ctrl *gomock.Controller @@ -160,69 +124,3 @@ func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID)) } - -// MockHashAlgorithm is a mock of HashAlgorithm interface. -type MockHashAlgorithm struct { - ctrl *gomock.Controller - recorder *MockHashAlgorithmMockRecorder -} - -// MockHashAlgorithmMockRecorder is the mock recorder for MockHashAlgorithm. -type MockHashAlgorithmMockRecorder struct { - mock *MockHashAlgorithm -} - -// NewMockHashAlgorithm creates a new mock instance. -func NewMockHashAlgorithm(ctrl *gomock.Controller) *MockHashAlgorithm { - mock := &MockHashAlgorithm{ctrl: ctrl} - mock.recorder = &MockHashAlgorithmMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHashAlgorithm) EXPECT() *MockHashAlgorithmMockRecorder { - return m.recorder -} - -// Algorithm mocks base method. -func (m *MockHashAlgorithm) Algorithm() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Algorithm") - ret0, _ := ret[0].(string) - return ret0 -} - -// Algorithm indicates an expected call of Algorithm. -func (mr *MockHashAlgorithmMockRecorder) Algorithm() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockHashAlgorithm)(nil).Algorithm)) -} - -// CompareHash mocks base method. -func (m *MockHashAlgorithm) CompareHash(hashed, comparer []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CompareHash", hashed, comparer) - ret0, _ := ret[0].(error) - return ret0 -} - -// CompareHash indicates an expected call of CompareHash. -func (mr *MockHashAlgorithmMockRecorder) CompareHash(hashed, comparer any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareHash", reflect.TypeOf((*MockHashAlgorithm)(nil).CompareHash), hashed, comparer) -} - -// Hash mocks base method. -func (m *MockHashAlgorithm) Hash(value []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Hash", value) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Hash indicates an expected call of Hash. -func (mr *MockHashAlgorithmMockRecorder) Hash(value any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHashAlgorithm)(nil).Hash), value) -} diff --git a/internal/crypto/crypto_test.go b/internal/crypto/crypto_test.go index 3fd42e53c0..c5b4d17dde 100644 --- a/internal/crypto/crypto_test.go +++ b/internal/crypto/crypto_test.go @@ -60,7 +60,7 @@ func (a *alg) Algorithm() string { func TestCrypt(t *testing.T) { type args struct { value []byte - c Crypto + c EncryptionAlgorithm } tests := []struct { name string @@ -74,18 +74,6 @@ func TestCrypt(t *testing.T) { &CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, false, }, - { - "hash", - args{[]byte("test"), &mockHashCrypto{}}, - &CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, - false, - }, - { - "wrong type", - args{[]byte("test"), &alg{}}, - nil, - true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -208,71 +196,3 @@ func TestDecryptString(t *testing.T) { }) } } - -func TestHash(t *testing.T) { - type args struct { - value []byte - c HashAlgorithm - } - tests := []struct { - name string - args args - want *CryptoValue - wantErr bool - }{ - { - "ok", - args{[]byte("test"), &mockHashCrypto{}}, - &CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Hash(tt.args.value, tt.args.c) - if (err != nil) != tt.wantErr { - t.Errorf("Hash() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Hash() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCompareHash(t *testing.T) { - type args struct { - value *CryptoValue - comparer []byte - c HashAlgorithm - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "ok", - args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test"), &mockHashCrypto{}}, - false, - }, - { - "wrong", - args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test"), &mockHashCrypto{}}, - false, - }, - { - "nil", - args{nil, []byte("test2"), &mockHashCrypto{}}, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := CompareHash(tt.args.value, tt.args.comparer, tt.args.c); (err != nil) != tt.wantErr { - t.Errorf("CompareHash() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/crypto/passwap.go b/internal/crypto/passwap.go index 280fbe4d86..986019c5ba 100644 --- a/internal/crypto/passwap.go +++ b/internal/crypto/passwap.go @@ -16,12 +16,12 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -type PasswordHasher struct { +type Hasher struct { *passwap.Swapper Prefixes []string } -func (h *PasswordHasher) EncodingSupported(encodedHash string) bool { +func (h *Hasher) EncodingSupported(encodedHash string) bool { for _, prefix := range h.Prefixes { if strings.HasPrefix(encodedHash, prefix) { return true @@ -54,12 +54,12 @@ const ( HashModeSHA512 HashMode = "sha512" ) -type PasswordHashConfig struct { +type HashConfig struct { Verifiers []HashName Hasher HasherConfig } -func (c *PasswordHashConfig) PasswordHasher() (*PasswordHasher, error) { +func (c *HashConfig) NewHasher() (*Hasher, error) { verifiers, vPrefixes, err := c.buildVerifiers() if err != nil { return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-sahW9", "password hash config invalid") @@ -68,7 +68,7 @@ func (c *PasswordHashConfig) PasswordHasher() (*PasswordHasher, error) { if err != nil { return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-Que4r", "password hash config invalid") } - return &PasswordHasher{ + return &Hasher{ Swapper: passwap.NewSwapper(hasher, verifiers...), Prefixes: append(hPrefixes, vPrefixes...), }, nil @@ -105,7 +105,7 @@ var knowVerifiers = map[HashName]prefixVerifier{ }, } -func (c *PasswordHashConfig) buildVerifiers() (verifiers []verifier.Verifier, prefixes []string, err error) { +func (c *HashConfig) buildVerifiers() (verifiers []verifier.Verifier, prefixes []string, err error) { verifiers = make([]verifier.Verifier, len(c.Verifiers)) prefixes = make([]string, 0, len(c.Verifiers)+1) for i, name := range c.Verifiers { diff --git a/internal/crypto/passwap_test.go b/internal/crypto/passwap_test.go index 38ca0d5d3d..cbc7202501 100644 --- a/internal/crypto/passwap_test.go +++ b/internal/crypto/passwap_test.go @@ -49,7 +49,7 @@ func TestPasswordHasher_EncodingSupported(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - h := &PasswordHasher{ + h := &Hasher{ Prefixes: []string{bcrypt.Prefix, argon2.Prefix}, } got := h.EncodingSupported(tt.encodedHash) @@ -340,11 +340,11 @@ func TestPasswordHashConfig_PasswordHasher(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &PasswordHashConfig{ + c := &HashConfig{ Verifiers: tt.fields.Verifiers, Hasher: tt.fields.Hasher, } - got, err := c.PasswordHasher() + got, err := c.NewHasher() if tt.wantErr { assert.Error(t, err) return diff --git a/internal/domain/application_api.go b/internal/domain/application_api.go index 06d5dc7e64..8e4f30a783 100644 --- a/internal/domain/application_api.go +++ b/internal/domain/application_api.go @@ -11,7 +11,7 @@ type APIApp struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + EncodedHash string ClientSecretString string AuthMethodType APIAuthMethodType @@ -41,21 +41,21 @@ func (a *APIApp) setClientID(clientID string) { a.ClientID = clientID } -func (a *APIApp) setClientSecret(clientSecret *crypto.CryptoValue) { - a.ClientSecret = clientSecret +func (a *APIApp) setClientSecret(encodedHash string) { + a.EncodedHash = encodedHash } func (a *APIApp) requiresClientSecret() bool { return a.AuthMethodType == APIAuthMethodTypeBasic } -func (a *APIApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (secret string, err error) { +func (a *APIApp) GenerateClientSecretIfNeeded(generator *crypto.HashGenerator) (plain string, err error) { if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT { return "", nil } - a.ClientSecret, secret, err = NewClientSecret(generator) + a.EncodedHash, plain, err = generator.NewCode() if err != nil { return "", err } - return secret, nil + return plain, nil } diff --git a/internal/domain/application_oauth.go b/internal/domain/application_oauth.go index 816eba4fda..0f2179d534 100644 --- a/internal/domain/application_oauth.go +++ b/internal/domain/application_oauth.go @@ -4,16 +4,12 @@ import ( "fmt" "strings" - "github.com/zitadel/logging" - - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/id" - "github.com/zitadel/zitadel/internal/zerrors" ) type oAuthApplication interface { setClientID(clientID string) - setClientSecret(secret *crypto.CryptoValue) + setClientSecret(encodedHash string) requiresClientSecret() bool } @@ -37,23 +33,14 @@ func NewClientID(idGenerator id.Generator, projectName string) (string, error) { return fmt.Sprintf("%s@%s", rndID, strings.ReplaceAll(strings.ToLower(projectName), " ", "_")), nil } -func SetNewClientSecretIfNeeded(a oAuthApplication, generator crypto.Generator) (string, error) { +func SetNewClientSecretIfNeeded(a oAuthApplication, generate func() (encodedHash, plain string, err error)) (string, error) { if !a.requiresClientSecret() { return "", nil } - clientSecret, secretString, err := NewClientSecret(generator) + encodedHash, plain, err := generate() if err != nil { return "", err } - a.setClientSecret(clientSecret) - return secretString, nil -} - -func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) { - cryptoValue, stringSecret, err := crypto.NewCode(generator) - if err != nil { - logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret") - return nil, "", zerrors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret") - } - return cryptoValue, stringSecret, nil + a.setClientSecret(encodedHash) + return plain, nil } diff --git a/internal/domain/application_oidc.go b/internal/domain/application_oidc.go index 0f981197f0..9fe526d684 100644 --- a/internal/domain/application_oidc.go +++ b/internal/domain/application_oidc.go @@ -5,7 +5,6 @@ import ( "time" http_util "github.com/zitadel/zitadel/internal/api/http" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/eventstore/v1/models" ) @@ -28,7 +27,7 @@ type OIDCApp struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + EncodedHash string ClientSecretString string RedirectUris []string ResponseTypes []OIDCResponseType @@ -62,8 +61,8 @@ func (a *OIDCApp) setClientID(clientID string) { a.ClientID = clientID } -func (a *OIDCApp) setClientSecret(clientSecret *crypto.CryptoValue) { - a.ClientSecret = clientSecret +func (a *OIDCApp) setClientSecret(encodedHash string) { + a.EncodedHash = encodedHash } func (a *OIDCApp) requiresClientSecret() bool { diff --git a/internal/domain/custom_login_text.go b/internal/domain/custom_login_text.go index 63a5599eb8..b8f4173b34 100644 --- a/internal/domain/custom_login_text.go +++ b/internal/domain/custom_login_text.go @@ -264,6 +264,12 @@ const ( LoginKeyRegisterOrgSaveButtonText = LoginKeyRegistrationOrg + "SaveButtonText" LoginKeyRegisterOrgBackButtonText = LoginKeyRegistrationOrg + "BackButtonText" + LoginKeyLinkingUserPrompt = "LinkingUserPrompt." + LoginKeyLinkingUserPromptTitle = LoginKeyLinkingUserPrompt + "Title" + LoginKeyLinkingUserPromptDescription = LoginKeyLinkingUserPrompt + "Description" + LoginKeyLinkingUserPromptLinkButtonText = LoginKeyLinkingUserPrompt + "LinkButtonText" + LoginKeyLinkingUserPromptOtherButtonText = LoginKeyLinkingUserPrompt + "OtherButtonText" + LoginKeyLinkingUserDone = "LinkingUsersDone." LoginKeyLinkingUserDoneTitle = LoginKeyLinkingUserDone + "Title" LoginKeyLinkingUserDoneDescription = LoginKeyLinkingUserDone + "Description" @@ -336,6 +342,7 @@ type CustomLoginText struct { RegistrationUser RegistrationUserScreenText ExternalRegistrationUserOverview ExternalRegistrationUserOverviewScreenText RegistrationOrg RegistrationOrgScreenText + LinkingUserPrompt LinkingUserPromptScreenText LinkingUsersDone LinkingUserDoneScreenText ExternalNotFound ExternalUserNotFoundScreenText LoginSuccess SuccessLoginScreenText @@ -607,6 +614,13 @@ type RegistrationOrgScreenText struct { SaveButtonText string } +type LinkingUserPromptScreenText struct { + Title string + Description string + LinkButtonText string + OtherButtonText string +} + type LinkingUserDoneScreenText struct { Title string Description string diff --git a/internal/domain/feature.go b/internal/domain/feature.go index 47f58c9912..ffd4e8e689 100644 --- a/internal/domain/feature.go +++ b/internal/domain/feature.go @@ -1,5 +1,3 @@ -//go:generate enumer -type Feature - package domain type Feature int diff --git a/internal/domain/human.go b/internal/domain/human.go index 6c2ec4daa0..d81aed1f71 100644 --- a/internal/domain/human.go +++ b/internal/domain/human.go @@ -4,6 +4,8 @@ import ( "strings" "time" + "golang.org/x/net/context" + "github.com/zitadel/zitadel/internal/crypto" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/zerrors" @@ -102,10 +104,10 @@ func (u *Human) EnsureDisplayName() { u.DisplayName = u.Username } -func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hasher *crypto.PasswordHasher, onetime bool) error { +func (u *Human) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher, onetime bool) error { if u.Password != nil { u.Password.ChangeRequired = onetime - return u.Password.HashPasswordIfExisting(policy, hasher) + return u.Password.HashPasswordIfExisting(ctx, policy, hasher) } return nil } diff --git a/internal/domain/human_password.go b/internal/domain/human_password.go index 779ed4dba7..3afa0826cf 100644 --- a/internal/domain/human_password.go +++ b/internal/domain/human_password.go @@ -1,10 +1,12 @@ package domain import ( + "context" "time" "github.com/zitadel/zitadel/internal/crypto" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -30,7 +32,7 @@ type PasswordCode struct { NotificationType NotificationType } -func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hasher *crypto.PasswordHasher) error { +func (p *Password) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher) error { if p.SecretString == "" { return nil } @@ -40,7 +42,9 @@ func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hash if err := policy.Check(p.SecretString); err != nil { return err } + _, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash") encoded, err := hasher.Hash(p.SecretString) + spanHash.EndWithError(err) if err != nil { return err } diff --git a/internal/domain/idp.go b/internal/domain/idp.go index 76c2e38cf9..3df11fa0d7 100644 --- a/internal/domain/idp.go +++ b/internal/domain/idp.go @@ -126,3 +126,11 @@ func (s IDPIntentState) Valid() bool { func (s IDPIntentState) Exists() bool { return s != IDPIntentStateUnspecified && s != IDPIntentStateFailed //TODO: ? } + +type AutoLinkingOption uint8 + +const ( + AutoLinkingOptionUnspecified AutoLinkingOption = iota + AutoLinkingOptionUsername + AutoLinkingOptionEmail +) diff --git a/internal/domain/machine_secret.go b/internal/domain/machine_secret.go deleted file mode 100644 index ba9bfe7b99..0000000000 --- a/internal/domain/machine_secret.go +++ /dev/null @@ -1,14 +0,0 @@ -package domain - -import ( - "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/zerrors" -) - -func NewMachineClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) { - cryptoValue, stringSecret, err := crypto.NewCode(generator) - if err != nil { - return nil, "", zerrors.ThrowInternal(err, "MODEL-57cjsiw", "Errors.User.Machine.Secret.CouldNotGenerate") - } - return cryptoValue, stringSecret, nil -} diff --git a/internal/domain/policy_password_lockout.go b/internal/domain/policy_password_lockout.go index ddac4de427..a5c304cd69 100644 --- a/internal/domain/policy_password_lockout.go +++ b/internal/domain/policy_password_lockout.go @@ -9,5 +9,6 @@ type LockoutPolicy struct { Default bool MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShowLockOutFailures bool } diff --git a/internal/domain/request.go b/internal/domain/request.go index bbacddc757..351ddd4e5f 100644 --- a/internal/domain/request.go +++ b/internal/domain/request.go @@ -63,6 +63,7 @@ type AuthRequestDevice struct { DeviceCode string UserCode string Scopes []string + Audience []string } func (*AuthRequestDevice) Type() AuthRequestType { diff --git a/internal/domain/smtp.go b/internal/domain/smtp.go index 02460ca8b2..e941616901 100644 --- a/internal/domain/smtp.go +++ b/internal/domain/smtp.go @@ -6,4 +6,9 @@ const ( SMTPConfigStateUnspecified SMTPConfigState = iota SMTPConfigStateActive SMTPConfigStateRemoved + SMTPConfigStateInactive ) + +func (s SMTPConfigState) Exists() bool { + return s != SMTPConfigStateUnspecified && s != SMTPConfigStateRemoved +} diff --git a/internal/eventstore/handler/crdb/db_mock_test.go b/internal/eventstore/handler/crdb/db_mock_test.go index aab63a4141..67f4e464e8 100644 --- a/internal/eventstore/handler/crdb/db_mock_test.go +++ b/internal/eventstore/handler/crdb/db_mock_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/zitadel/zitadel/internal/database" ) diff --git a/internal/eventstore/handler/v2/handler.go b/internal/eventstore/handler/v2/handler.go index d9a78262f1..02afc58ca2 100644 --- a/internal/eventstore/handler/v2/handler.go +++ b/internal/eventstore/handler/v2/handler.go @@ -11,7 +11,6 @@ import ( "time" "github.com/jackc/pgx/v5/pgconn" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" @@ -422,7 +421,7 @@ func (h *Handler) Trigger(ctx context.Context, opts ...TriggerOpt) (_ context.Co } } -// lockInstances tries to lock the instance. +// lockInstance tries to lock the instance. // If the instance is already locked from another process no cancel function is returned // the instance can be skipped then // If the instance is locked, an unlock deferrable function is returned diff --git a/internal/eventstore/repository/mock/repository.mock.go b/internal/eventstore/repository/mock/repository.mock.go index 8fef0ceac3..a854de2995 100644 --- a/internal/eventstore/repository/mock/repository.mock.go +++ b/internal/eventstore/repository/mock/repository.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./repository.mock.go github.com/zitadel/zitadel/internal/eventstore Querier,Pusher // + // Package mock is a generated GoMock package. package mock diff --git a/internal/feature/feature.go b/internal/feature/feature.go index d497f626b0..5edab4a8ba 100644 --- a/internal/feature/feature.go +++ b/internal/feature/feature.go @@ -10,6 +10,7 @@ const ( KeyLegacyIntrospection KeyUserSchema KeyTokenExchange + KeyActions ) //go:generate enumer -type Level -transform snake -trimprefix Level @@ -31,4 +32,5 @@ type Features struct { LegacyIntrospection bool `json:"legacy_introspection,omitempty"` UserSchema bool `json:"user_schema,omitempty"` TokenExchange bool `json:"token_exchange,omitempty"` + Actions bool `json:"actions,omitempty"` } diff --git a/internal/feature/key_enumer.go b/internal/feature/key_enumer.go index 06a052ee86..172d5f9d01 100644 --- a/internal/feature/key_enumer.go +++ b/internal/feature/key_enumer.go @@ -7,11 +7,11 @@ import ( "strings" ) -const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchange" +const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactions" -var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106} +var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113} -const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchange" +const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactions" func (i Key) String() string { if i < 0 || i >= Key(len(_KeyIndex)-1) { @@ -30,23 +30,26 @@ func _KeyNoOp() { _ = x[KeyLegacyIntrospection-(3)] _ = x[KeyUserSchema-(4)] _ = x[KeyTokenExchange-(5)] + _ = x[KeyActions-(6)] } -var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange} +var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions} var _KeyNameToValueMap = map[string]Key{ - _KeyName[0:11]: KeyUnspecified, - _KeyLowerName[0:11]: KeyUnspecified, - _KeyName[11:28]: KeyLoginDefaultOrg, - _KeyLowerName[11:28]: KeyLoginDefaultOrg, - _KeyName[28:61]: KeyTriggerIntrospectionProjections, - _KeyLowerName[28:61]: KeyTriggerIntrospectionProjections, - _KeyName[61:81]: KeyLegacyIntrospection, - _KeyLowerName[61:81]: KeyLegacyIntrospection, - _KeyName[81:92]: KeyUserSchema, - _KeyLowerName[81:92]: KeyUserSchema, - _KeyName[92:106]: KeyTokenExchange, - _KeyLowerName[92:106]: KeyTokenExchange, + _KeyName[0:11]: KeyUnspecified, + _KeyLowerName[0:11]: KeyUnspecified, + _KeyName[11:28]: KeyLoginDefaultOrg, + _KeyLowerName[11:28]: KeyLoginDefaultOrg, + _KeyName[28:61]: KeyTriggerIntrospectionProjections, + _KeyLowerName[28:61]: KeyTriggerIntrospectionProjections, + _KeyName[61:81]: KeyLegacyIntrospection, + _KeyLowerName[61:81]: KeyLegacyIntrospection, + _KeyName[81:92]: KeyUserSchema, + _KeyLowerName[81:92]: KeyUserSchema, + _KeyName[92:106]: KeyTokenExchange, + _KeyLowerName[92:106]: KeyTokenExchange, + _KeyName[106:113]: KeyActions, + _KeyLowerName[106:113]: KeyActions, } var _KeyNames = []string{ @@ -56,6 +59,7 @@ var _KeyNames = []string{ _KeyName[61:81], _KeyName[81:92], _KeyName[92:106], + _KeyName[106:113], } // KeyString retrieves an enum value from the enum constants string name. diff --git a/internal/id/mock/generator.mock.go b/internal/id/mock/generator.mock.go index 898d3a1e8d..9fce9d9581 100644 --- a/internal/id/mock/generator.mock.go +++ b/internal/id/mock/generator.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./mock/generator.mock.go github.com/zitadel/zitadel/internal/id Generator // + // Package mock is a generated GoMock package. package mock diff --git a/internal/idp/providers/apple/apple.go b/internal/idp/providers/apple/apple.go index 9664e006c8..65debed1a3 100644 --- a/internal/idp/providers/apple/apple.go +++ b/internal/idp/providers/apple/apple.go @@ -5,7 +5,7 @@ import ( "encoding/pem" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/zitadel/oidc/v3/pkg/crypto" openid "github.com/zitadel/oidc/v3/pkg/oidc" diff --git a/internal/idp/providers/jwt/session_test.go b/internal/idp/providers/jwt/session_test.go index ed34d8412c..de761f1531 100644 --- a/internal/idp/providers/jwt/session_test.go +++ b/internal/idp/providers/jwt/session_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/h2non/gock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/idp/providers/oidc/session.go b/internal/idp/providers/oidc/session.go index bb44fb1146..bd6303f2e5 100644 --- a/internal/idp/providers/oidc/session.go +++ b/internal/idp/providers/oidc/session.go @@ -38,17 +38,20 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) { return nil, err } } - info, err := rp.Userinfo[*oidc.UserInfo](ctx, - s.Tokens.AccessToken, - s.Tokens.TokenType, - s.Tokens.IDTokenClaims.GetSubject(), - s.Provider.RelyingParty, - ) - if err != nil { - return nil, err - } + + var info *oidc.UserInfo if s.Provider.useIDToken { info = s.Tokens.IDTokenClaims.GetUserInfo() + } else { + info, err = rp.Userinfo[*oidc.UserInfo](ctx, + s.Tokens.AccessToken, + s.Tokens.TokenType, + s.Tokens.IDTokenClaims.GetSubject(), + s.Provider.RelyingParty, + ) + if err != nil { + return nil, err + } } u := s.Provider.userInfoMapper(info) return u, nil diff --git a/internal/idp/providers/oidc/session_test.go b/internal/idp/providers/oidc/session_test.go index 200dac8fc4..f79ac7906f 100644 --- a/internal/idp/providers/oidc/session_test.go +++ b/internal/idp/providers/oidc/session_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/go-jose/go-jose/v3" + "github.com/go-jose/go-jose/v4" "github.com/h2non/gock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -54,6 +54,7 @@ func TestSession_FetchUser(t *testing.T) { tests := []struct { name string fields fields + opts []ProviderOpts want want }{ { @@ -198,6 +199,70 @@ func TestSession_FetchUser(t *testing.T) { profile: "profile", }, }, + { + name: "use ID token", + fields: fields{ + name: "oidc", + issuer: "https://issuer.com", + clientID: "clientID", + clientSecret: "clientSecret", + redirectURI: "redirectURI", + scopes: []string{"openid"}, + userMapper: DefaultMapper, + httpMock: func(issuer string) { + gock.New(issuer). + Get(oidc.DiscoveryEndpoint). + Reply(200). + JSON(&oidc.DiscoveryConfiguration{ + Issuer: issuer, + AuthorizationEndpoint: issuer + "/authorize", + TokenEndpoint: issuer + "/token", + UserinfoEndpoint: issuer + "/userinfo", + }) + }, + authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState", + tokens: &oidc.Tokens[*oidc.IDTokenClaims]{ + Token: &oauth2.Token{ + AccessToken: "accessToken", + TokenType: oidc.BearerToken, + }, + IDTokenClaims: func() *oidc.IDTokenClaims { + claims := oidc.NewIDTokenClaims( + "https://issuer.com", + "sub", + []string{"clientID"}, + time.Now().Add(1*time.Hour), + time.Now().Add(-1*time.Second), + "nonce", + "", + nil, + "clientID", + 0, + ) + claims.SetUserInfo(userinfo()) + return claims + }(), + }, + }, + opts: []ProviderOpts{ + WithIDTokenMapping(), + }, + want: want{ + id: "sub", + firstName: "firstname", + lastName: "lastname", + displayName: "firstname lastname", + nickName: "nickname", + preferredUsername: "username", + email: "email", + isEmailVerified: true, + phone: "phone", + isPhoneVerified: true, + preferredLanguage: language.English, + avatarURL: "picture", + profile: "profile", + }, + }, { name: "successful fetch with token exchange", fields: fields{ @@ -260,7 +325,7 @@ func TestSession_FetchUser(t *testing.T) { tt.fields.httpMock(tt.fields.issuer) a := assert.New(t) - provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.userMapper) + provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.userMapper, tt.opts...) require.NoError(t, err) session := &Session{ @@ -275,7 +340,7 @@ func TestSession_FetchUser(t *testing.T) { a.Fail("invalid error", "expected %v, got %v", tt.want.err, err) } if tt.want.err == nil { - a.NoError(err) + require.NoError(t, err) a.Equal(tt.want.id, user.GetID()) a.Equal(tt.want.firstName, user.GetFirstName()) a.Equal(tt.want.lastName, user.GetLastName()) diff --git a/internal/integration/client.go b/internal/integration/client.go index 48e7dc4a10..eb95134bc3 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -25,9 +25,9 @@ import ( openid "github.com/zitadel/zitadel/internal/idp/providers/oidc" "github.com/zitadel/zitadel/internal/idp/providers/saml" "github.com/zitadel/zitadel/internal/repository/idp" + action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha" "github.com/zitadel/zitadel/pkg/grpc/admin" "github.com/zitadel/zitadel/pkg/grpc/auth" - execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha" feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta" mgmt "github.com/zitadel/zitadel/pkg/grpc/management" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -53,7 +53,7 @@ type Client struct { OIDCv2 oidc_pb.OIDCServiceClient OrgV2 organisation.OrganizationServiceClient System system.SystemServiceClient - ExecutionV3 execution.ExecutionServiceClient + ActionV3 action.ActionServiceClient FeatureV2 feature.FeatureServiceClient UserSchemaV3 schema.UserSchemaServiceClient } @@ -70,7 +70,7 @@ func newClient(cc *grpc.ClientConn) Client { OIDCv2: oidc_pb.NewOIDCServiceClient(cc), OrgV2: organisation.NewOrganizationServiceClient(cc), System: system.NewSystemServiceClient(cc), - ExecutionV3: execution.NewExecutionServiceClient(cc), + ActionV3: action.NewActionServiceClient(cc), FeatureV2: feature.NewFeatureServiceClient(cc), UserSchemaV3: schema.NewUserSchemaServiceClient(cc), } @@ -522,48 +522,48 @@ func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, proj require.NoError(t, err) } -func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *execution.CreateTargetResponse { - req := &execution.CreateTargetRequest{ +func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *action.CreateTargetResponse { + req := &action.CreateTargetRequest{ Name: fmt.Sprint(time.Now().UnixNano() + 1), - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), } - target, err := s.Client.ExecutionV3.CreateTarget(ctx, req) + target, err := s.Client.ActionV3.CreateTarget(ctx, req) require.NoError(t, err) return target } -func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T, name string, async bool, interrupt bool) *execution.CreateTargetResponse { - req := &execution.CreateTargetRequest{ +func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T, name string, async bool, interrupt bool) *action.CreateTargetResponse { + req := &action.CreateTargetRequest{ Name: name, - TargetType: &execution.CreateTargetRequest_RestWebhook{ - RestWebhook: &execution.SetRESTWebhook{ + TargetType: &action.CreateTargetRequest_RestWebhook{ + RestWebhook: &action.SetRESTWebhook{ Url: "https://example.com", }, }, Timeout: durationpb.New(10 * time.Second), } if async { - req.ExecutionType = &execution.CreateTargetRequest_IsAsync{ + req.ExecutionType = &action.CreateTargetRequest_IsAsync{ IsAsync: true, } } if interrupt { - req.ExecutionType = &execution.CreateTargetRequest_InterruptOnError{ + req.ExecutionType = &action.CreateTargetRequest_InterruptOnError{ InterruptOnError: true, } } - target, err := s.Client.ExecutionV3.CreateTarget(ctx, req) + target, err := s.Client.ActionV3.CreateTarget(ctx, req) require.NoError(t, err) return target } -func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *execution.Condition, targets []string, includes []string) *execution.SetExecutionResponse { - target, err := s.Client.ExecutionV3.SetExecution(ctx, &execution.SetExecutionRequest{ +func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []string, includes []string) *action.SetExecutionResponse { + target, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{ Condition: cond, Targets: targets, Includes: includes, diff --git a/internal/notification/channels/log/channel.go b/internal/notification/channels/log/channel.go index a435ca1b41..ecdb59c642 100644 --- a/internal/notification/channels/log/channel.go +++ b/internal/notification/channels/log/channel.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/k3a/html2text" - "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/notification/channels" ) diff --git a/internal/notification/channels/mock/channel.mock.go b/internal/notification/channels/mock/channel.mock.go index f00b611193..3594178a4d 100644 --- a/internal/notification/channels/mock/channel.mock.go +++ b/internal/notification/channels/mock/channel.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./mock/channel.mock.go github.com/zitadel/zitadel/internal/notification/channels NotificationChannel // + // Package mock is a generated GoMock package. package mock diff --git a/internal/notification/channels/mock/message.mock.go b/internal/notification/channels/mock/message.mock.go index 4a498b2698..1e8ea0067c 100644 --- a/internal/notification/channels/mock/message.mock.go +++ b/internal/notification/channels/mock/message.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./mock/message.mock.go github.com/zitadel/zitadel/internal/notification/channels Message // + // Package mock is a generated GoMock package. package mock diff --git a/internal/notification/channels/smtp/channel.go b/internal/notification/channels/smtp/channel.go index 2d6dc9dadd..3524634765 100644 --- a/internal/notification/channels/smtp/channel.go +++ b/internal/notification/channels/smtp/channel.go @@ -91,9 +91,9 @@ func (smtpConfig SMTP) connectToSMTP(tlsRequired bool) (client *smtp.Client, err } if !tlsRequired { - client, err = smtpConfig.getSMPTClient() + client, err = smtpConfig.getSMTPClient() } else { - client, err = smtpConfig.getSMPTClientWithTls(host) + client, err = smtpConfig.getSMTPClientWithTls(host) } if err != nil { return nil, err @@ -106,7 +106,7 @@ func (smtpConfig SMTP) connectToSMTP(tlsRequired bool) (client *smtp.Client, err return client, nil } -func (smtpConfig SMTP) getSMPTClient() (*smtp.Client, error) { +func (smtpConfig SMTP) getSMTPClient() (*smtp.Client, error) { client, err := smtp.Dial(smtpConfig.Host) if err != nil { return nil, zerrors.ThrowInternal(err, "EMAIL-skwos", "could not make smtp dial") @@ -114,12 +114,12 @@ func (smtpConfig SMTP) getSMPTClient() (*smtp.Client, error) { return client, nil } -func (smtpConfig SMTP) getSMPTClientWithTls(host string) (*smtp.Client, error) { +func (smtpConfig SMTP) getSMTPClientWithTls(host string) (*smtp.Client, error) { conn, err := tls.Dial("tcp", smtpConfig.Host, &tls.Config{}) if errors.As(err, &tls.RecordHeaderError{}) { logging.Log("MAIN-xKIzT").OnError(err).Warn("could not connect using normal tls. trying starttls instead...") - return smtpConfig.getSMPTClientWithStartTls(host) + return smtpConfig.getSMTPClientWithStartTls(host) } if err != nil { @@ -133,8 +133,8 @@ func (smtpConfig SMTP) getSMPTClientWithTls(host string) (*smtp.Client, error) { return client, err } -func (smtpConfig SMTP) getSMPTClientWithStartTls(host string) (*smtp.Client, error) { - client, err := smtpConfig.getSMPTClient() +func (smtpConfig SMTP) getSMTPClientWithStartTls(host string) (*smtp.Client, error) { + client, err := smtpConfig.getSMTPClient() if err != nil { return nil, err } diff --git a/internal/notification/channels/smtp/config.go b/internal/notification/channels/smtp/config.go index 8646c9d58f..865a2f4cd1 100644 --- a/internal/notification/channels/smtp/config.go +++ b/internal/notification/channels/smtp/config.go @@ -1,6 +1,7 @@ package smtp type Config struct { + Description string SMTP SMTP Tls bool From string diff --git a/internal/notification/handlers/config_smtp.go b/internal/notification/handlers/config_smtp.go index aa245157c5..8cce47dec6 100644 --- a/internal/notification/handlers/config_smtp.go +++ b/internal/notification/handlers/config_smtp.go @@ -10,7 +10,7 @@ import ( // GetSMTPConfig reads the iam SMTP provider config func (n *NotificationQueries) GetSMTPConfig(ctx context.Context) (*smtp.Config, error) { - config, err := n.SMTPConfigByAggregateID(ctx, authz.GetInstance(ctx).InstanceID()) + config, err := n.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) if err != nil { return nil, err } @@ -19,6 +19,7 @@ func (n *NotificationQueries) GetSMTPConfig(ctx context.Context) (*smtp.Config, return nil, err } return &smtp.Config{ + Description: config.Description, From: config.SenderAddress, FromName: config.SenderName, ReplyToAddress: config.ReplyToAddress, diff --git a/internal/notification/handlers/mock/commands.mock.go b/internal/notification/handlers/mock/commands.mock.go index 845ab264e6..3105dcdf60 100644 --- a/internal/notification/handlers/mock/commands.mock.go +++ b/internal/notification/handlers/mock/commands.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./mock/commands.mock.go github.com/zitadel/zitadel/internal/notification/handlers Commands // + // Package mock is a generated GoMock package. package mock diff --git a/internal/notification/handlers/mock/queries.mock.go b/internal/notification/handlers/mock/queries.mock.go index 620e8ccd59..210493d875 100644 --- a/internal/notification/handlers/mock/queries.mock.go +++ b/internal/notification/handlers/mock/queries.mock.go @@ -5,6 +5,7 @@ // // mockgen -package mock -destination ./mock/queries.mock.go github.com/zitadel/zitadel/internal/notification/handlers Queries // + // Package mock is a generated GoMock package. package mock @@ -180,19 +181,19 @@ func (mr *MockQueriesMockRecorder) SMSProviderConfig(arg0 any, arg1 ...any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfig", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfig), varargs...) } -// SMTPConfigByAggregateID mocks base method. -func (m *MockQueries) SMTPConfigByAggregateID(arg0 context.Context, arg1 string) (*query.SMTPConfig, error) { +// SMTPConfigActive mocks base method. +func (m *MockQueries) SMTPConfigActive(arg0 context.Context, arg1 string) (*query.SMTPConfig, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SMTPConfigByAggregateID", arg0, arg1) + ret := m.ctrl.Call(m, "SMTPConfigActive", arg0, arg1) ret0, _ := ret[0].(*query.SMTPConfig) ret1, _ := ret[1].(error) return ret0, ret1 } -// SMTPConfigByAggregateID indicates an expected call of SMTPConfigByAggregateID. -func (mr *MockQueriesMockRecorder) SMTPConfigByAggregateID(arg0, arg1 any) *gomock.Call { +// SMTPConfigActive indicates an expected call of SMTPConfigActive. +func (mr *MockQueriesMockRecorder) SMTPConfigActive(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMTPConfigByAggregateID", reflect.TypeOf((*MockQueries)(nil).SMTPConfigByAggregateID), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMTPConfigActive", reflect.TypeOf((*MockQueries)(nil).SMTPConfigActive), arg0, arg1) } // SearchInstanceDomains mocks base method. diff --git a/internal/notification/handlers/queries.go b/internal/notification/handlers/queries.go index afaeb3419a..ce7597ead8 100644 --- a/internal/notification/handlers/queries.go +++ b/internal/notification/handlers/queries.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/crypto" @@ -21,7 +22,7 @@ type Queries interface { SearchMilestones(ctx context.Context, instanceIDs []string, queries *query.MilestonesSearchQueries) (*query.Milestones, error) NotificationProviderByIDAndType(ctx context.Context, aggID string, providerType domain.NotificationProviderType) (*query.DebugNotificationProvider, error) SMSProviderConfig(ctx context.Context, queries ...query.SearchQuery) (*query.SMSConfig, error) - SMTPConfigByAggregateID(ctx context.Context, aggregateID string) (*query.SMTPConfig, error) + SMTPConfigActive(ctx context.Context, resourceOwner string) (*query.SMTPConfig, error) GetDefaultLanguage(ctx context.Context) language.Tag GetInstanceRestrictions(ctx context.Context) (restrictions query.Restrictions, err error) } diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go index 86b843fc52..55751beb13 100644 --- a/internal/notification/handlers/user_notifier_test.go +++ b/internal/notification/handlers/user_notifier_test.go @@ -7,10 +7,6 @@ import ( "testing" "time" - "github.com/zitadel/zitadel/internal/repository/session" - - "github.com/zitadel/zitadel/internal/notification/messages" - "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "golang.org/x/text/language" @@ -24,9 +20,11 @@ import ( "github.com/zitadel/zitadel/internal/notification/channels/twilio" "github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/handlers/mock" + "github.com/zitadel/zitadel/internal/notification/messages" "github.com/zitadel/zitadel/internal/notification/senders" "github.com/zitadel/zitadel/internal/notification/types" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/session" "github.com/zitadel/zitadel/internal/repository/user" ) diff --git a/internal/notification/senders/email.go b/internal/notification/senders/email.go index 5123798f47..ea93c0911e 100644 --- a/internal/notification/senders/email.go +++ b/internal/notification/senders/email.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/notification/channels" "github.com/zitadel/zitadel/internal/notification/channels/fs" diff --git a/internal/notification/senders/webhook.go b/internal/notification/senders/webhook.go index 050168432b..d677f9a80e 100644 --- a/internal/notification/senders/webhook.go +++ b/internal/notification/senders/webhook.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/notification/channels" "github.com/zitadel/zitadel/internal/notification/channels/fs" diff --git a/internal/notification/types/otp.go b/internal/notification/types/otp.go index 7919984f7b..91befa23fc 100644 --- a/internal/notification/types/otp.go +++ b/internal/notification/types/otp.go @@ -4,10 +4,8 @@ import ( "context" "time" - http_utils "github.com/zitadel/zitadel/internal/api/http" - "github.com/zitadel/zitadel/internal/api/authz" - + http_utils "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/domain" ) diff --git a/internal/notification/types/templateData.go b/internal/notification/types/templateData.go index d020d977cf..23b7deee70 100644 --- a/internal/notification/types/templateData.go +++ b/internal/notification/types/templateData.go @@ -5,9 +5,8 @@ import ( "fmt" "strings" - http_util "github.com/zitadel/zitadel/internal/api/http" - "github.com/zitadel/zitadel/internal/api/assets" + http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/notification/templates" "github.com/zitadel/zitadel/internal/query" diff --git a/internal/query/app_test.go b/internal/query/app_test.go index 9f56bab962..3ccec2e4c8 100644 --- a/internal/query/app_test.go +++ b/internal/query/app_test.go @@ -15,98 +15,98 @@ import ( ) var ( - expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + - ` projections.apps6.name,` + - ` projections.apps6.project_id,` + - ` projections.apps6.creation_date,` + - ` projections.apps6.change_date,` + - ` projections.apps6.resource_owner,` + - ` projections.apps6.state,` + - ` projections.apps6.sequence,` + + expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` + + ` projections.apps7.name,` + + ` projections.apps7.project_id,` + + ` projections.apps7.creation_date,` + + ` projections.apps7.change_date,` + + ` projections.apps7.resource_owner,` + + ` projections.apps7.state,` + + ` projections.apps7.sequence,` + // api config - ` projections.apps6_api_configs.app_id,` + - ` projections.apps6_api_configs.client_id,` + - ` projections.apps6_api_configs.auth_method,` + + ` projections.apps7_api_configs.app_id,` + + ` projections.apps7_api_configs.client_id,` + + ` projections.apps7_api_configs.auth_method,` + // oidc config - ` projections.apps6_oidc_configs.app_id,` + - ` projections.apps6_oidc_configs.version,` + - ` projections.apps6_oidc_configs.client_id,` + - ` projections.apps6_oidc_configs.redirect_uris,` + - ` projections.apps6_oidc_configs.response_types,` + - ` projections.apps6_oidc_configs.grant_types,` + - ` projections.apps6_oidc_configs.application_type,` + - ` projections.apps6_oidc_configs.auth_method_type,` + - ` projections.apps6_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps6_oidc_configs.is_dev_mode,` + - ` projections.apps6_oidc_configs.access_token_type,` + - ` projections.apps6_oidc_configs.access_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps6_oidc_configs.clock_skew,` + - ` projections.apps6_oidc_configs.additional_origins,` + - ` projections.apps6_oidc_configs.skip_native_app_success_page,` + + ` projections.apps7_oidc_configs.app_id,` + + ` projections.apps7_oidc_configs.version,` + + ` projections.apps7_oidc_configs.client_id,` + + ` projections.apps7_oidc_configs.redirect_uris,` + + ` projections.apps7_oidc_configs.response_types,` + + ` projections.apps7_oidc_configs.grant_types,` + + ` projections.apps7_oidc_configs.application_type,` + + ` projections.apps7_oidc_configs.auth_method_type,` + + ` projections.apps7_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps7_oidc_configs.is_dev_mode,` + + ` projections.apps7_oidc_configs.access_token_type,` + + ` projections.apps7_oidc_configs.access_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps7_oidc_configs.clock_skew,` + + ` projections.apps7_oidc_configs.additional_origins,` + + ` projections.apps7_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps6_saml_configs.app_id,` + - ` projections.apps6_saml_configs.entity_id,` + - ` projections.apps6_saml_configs.metadata,` + - ` projections.apps6_saml_configs.metadata_url` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + ` projections.apps7_saml_configs.app_id,` + + ` projections.apps7_saml_configs.entity_id,` + + ` projections.apps7_saml_configs.metadata,` + + ` projections.apps7_saml_configs.metadata_url` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + - ` projections.apps6.name,` + - ` projections.apps6.project_id,` + - ` projections.apps6.creation_date,` + - ` projections.apps6.change_date,` + - ` projections.apps6.resource_owner,` + - ` projections.apps6.state,` + - ` projections.apps6.sequence,` + + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` + + ` projections.apps7.name,` + + ` projections.apps7.project_id,` + + ` projections.apps7.creation_date,` + + ` projections.apps7.change_date,` + + ` projections.apps7.resource_owner,` + + ` projections.apps7.state,` + + ` projections.apps7.sequence,` + // api config - ` projections.apps6_api_configs.app_id,` + - ` projections.apps6_api_configs.client_id,` + - ` projections.apps6_api_configs.auth_method,` + + ` projections.apps7_api_configs.app_id,` + + ` projections.apps7_api_configs.client_id,` + + ` projections.apps7_api_configs.auth_method,` + // oidc config - ` projections.apps6_oidc_configs.app_id,` + - ` projections.apps6_oidc_configs.version,` + - ` projections.apps6_oidc_configs.client_id,` + - ` projections.apps6_oidc_configs.redirect_uris,` + - ` projections.apps6_oidc_configs.response_types,` + - ` projections.apps6_oidc_configs.grant_types,` + - ` projections.apps6_oidc_configs.application_type,` + - ` projections.apps6_oidc_configs.auth_method_type,` + - ` projections.apps6_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps6_oidc_configs.is_dev_mode,` + - ` projections.apps6_oidc_configs.access_token_type,` + - ` projections.apps6_oidc_configs.access_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps6_oidc_configs.clock_skew,` + - ` projections.apps6_oidc_configs.additional_origins,` + - ` projections.apps6_oidc_configs.skip_native_app_success_page,` + + ` projections.apps7_oidc_configs.app_id,` + + ` projections.apps7_oidc_configs.version,` + + ` projections.apps7_oidc_configs.client_id,` + + ` projections.apps7_oidc_configs.redirect_uris,` + + ` projections.apps7_oidc_configs.response_types,` + + ` projections.apps7_oidc_configs.grant_types,` + + ` projections.apps7_oidc_configs.application_type,` + + ` projections.apps7_oidc_configs.auth_method_type,` + + ` projections.apps7_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps7_oidc_configs.is_dev_mode,` + + ` projections.apps7_oidc_configs.access_token_type,` + + ` projections.apps7_oidc_configs.access_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps7_oidc_configs.clock_skew,` + + ` projections.apps7_oidc_configs.additional_origins,` + + ` projections.apps7_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps6_saml_configs.app_id,` + - ` projections.apps6_saml_configs.entity_id,` + - ` projections.apps6_saml_configs.metadata,` + - ` projections.apps6_saml_configs.metadata_url,` + + ` projections.apps7_saml_configs.app_id,` + + ` projections.apps7_saml_configs.entity_id,` + + ` projections.apps7_saml_configs.metadata,` + + ` projections.apps7_saml_configs.metadata_url,` + ` COUNT(*) OVER ()` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps6_api_configs.client_id,` + - ` projections.apps6_oidc_configs.client_id` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + + expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps7_api_configs.client_id,` + + ` projections.apps7_oidc_configs.client_id` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.project_id` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.project_id` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects4.id,` + ` projections.projects4.creation_date,` + @@ -120,10 +120,10 @@ var ( ` projections.projects4.has_project_check,` + ` projections.projects4.private_labeling_setting` + ` FROM projections.projects4` + - ` JOIN projections.apps6 ON projections.projects4.id = projections.apps6.project_id AND projections.projects4.instance_id = projections.apps6.instance_id` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + ` JOIN projections.apps7 ON projections.projects4.id = projections.apps7.project_id AND projections.projects4.instance_id = projections.apps7.instance_id` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) appCols = database.TextArray[string]{ diff --git a/internal/query/auth_request.go b/internal/query/auth_request.go index 9de2cf9db0..c0554778ab 100644 --- a/internal/query/auth_request.go +++ b/internal/query/auth_request.go @@ -41,7 +41,7 @@ func (a *AuthRequest) checkLoginClient(ctx context.Context) error { return nil } -//go:embed embed/auth_request_by_id.sql +//go:embed auth_request_by_id.sql var authRequestByIDQuery string func (q *Queries) authRequestByIDQuery(ctx context.Context) string { diff --git a/internal/query/embed/auth_request_by_id.sql b/internal/query/auth_request_by_id.sql similarity index 100% rename from internal/query/embed/auth_request_by_id.sql rename to internal/query/auth_request_by_id.sql diff --git a/internal/query/authn_key.go b/internal/query/authn_key.go index 584270e2bc..99ccca0d97 100644 --- a/internal/query/authn_key.go +++ b/internal/query/authn_key.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/custom_text.go b/internal/query/custom_text.go index 3bd3bf0793..5558585be1 100644 --- a/internal/query/custom_text.go +++ b/internal/query/custom_text.go @@ -409,8 +409,11 @@ func CustomTextsToLoginDomain(instanceID, aggregateID, lang string, texts *Custo if strings.HasPrefix(text.Key, domain.LoginKeyRegistrationOrg) { registrationOrgKeyToDomain(text, result) } + if strings.HasPrefix(text.Key, domain.LoginKeyLinkingUserPrompt) { + linkingUserPromptKeyToDomain(text, result) + } if strings.HasPrefix(text.Key, domain.LoginKeyLinkingUserDone) { - linkingUserKeyToDomain(text, result) + linkingUserDoneKeyToDomain(text, result) } if strings.HasPrefix(text.Key, domain.LoginKeyExternalNotFound) { externalUserNotFoundKeyToDomain(text, result) @@ -1100,7 +1103,22 @@ func registrationOrgKeyToDomain(text *CustomText, result *domain.CustomLoginText } } -func linkingUserKeyToDomain(text *CustomText, result *domain.CustomLoginText) { +func linkingUserPromptKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyLinkingUserPromptTitle { + result.LinkingUserPrompt.Title = text.Text + } + if text.Key == domain.LoginKeyLinkingUserPromptDescription { + result.LinkingUserPrompt.Description = text.Text + } + if text.Key == domain.LoginKeyLinkingUserPromptLinkButtonText { + result.LinkingUserPrompt.LinkButtonText = text.Text + } + if text.Key == domain.LoginKeyLinkingUserPromptOtherButtonText { + result.LinkingUserPrompt.OtherButtonText = text.Text + } +} + +func linkingUserDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { if text.Key == domain.LoginKeyLinkingUserDoneTitle { result.LinkingUsersDone.Title = text.Text } diff --git a/internal/query/device_auth.go b/internal/query/device_auth.go index 17e1b8e943..9cef26d2bd 100644 --- a/internal/query/device_auth.go +++ b/internal/query/device_auth.go @@ -37,6 +37,10 @@ var ( name: projection.DeviceAuthRequestColumnScopes, table: deviceAuthRequestTable, } + DeviceAuthRequestColumnAudience = Column{ + name: projection.DeviceAuthRequestColumnAudience, + table: deviceAuthRequestTable, + } DeviceAuthRequestColumnCreationDate = Column{ name: projection.DeviceAuthRequestColumnCreationDate, table: deviceAuthRequestTable, @@ -61,6 +65,7 @@ type DeviceAuth struct { UserCode string Expires time.Time Scopes []string + Audience []string State domain.DeviceAuthState Subject string UserAuthMethods []domain.UserAuthMethodType @@ -109,6 +114,7 @@ var deviceAuthSelectColumns = []string{ DeviceAuthRequestColumnDeviceCode.identifier(), DeviceAuthRequestColumnUserCode.identifier(), DeviceAuthRequestColumnScopes.identifier(), + DeviceAuthRequestColumnAudience.identifier(), } func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*domain.AuthRequestDevice, error)) { @@ -116,7 +122,8 @@ func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectB func(row *sql.Row) (*domain.AuthRequestDevice, error) { dst := new(domain.AuthRequestDevice) var ( - scopes database.TextArray[string] + scopes database.TextArray[string] + audience database.TextArray[string] ) err := row.Scan( @@ -124,6 +131,7 @@ func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectB &dst.DeviceCode, &dst.UserCode, &scopes, + &audience, ) if errors.Is(err, sql.ErrNoRows) { return nil, zerrors.ThrowNotFound(err, "QUERY-Sah9a", "Errors.DeviceAuth.NotExisting") @@ -132,6 +140,7 @@ func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectB return nil, zerrors.ThrowInternal(err, "QUERY-Voo3o", "Errors.Internal") } dst.Scopes = scopes + dst.Audience = audience return dst, nil } } diff --git a/internal/query/device_auth_model.go b/internal/query/device_auth_model.go index 23fc0775e1..91ad06ddfc 100644 --- a/internal/query/device_auth_model.go +++ b/internal/query/device_auth_model.go @@ -29,6 +29,7 @@ func (m *DeviceAuthReadModel) Reduce() error { m.UserCode = e.UserCode m.Expires = e.Expires m.Scopes = e.Scopes + m.Audience = e.Audience m.State = e.State case *deviceauth.ApprovedEvent: m.State = domain.DeviceAuthStateApproved diff --git a/internal/query/device_auth_test.go b/internal/query/device_auth_test.go index fab198073b..de123a3a78 100644 --- a/internal/query/device_auth_test.go +++ b/internal/query/device_auth_test.go @@ -55,6 +55,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { ctx, deviceauth.NewAggregate("device1", "instance1"), "client1", "device1", "user-code", timestamp, []string{"foo", "bar"}, + []string{"projectID", "clientID"}, )), ), ), @@ -64,6 +65,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { UserCode: "user-code", Expires: timestamp, Scopes: []string{"foo", "bar"}, + Audience: []string{"projectID", "clientID"}, State: domain.DeviceAuthStateInitiated, }, }, @@ -75,6 +77,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { ctx, deviceauth.NewAggregate("device1", "instance1"), "client1", "device1", "user-code", timestamp, []string{"foo", "bar"}, + []string{"projectID", "clientID"}, )), eventFromEventPusher(deviceauth.NewApprovedEvent( ctx, @@ -90,6 +93,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { UserCode: "user-code", Expires: timestamp, Scopes: []string{"foo", "bar"}, + Audience: []string{"projectID", "clientID"}, State: domain.DeviceAuthStateApproved, Subject: "user1", UserAuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePasswordless}, @@ -104,6 +108,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { ctx, deviceauth.NewAggregate("device1", "instance1"), "client1", "device1", "user-code", timestamp, []string{"foo", "bar"}, + []string{"projectID", "clientID"}, )), eventFromEventPusher(deviceauth.NewCanceledEvent( ctx, @@ -118,6 +123,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { UserCode: "user-code", Expires: timestamp, Scopes: []string{"foo", "bar"}, + Audience: []string{"projectID", "clientID"}, State: domain.DeviceAuthStateDenied, }, }, @@ -129,6 +135,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { ctx, deviceauth.NewAggregate("device1", "instance1"), "client1", "device1", "user-code", timestamp, []string{"foo", "bar"}, + []string{"projectID", "clientID"}, )), eventFromEventPusher(deviceauth.NewCanceledEvent( ctx, @@ -143,6 +150,7 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { UserCode: "user-code", Expires: timestamp, Scopes: []string{"foo", "bar"}, + Audience: []string{"projectID", "clientID"}, State: domain.DeviceAuthStateExpired, }, }, @@ -161,14 +169,15 @@ func TestQueries_DeviceAuthByDeviceCode(t *testing.T) { const ( expectedDeviceAuthQueryC = `SELECT` + - ` projections.device_auth_requests.client_id,` + - ` projections.device_auth_requests.device_code,` + - ` projections.device_auth_requests.user_code,` + - ` projections.device_auth_requests.scopes` + - ` FROM projections.device_auth_requests` + ` projections.device_auth_requests1.client_id,` + + ` projections.device_auth_requests1.device_code,` + + ` projections.device_auth_requests1.user_code,` + + ` projections.device_auth_requests1.scopes,` + + ` projections.device_auth_requests1.audience` + + ` FROM projections.device_auth_requests1` expectedDeviceAuthWhereUserCodeQueryC = expectedDeviceAuthQueryC + - ` WHERE projections.device_auth_requests.instance_id = $1` + - ` AND projections.device_auth_requests.user_code = $2` + ` WHERE projections.device_auth_requests1.instance_id = $1` + + ` AND projections.device_auth_requests1.user_code = $2` ) var ( @@ -179,12 +188,14 @@ var ( "device1", "user-code", database.TextArray[string]{"a", "b", "c"}, + []string{"projectID", "clientID"}, } expectedDeviceAuth = &domain.AuthRequestDevice{ ClientID: "client-id", DeviceCode: "device1", UserCode: "user-code", Scopes: []string{"a", "b", "c"}, + Audience: []string{"projectID", "clientID"}, } ) diff --git a/internal/query/domain_policy.go b/internal/query/domain_policy.go index 922efb3869..d971723bcf 100644 --- a/internal/query/domain_policy.go +++ b/internal/query/domain_policy.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/embed/introspection_client_by_id.sql b/internal/query/embed/introspection_client_by_id.sql deleted file mode 100644 index b8c85806b0..0000000000 --- a/internal/query/embed/introspection_client_by_id.sql +++ /dev/null @@ -1,23 +0,0 @@ -with config as ( - select app_id, client_id, client_secret - from projections.apps6_api_configs - where instance_id = $1 - and client_id = $2 - union - select app_id, client_id, client_secret - from projections.apps6_oidc_configs - where instance_id = $1 - and client_id = $2 -), -keys as ( - select identifier as client_id, json_object_agg(id, encode(public_key, 'base64')) as public_keys - from projections.authn_keys2 - where $3 = true -- when argument is false, don't waste time on trying to query for keys. - and instance_id = $1 - and identifier = $2 - and expiration > current_timestamp - group by identifier -) -select config.client_id, config.client_secret, apps.project_id, keys.public_keys from config -join projections.apps6 apps on apps.id = config.app_id -left join keys on keys.client_id = config.client_id; diff --git a/internal/query/iam_member_test.go b/internal/query/iam_member_test.go index 074819b0c1..36bb476d22 100644 --- a/internal/query/iam_member_test.go +++ b/internal/query/iam_member_test.go @@ -21,21 +21,21 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.instance_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id AND members.instance_id = projections.login_names3.instance_id " + "AS OF SYSTEM TIME '-1 ms' " + diff --git a/internal/query/idp.go b/internal/query/idp.go index 06d96a76a5..2687397330 100644 --- a/internal/query/idp.go +++ b/internal/query/idp.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/idp_login_policy_link_test.go b/internal/query/idp_login_policy_link_test.go index 4f720b86af..45cb8e594d 100644 --- a/internal/query/idp_login_policy_link_test.go +++ b/internal/query/idp_login_policy_link_test.go @@ -16,12 +16,12 @@ import ( var ( loginPolicyIDPLinksQuery = regexp.QuoteMeta(`SELECT projections.idp_login_policy_links5.idp_id,` + - ` projections.idp_templates5.name,` + - ` projections.idp_templates5.type,` + - ` projections.idp_templates5.owner_type,` + + ` projections.idp_templates6.name,` + + ` projections.idp_templates6.type,` + + ` projections.idp_templates6.owner_type,` + ` COUNT(*) OVER ()` + ` FROM projections.idp_login_policy_links5` + - ` LEFT JOIN projections.idp_templates5 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates5.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates5.instance_id` + + ` LEFT JOIN projections.idp_templates6 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates6.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates6.instance_id` + ` RIGHT JOIN (SELECT login_policy_owner.aggregate_id, login_policy_owner.instance_id, login_policy_owner.owner_removed FROM projections.login_policies5 AS login_policy_owner` + ` WHERE (login_policy_owner.instance_id = $1 AND (login_policy_owner.aggregate_id = $2 OR login_policy_owner.aggregate_id = $3)) ORDER BY login_policy_owner.is_default LIMIT 1) AS login_policy_owner` + ` ON login_policy_owner.aggregate_id = projections.idp_login_policy_links5.resource_owner AND login_policy_owner.instance_id = projections.idp_login_policy_links5.instance_id` + diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go index 1cfdc14d5b..361389f995 100644 --- a/internal/query/idp_template.go +++ b/internal/query/idp_template.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" @@ -36,6 +35,7 @@ type IDPTemplate struct { IsLinkingAllowed bool IsAutoCreation bool IsAutoUpdate bool + AutoLinking domain.AutoLinkingOption *OAuthIDPTemplate *OIDCIDPTemplate *JWTIDPTemplate @@ -228,6 +228,10 @@ var ( name: projection.IDPTemplateIsAutoUpdateCol, table: idpTemplateTable, } + IDPTemplateAutoLinkingCol = Column{ + name: projection.IDPTemplateAutoLinkingCol, + table: idpTemplateTable, + } ) var ( @@ -813,6 +817,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se IDPTemplateIsLinkingAllowedCol.identifier(), IDPTemplateIsAutoCreationCol.identifier(), IDPTemplateIsAutoUpdateCol.identifier(), + IDPTemplateAutoLinkingCol.identifier(), // oauth OAuthIDCol.identifier(), OAuthClientIDCol.identifier(), @@ -1038,6 +1043,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se &idpTemplate.IsLinkingAllowed, &idpTemplate.IsAutoCreation, &idpTemplate.IsAutoUpdate, + &idpTemplate.AutoLinking, // oauth &oauthID, &oauthClientID, @@ -1298,6 +1304,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec IDPTemplateIsLinkingAllowedCol.identifier(), IDPTemplateIsAutoCreationCol.identifier(), IDPTemplateIsAutoUpdateCol.identifier(), + IDPTemplateAutoLinkingCol.identifier(), // oauth OAuthIDCol.identifier(), OAuthClientIDCol.identifier(), @@ -1528,6 +1535,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec &idpTemplate.IsLinkingAllowed, &idpTemplate.IsAutoCreation, &idpTemplate.IsAutoUpdate, + &idpTemplate.AutoLinking, // oauth &oauthID, &oauthClientID, diff --git a/internal/query/idp_template_test.go b/internal/query/idp_template_test.go index f95b6da05b..72e5d9f8c8 100644 --- a/internal/query/idp_template_test.go +++ b/internal/query/idp_template_test.go @@ -16,128 +16,129 @@ import ( ) var ( - idpTemplateQuery = `SELECT projections.idp_templates5.id,` + - ` projections.idp_templates5.resource_owner,` + - ` projections.idp_templates5.creation_date,` + - ` projections.idp_templates5.change_date,` + - ` projections.idp_templates5.sequence,` + - ` projections.idp_templates5.state,` + - ` projections.idp_templates5.name,` + - ` projections.idp_templates5.type,` + - ` projections.idp_templates5.owner_type,` + - ` projections.idp_templates5.is_creation_allowed,` + - ` projections.idp_templates5.is_linking_allowed,` + - ` projections.idp_templates5.is_auto_creation,` + - ` projections.idp_templates5.is_auto_update,` + + idpTemplateQuery = `SELECT projections.idp_templates6.id,` + + ` projections.idp_templates6.resource_owner,` + + ` projections.idp_templates6.creation_date,` + + ` projections.idp_templates6.change_date,` + + ` projections.idp_templates6.sequence,` + + ` projections.idp_templates6.state,` + + ` projections.idp_templates6.name,` + + ` projections.idp_templates6.type,` + + ` projections.idp_templates6.owner_type,` + + ` projections.idp_templates6.is_creation_allowed,` + + ` projections.idp_templates6.is_linking_allowed,` + + ` projections.idp_templates6.is_auto_creation,` + + ` projections.idp_templates6.is_auto_update,` + + ` projections.idp_templates6.auto_linking,` + // oauth - ` projections.idp_templates5_oauth2.idp_id,` + - ` projections.idp_templates5_oauth2.client_id,` + - ` projections.idp_templates5_oauth2.client_secret,` + - ` projections.idp_templates5_oauth2.authorization_endpoint,` + - ` projections.idp_templates5_oauth2.token_endpoint,` + - ` projections.idp_templates5_oauth2.user_endpoint,` + - ` projections.idp_templates5_oauth2.scopes,` + - ` projections.idp_templates5_oauth2.id_attribute,` + + ` projections.idp_templates6_oauth2.idp_id,` + + ` projections.idp_templates6_oauth2.client_id,` + + ` projections.idp_templates6_oauth2.client_secret,` + + ` projections.idp_templates6_oauth2.authorization_endpoint,` + + ` projections.idp_templates6_oauth2.token_endpoint,` + + ` projections.idp_templates6_oauth2.user_endpoint,` + + ` projections.idp_templates6_oauth2.scopes,` + + ` projections.idp_templates6_oauth2.id_attribute,` + // oidc - ` projections.idp_templates5_oidc.idp_id,` + - ` projections.idp_templates5_oidc.issuer,` + - ` projections.idp_templates5_oidc.client_id,` + - ` projections.idp_templates5_oidc.client_secret,` + - ` projections.idp_templates5_oidc.scopes,` + - ` projections.idp_templates5_oidc.id_token_mapping,` + + ` projections.idp_templates6_oidc.idp_id,` + + ` projections.idp_templates6_oidc.issuer,` + + ` projections.idp_templates6_oidc.client_id,` + + ` projections.idp_templates6_oidc.client_secret,` + + ` projections.idp_templates6_oidc.scopes,` + + ` projections.idp_templates6_oidc.id_token_mapping,` + // jwt - ` projections.idp_templates5_jwt.idp_id,` + - ` projections.idp_templates5_jwt.issuer,` + - ` projections.idp_templates5_jwt.jwt_endpoint,` + - ` projections.idp_templates5_jwt.keys_endpoint,` + - ` projections.idp_templates5_jwt.header_name,` + + ` projections.idp_templates6_jwt.idp_id,` + + ` projections.idp_templates6_jwt.issuer,` + + ` projections.idp_templates6_jwt.jwt_endpoint,` + + ` projections.idp_templates6_jwt.keys_endpoint,` + + ` projections.idp_templates6_jwt.header_name,` + // azure - ` projections.idp_templates5_azure.idp_id,` + - ` projections.idp_templates5_azure.client_id,` + - ` projections.idp_templates5_azure.client_secret,` + - ` projections.idp_templates5_azure.scopes,` + - ` projections.idp_templates5_azure.tenant,` + - ` projections.idp_templates5_azure.is_email_verified,` + + ` projections.idp_templates6_azure.idp_id,` + + ` projections.idp_templates6_azure.client_id,` + + ` projections.idp_templates6_azure.client_secret,` + + ` projections.idp_templates6_azure.scopes,` + + ` projections.idp_templates6_azure.tenant,` + + ` projections.idp_templates6_azure.is_email_verified,` + // github - ` projections.idp_templates5_github.idp_id,` + - ` projections.idp_templates5_github.client_id,` + - ` projections.idp_templates5_github.client_secret,` + - ` projections.idp_templates5_github.scopes,` + + ` projections.idp_templates6_github.idp_id,` + + ` projections.idp_templates6_github.client_id,` + + ` projections.idp_templates6_github.client_secret,` + + ` projections.idp_templates6_github.scopes,` + // github enterprise - ` projections.idp_templates5_github_enterprise.idp_id,` + - ` projections.idp_templates5_github_enterprise.client_id,` + - ` projections.idp_templates5_github_enterprise.client_secret,` + - ` projections.idp_templates5_github_enterprise.authorization_endpoint,` + - ` projections.idp_templates5_github_enterprise.token_endpoint,` + - ` projections.idp_templates5_github_enterprise.user_endpoint,` + - ` projections.idp_templates5_github_enterprise.scopes,` + + ` projections.idp_templates6_github_enterprise.idp_id,` + + ` projections.idp_templates6_github_enterprise.client_id,` + + ` projections.idp_templates6_github_enterprise.client_secret,` + + ` projections.idp_templates6_github_enterprise.authorization_endpoint,` + + ` projections.idp_templates6_github_enterprise.token_endpoint,` + + ` projections.idp_templates6_github_enterprise.user_endpoint,` + + ` projections.idp_templates6_github_enterprise.scopes,` + // gitlab - ` projections.idp_templates5_gitlab.idp_id,` + - ` projections.idp_templates5_gitlab.client_id,` + - ` projections.idp_templates5_gitlab.client_secret,` + - ` projections.idp_templates5_gitlab.scopes,` + + ` projections.idp_templates6_gitlab.idp_id,` + + ` projections.idp_templates6_gitlab.client_id,` + + ` projections.idp_templates6_gitlab.client_secret,` + + ` projections.idp_templates6_gitlab.scopes,` + // gitlab self hosted - ` projections.idp_templates5_gitlab_self_hosted.idp_id,` + - ` projections.idp_templates5_gitlab_self_hosted.issuer,` + - ` projections.idp_templates5_gitlab_self_hosted.client_id,` + - ` projections.idp_templates5_gitlab_self_hosted.client_secret,` + - ` projections.idp_templates5_gitlab_self_hosted.scopes,` + + ` projections.idp_templates6_gitlab_self_hosted.idp_id,` + + ` projections.idp_templates6_gitlab_self_hosted.issuer,` + + ` projections.idp_templates6_gitlab_self_hosted.client_id,` + + ` projections.idp_templates6_gitlab_self_hosted.client_secret,` + + ` projections.idp_templates6_gitlab_self_hosted.scopes,` + // google - ` projections.idp_templates5_google.idp_id,` + - ` projections.idp_templates5_google.client_id,` + - ` projections.idp_templates5_google.client_secret,` + - ` projections.idp_templates5_google.scopes,` + + ` projections.idp_templates6_google.idp_id,` + + ` projections.idp_templates6_google.client_id,` + + ` projections.idp_templates6_google.client_secret,` + + ` projections.idp_templates6_google.scopes,` + // saml - ` projections.idp_templates5_saml.idp_id,` + - ` projections.idp_templates5_saml.metadata,` + - ` projections.idp_templates5_saml.key,` + - ` projections.idp_templates5_saml.certificate,` + - ` projections.idp_templates5_saml.binding,` + - ` projections.idp_templates5_saml.with_signed_request,` + + ` projections.idp_templates6_saml.idp_id,` + + ` projections.idp_templates6_saml.metadata,` + + ` projections.idp_templates6_saml.key,` + + ` projections.idp_templates6_saml.certificate,` + + ` projections.idp_templates6_saml.binding,` + + ` projections.idp_templates6_saml.with_signed_request,` + // ldap - ` projections.idp_templates5_ldap2.idp_id,` + - ` projections.idp_templates5_ldap2.servers,` + - ` projections.idp_templates5_ldap2.start_tls,` + - ` projections.idp_templates5_ldap2.base_dn,` + - ` projections.idp_templates5_ldap2.bind_dn,` + - ` projections.idp_templates5_ldap2.bind_password,` + - ` projections.idp_templates5_ldap2.user_base,` + - ` projections.idp_templates5_ldap2.user_object_classes,` + - ` projections.idp_templates5_ldap2.user_filters,` + - ` projections.idp_templates5_ldap2.timeout,` + - ` projections.idp_templates5_ldap2.id_attribute,` + - ` projections.idp_templates5_ldap2.first_name_attribute,` + - ` projections.idp_templates5_ldap2.last_name_attribute,` + - ` projections.idp_templates5_ldap2.display_name_attribute,` + - ` projections.idp_templates5_ldap2.nick_name_attribute,` + - ` projections.idp_templates5_ldap2.preferred_username_attribute,` + - ` projections.idp_templates5_ldap2.email_attribute,` + - ` projections.idp_templates5_ldap2.email_verified,` + - ` projections.idp_templates5_ldap2.phone_attribute,` + - ` projections.idp_templates5_ldap2.phone_verified_attribute,` + - ` projections.idp_templates5_ldap2.preferred_language_attribute,` + - ` projections.idp_templates5_ldap2.avatar_url_attribute,` + - ` projections.idp_templates5_ldap2.profile_attribute,` + + ` projections.idp_templates6_ldap2.idp_id,` + + ` projections.idp_templates6_ldap2.servers,` + + ` projections.idp_templates6_ldap2.start_tls,` + + ` projections.idp_templates6_ldap2.base_dn,` + + ` projections.idp_templates6_ldap2.bind_dn,` + + ` projections.idp_templates6_ldap2.bind_password,` + + ` projections.idp_templates6_ldap2.user_base,` + + ` projections.idp_templates6_ldap2.user_object_classes,` + + ` projections.idp_templates6_ldap2.user_filters,` + + ` projections.idp_templates6_ldap2.timeout,` + + ` projections.idp_templates6_ldap2.id_attribute,` + + ` projections.idp_templates6_ldap2.first_name_attribute,` + + ` projections.idp_templates6_ldap2.last_name_attribute,` + + ` projections.idp_templates6_ldap2.display_name_attribute,` + + ` projections.idp_templates6_ldap2.nick_name_attribute,` + + ` projections.idp_templates6_ldap2.preferred_username_attribute,` + + ` projections.idp_templates6_ldap2.email_attribute,` + + ` projections.idp_templates6_ldap2.email_verified,` + + ` projections.idp_templates6_ldap2.phone_attribute,` + + ` projections.idp_templates6_ldap2.phone_verified_attribute,` + + ` projections.idp_templates6_ldap2.preferred_language_attribute,` + + ` projections.idp_templates6_ldap2.avatar_url_attribute,` + + ` projections.idp_templates6_ldap2.profile_attribute,` + // apple - ` projections.idp_templates5_apple.idp_id,` + - ` projections.idp_templates5_apple.client_id,` + - ` projections.idp_templates5_apple.team_id,` + - ` projections.idp_templates5_apple.key_id,` + - ` projections.idp_templates5_apple.private_key,` + - ` projections.idp_templates5_apple.scopes` + - ` FROM projections.idp_templates5` + - ` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` + - ` LEFT JOIN projections.idp_templates5_oidc ON projections.idp_templates5.id = projections.idp_templates5_oidc.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oidc.instance_id` + - ` LEFT JOIN projections.idp_templates5_jwt ON projections.idp_templates5.id = projections.idp_templates5_jwt.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_jwt.instance_id` + - ` LEFT JOIN projections.idp_templates5_azure ON projections.idp_templates5.id = projections.idp_templates5_azure.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_azure.instance_id` + - ` LEFT JOIN projections.idp_templates5_github ON projections.idp_templates5.id = projections.idp_templates5_github.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github.instance_id` + - ` LEFT JOIN projections.idp_templates5_github_enterprise ON projections.idp_templates5.id = projections.idp_templates5_github_enterprise.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github_enterprise.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab ON projections.idp_templates5.id = projections.idp_templates5_gitlab.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab_self_hosted ON projections.idp_templates5.id = projections.idp_templates5_gitlab_self_hosted.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab_self_hosted.instance_id` + - ` LEFT JOIN projections.idp_templates5_google ON projections.idp_templates5.id = projections.idp_templates5_google.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_google.instance_id` + - ` LEFT JOIN projections.idp_templates5_saml ON projections.idp_templates5.id = projections.idp_templates5_saml.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_saml.instance_id` + - ` LEFT JOIN projections.idp_templates5_ldap2 ON projections.idp_templates5.id = projections.idp_templates5_ldap2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_ldap2.instance_id` + - ` LEFT JOIN projections.idp_templates5_apple ON projections.idp_templates5.id = projections.idp_templates5_apple.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_apple.instance_id` + + ` projections.idp_templates6_apple.idp_id,` + + ` projections.idp_templates6_apple.client_id,` + + ` projections.idp_templates6_apple.team_id,` + + ` projections.idp_templates6_apple.key_id,` + + ` projections.idp_templates6_apple.private_key,` + + ` projections.idp_templates6_apple.scopes` + + ` FROM projections.idp_templates6` + + ` LEFT JOIN projections.idp_templates6_oauth2 ON projections.idp_templates6.id = projections.idp_templates6_oauth2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oauth2.instance_id` + + ` LEFT JOIN projections.idp_templates6_oidc ON projections.idp_templates6.id = projections.idp_templates6_oidc.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oidc.instance_id` + + ` LEFT JOIN projections.idp_templates6_jwt ON projections.idp_templates6.id = projections.idp_templates6_jwt.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_jwt.instance_id` + + ` LEFT JOIN projections.idp_templates6_azure ON projections.idp_templates6.id = projections.idp_templates6_azure.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_azure.instance_id` + + ` LEFT JOIN projections.idp_templates6_github ON projections.idp_templates6.id = projections.idp_templates6_github.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github.instance_id` + + ` LEFT JOIN projections.idp_templates6_github_enterprise ON projections.idp_templates6.id = projections.idp_templates6_github_enterprise.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github_enterprise.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab ON projections.idp_templates6.id = projections.idp_templates6_gitlab.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` + + ` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` + + ` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` + + ` LEFT JOIN projections.idp_templates6_ldap2 ON projections.idp_templates6.id = projections.idp_templates6_ldap2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap2.instance_id` + + ` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` idpTemplateCols = []string{ "id", @@ -153,6 +154,7 @@ var ( "is_linking_allowed", "is_auto_creation", "is_auto_update", + "auto_linking", // oauth config "idp_id", "client_id", @@ -250,129 +252,130 @@ var ( "private_key", "scopes", } - idpTemplatesQuery = `SELECT projections.idp_templates5.id,` + - ` projections.idp_templates5.resource_owner,` + - ` projections.idp_templates5.creation_date,` + - ` projections.idp_templates5.change_date,` + - ` projections.idp_templates5.sequence,` + - ` projections.idp_templates5.state,` + - ` projections.idp_templates5.name,` + - ` projections.idp_templates5.type,` + - ` projections.idp_templates5.owner_type,` + - ` projections.idp_templates5.is_creation_allowed,` + - ` projections.idp_templates5.is_linking_allowed,` + - ` projections.idp_templates5.is_auto_creation,` + - ` projections.idp_templates5.is_auto_update,` + + idpTemplatesQuery = `SELECT projections.idp_templates6.id,` + + ` projections.idp_templates6.resource_owner,` + + ` projections.idp_templates6.creation_date,` + + ` projections.idp_templates6.change_date,` + + ` projections.idp_templates6.sequence,` + + ` projections.idp_templates6.state,` + + ` projections.idp_templates6.name,` + + ` projections.idp_templates6.type,` + + ` projections.idp_templates6.owner_type,` + + ` projections.idp_templates6.is_creation_allowed,` + + ` projections.idp_templates6.is_linking_allowed,` + + ` projections.idp_templates6.is_auto_creation,` + + ` projections.idp_templates6.is_auto_update,` + + ` projections.idp_templates6.auto_linking,` + // oauth - ` projections.idp_templates5_oauth2.idp_id,` + - ` projections.idp_templates5_oauth2.client_id,` + - ` projections.idp_templates5_oauth2.client_secret,` + - ` projections.idp_templates5_oauth2.authorization_endpoint,` + - ` projections.idp_templates5_oauth2.token_endpoint,` + - ` projections.idp_templates5_oauth2.user_endpoint,` + - ` projections.idp_templates5_oauth2.scopes,` + - ` projections.idp_templates5_oauth2.id_attribute,` + + ` projections.idp_templates6_oauth2.idp_id,` + + ` projections.idp_templates6_oauth2.client_id,` + + ` projections.idp_templates6_oauth2.client_secret,` + + ` projections.idp_templates6_oauth2.authorization_endpoint,` + + ` projections.idp_templates6_oauth2.token_endpoint,` + + ` projections.idp_templates6_oauth2.user_endpoint,` + + ` projections.idp_templates6_oauth2.scopes,` + + ` projections.idp_templates6_oauth2.id_attribute,` + // oidc - ` projections.idp_templates5_oidc.idp_id,` + - ` projections.idp_templates5_oidc.issuer,` + - ` projections.idp_templates5_oidc.client_id,` + - ` projections.idp_templates5_oidc.client_secret,` + - ` projections.idp_templates5_oidc.scopes,` + - ` projections.idp_templates5_oidc.id_token_mapping,` + + ` projections.idp_templates6_oidc.idp_id,` + + ` projections.idp_templates6_oidc.issuer,` + + ` projections.idp_templates6_oidc.client_id,` + + ` projections.idp_templates6_oidc.client_secret,` + + ` projections.idp_templates6_oidc.scopes,` + + ` projections.idp_templates6_oidc.id_token_mapping,` + // jwt - ` projections.idp_templates5_jwt.idp_id,` + - ` projections.idp_templates5_jwt.issuer,` + - ` projections.idp_templates5_jwt.jwt_endpoint,` + - ` projections.idp_templates5_jwt.keys_endpoint,` + - ` projections.idp_templates5_jwt.header_name,` + + ` projections.idp_templates6_jwt.idp_id,` + + ` projections.idp_templates6_jwt.issuer,` + + ` projections.idp_templates6_jwt.jwt_endpoint,` + + ` projections.idp_templates6_jwt.keys_endpoint,` + + ` projections.idp_templates6_jwt.header_name,` + // azure - ` projections.idp_templates5_azure.idp_id,` + - ` projections.idp_templates5_azure.client_id,` + - ` projections.idp_templates5_azure.client_secret,` + - ` projections.idp_templates5_azure.scopes,` + - ` projections.idp_templates5_azure.tenant,` + - ` projections.idp_templates5_azure.is_email_verified,` + + ` projections.idp_templates6_azure.idp_id,` + + ` projections.idp_templates6_azure.client_id,` + + ` projections.idp_templates6_azure.client_secret,` + + ` projections.idp_templates6_azure.scopes,` + + ` projections.idp_templates6_azure.tenant,` + + ` projections.idp_templates6_azure.is_email_verified,` + // github - ` projections.idp_templates5_github.idp_id,` + - ` projections.idp_templates5_github.client_id,` + - ` projections.idp_templates5_github.client_secret,` + - ` projections.idp_templates5_github.scopes,` + + ` projections.idp_templates6_github.idp_id,` + + ` projections.idp_templates6_github.client_id,` + + ` projections.idp_templates6_github.client_secret,` + + ` projections.idp_templates6_github.scopes,` + // github enterprise - ` projections.idp_templates5_github_enterprise.idp_id,` + - ` projections.idp_templates5_github_enterprise.client_id,` + - ` projections.idp_templates5_github_enterprise.client_secret,` + - ` projections.idp_templates5_github_enterprise.authorization_endpoint,` + - ` projections.idp_templates5_github_enterprise.token_endpoint,` + - ` projections.idp_templates5_github_enterprise.user_endpoint,` + - ` projections.idp_templates5_github_enterprise.scopes,` + + ` projections.idp_templates6_github_enterprise.idp_id,` + + ` projections.idp_templates6_github_enterprise.client_id,` + + ` projections.idp_templates6_github_enterprise.client_secret,` + + ` projections.idp_templates6_github_enterprise.authorization_endpoint,` + + ` projections.idp_templates6_github_enterprise.token_endpoint,` + + ` projections.idp_templates6_github_enterprise.user_endpoint,` + + ` projections.idp_templates6_github_enterprise.scopes,` + // gitlab - ` projections.idp_templates5_gitlab.idp_id,` + - ` projections.idp_templates5_gitlab.client_id,` + - ` projections.idp_templates5_gitlab.client_secret,` + - ` projections.idp_templates5_gitlab.scopes,` + + ` projections.idp_templates6_gitlab.idp_id,` + + ` projections.idp_templates6_gitlab.client_id,` + + ` projections.idp_templates6_gitlab.client_secret,` + + ` projections.idp_templates6_gitlab.scopes,` + // gitlab self hosted - ` projections.idp_templates5_gitlab_self_hosted.idp_id,` + - ` projections.idp_templates5_gitlab_self_hosted.issuer,` + - ` projections.idp_templates5_gitlab_self_hosted.client_id,` + - ` projections.idp_templates5_gitlab_self_hosted.client_secret,` + - ` projections.idp_templates5_gitlab_self_hosted.scopes,` + + ` projections.idp_templates6_gitlab_self_hosted.idp_id,` + + ` projections.idp_templates6_gitlab_self_hosted.issuer,` + + ` projections.idp_templates6_gitlab_self_hosted.client_id,` + + ` projections.idp_templates6_gitlab_self_hosted.client_secret,` + + ` projections.idp_templates6_gitlab_self_hosted.scopes,` + // google - ` projections.idp_templates5_google.idp_id,` + - ` projections.idp_templates5_google.client_id,` + - ` projections.idp_templates5_google.client_secret,` + - ` projections.idp_templates5_google.scopes,` + + ` projections.idp_templates6_google.idp_id,` + + ` projections.idp_templates6_google.client_id,` + + ` projections.idp_templates6_google.client_secret,` + + ` projections.idp_templates6_google.scopes,` + // saml - ` projections.idp_templates5_saml.idp_id,` + - ` projections.idp_templates5_saml.metadata,` + - ` projections.idp_templates5_saml.key,` + - ` projections.idp_templates5_saml.certificate,` + - ` projections.idp_templates5_saml.binding,` + - ` projections.idp_templates5_saml.with_signed_request,` + + ` projections.idp_templates6_saml.idp_id,` + + ` projections.idp_templates6_saml.metadata,` + + ` projections.idp_templates6_saml.key,` + + ` projections.idp_templates6_saml.certificate,` + + ` projections.idp_templates6_saml.binding,` + + ` projections.idp_templates6_saml.with_signed_request,` + // ldap - ` projections.idp_templates5_ldap2.idp_id,` + - ` projections.idp_templates5_ldap2.servers,` + - ` projections.idp_templates5_ldap2.start_tls,` + - ` projections.idp_templates5_ldap2.base_dn,` + - ` projections.idp_templates5_ldap2.bind_dn,` + - ` projections.idp_templates5_ldap2.bind_password,` + - ` projections.idp_templates5_ldap2.user_base,` + - ` projections.idp_templates5_ldap2.user_object_classes,` + - ` projections.idp_templates5_ldap2.user_filters,` + - ` projections.idp_templates5_ldap2.timeout,` + - ` projections.idp_templates5_ldap2.id_attribute,` + - ` projections.idp_templates5_ldap2.first_name_attribute,` + - ` projections.idp_templates5_ldap2.last_name_attribute,` + - ` projections.idp_templates5_ldap2.display_name_attribute,` + - ` projections.idp_templates5_ldap2.nick_name_attribute,` + - ` projections.idp_templates5_ldap2.preferred_username_attribute,` + - ` projections.idp_templates5_ldap2.email_attribute,` + - ` projections.idp_templates5_ldap2.email_verified,` + - ` projections.idp_templates5_ldap2.phone_attribute,` + - ` projections.idp_templates5_ldap2.phone_verified_attribute,` + - ` projections.idp_templates5_ldap2.preferred_language_attribute,` + - ` projections.idp_templates5_ldap2.avatar_url_attribute,` + - ` projections.idp_templates5_ldap2.profile_attribute,` + + ` projections.idp_templates6_ldap2.idp_id,` + + ` projections.idp_templates6_ldap2.servers,` + + ` projections.idp_templates6_ldap2.start_tls,` + + ` projections.idp_templates6_ldap2.base_dn,` + + ` projections.idp_templates6_ldap2.bind_dn,` + + ` projections.idp_templates6_ldap2.bind_password,` + + ` projections.idp_templates6_ldap2.user_base,` + + ` projections.idp_templates6_ldap2.user_object_classes,` + + ` projections.idp_templates6_ldap2.user_filters,` + + ` projections.idp_templates6_ldap2.timeout,` + + ` projections.idp_templates6_ldap2.id_attribute,` + + ` projections.idp_templates6_ldap2.first_name_attribute,` + + ` projections.idp_templates6_ldap2.last_name_attribute,` + + ` projections.idp_templates6_ldap2.display_name_attribute,` + + ` projections.idp_templates6_ldap2.nick_name_attribute,` + + ` projections.idp_templates6_ldap2.preferred_username_attribute,` + + ` projections.idp_templates6_ldap2.email_attribute,` + + ` projections.idp_templates6_ldap2.email_verified,` + + ` projections.idp_templates6_ldap2.phone_attribute,` + + ` projections.idp_templates6_ldap2.phone_verified_attribute,` + + ` projections.idp_templates6_ldap2.preferred_language_attribute,` + + ` projections.idp_templates6_ldap2.avatar_url_attribute,` + + ` projections.idp_templates6_ldap2.profile_attribute,` + // apple - ` projections.idp_templates5_apple.idp_id,` + - ` projections.idp_templates5_apple.client_id,` + - ` projections.idp_templates5_apple.team_id,` + - ` projections.idp_templates5_apple.key_id,` + - ` projections.idp_templates5_apple.private_key,` + - ` projections.idp_templates5_apple.scopes,` + + ` projections.idp_templates6_apple.idp_id,` + + ` projections.idp_templates6_apple.client_id,` + + ` projections.idp_templates6_apple.team_id,` + + ` projections.idp_templates6_apple.key_id,` + + ` projections.idp_templates6_apple.private_key,` + + ` projections.idp_templates6_apple.scopes,` + ` COUNT(*) OVER ()` + - ` FROM projections.idp_templates5` + - ` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` + - ` LEFT JOIN projections.idp_templates5_oidc ON projections.idp_templates5.id = projections.idp_templates5_oidc.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oidc.instance_id` + - ` LEFT JOIN projections.idp_templates5_jwt ON projections.idp_templates5.id = projections.idp_templates5_jwt.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_jwt.instance_id` + - ` LEFT JOIN projections.idp_templates5_azure ON projections.idp_templates5.id = projections.idp_templates5_azure.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_azure.instance_id` + - ` LEFT JOIN projections.idp_templates5_github ON projections.idp_templates5.id = projections.idp_templates5_github.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github.instance_id` + - ` LEFT JOIN projections.idp_templates5_github_enterprise ON projections.idp_templates5.id = projections.idp_templates5_github_enterprise.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github_enterprise.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab ON projections.idp_templates5.id = projections.idp_templates5_gitlab.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab_self_hosted ON projections.idp_templates5.id = projections.idp_templates5_gitlab_self_hosted.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab_self_hosted.instance_id` + - ` LEFT JOIN projections.idp_templates5_google ON projections.idp_templates5.id = projections.idp_templates5_google.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_google.instance_id` + - ` LEFT JOIN projections.idp_templates5_saml ON projections.idp_templates5.id = projections.idp_templates5_saml.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_saml.instance_id` + - ` LEFT JOIN projections.idp_templates5_ldap2 ON projections.idp_templates5.id = projections.idp_templates5_ldap2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_ldap2.instance_id` + - ` LEFT JOIN projections.idp_templates5_apple ON projections.idp_templates5.id = projections.idp_templates5_apple.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_apple.instance_id` + + ` FROM projections.idp_templates6` + + ` LEFT JOIN projections.idp_templates6_oauth2 ON projections.idp_templates6.id = projections.idp_templates6_oauth2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oauth2.instance_id` + + ` LEFT JOIN projections.idp_templates6_oidc ON projections.idp_templates6.id = projections.idp_templates6_oidc.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oidc.instance_id` + + ` LEFT JOIN projections.idp_templates6_jwt ON projections.idp_templates6.id = projections.idp_templates6_jwt.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_jwt.instance_id` + + ` LEFT JOIN projections.idp_templates6_azure ON projections.idp_templates6.id = projections.idp_templates6_azure.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_azure.instance_id` + + ` LEFT JOIN projections.idp_templates6_github ON projections.idp_templates6.id = projections.idp_templates6_github.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github.instance_id` + + ` LEFT JOIN projections.idp_templates6_github_enterprise ON projections.idp_templates6.id = projections.idp_templates6_github_enterprise.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github_enterprise.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab ON projections.idp_templates6.id = projections.idp_templates6_gitlab.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` + + ` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` + + ` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` + + ` LEFT JOIN projections.idp_templates6_ldap2 ON projections.idp_templates6.id = projections.idp_templates6_ldap2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap2.instance_id` + + ` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` idpTemplatesCols = []string{ "id", @@ -388,6 +391,7 @@ var ( "is_linking_allowed", "is_auto_creation", "is_auto_update", + "auto_linking", // oauth config "idp_id", "client_id", @@ -538,6 +542,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth "idp-id", "client_id", @@ -651,6 +656,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OAuthIDPTemplate: &OAuthIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -684,6 +690,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -797,6 +804,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OIDCIDPTemplate: &OIDCIDPTemplate{ IDPID: "idp-id", Issuer: "issuer", @@ -828,6 +836,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -941,6 +950,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, JWTIDPTemplate: &JWTIDPTemplate{ IDPID: "idp-id", Issuer: "issuer", @@ -971,6 +981,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1084,6 +1095,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GitHubIDPTemplate: &GitHubIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1113,6 +1125,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1226,6 +1239,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GitLabIDPTemplate: &GitLabIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1255,6 +1269,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1368,6 +1383,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GitLabSelfHostedIDPTemplate: &GitLabSelfHostedIDPTemplate{ IDPID: "idp-id", Issuer: "issuer", @@ -1398,6 +1414,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1511,6 +1528,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GoogleIDPTemplate: &GoogleIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1540,6 +1558,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1653,6 +1672,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, SAMLIDPTemplate: &SAMLIDPTemplate{ IDPID: "idp-id", Metadata: []byte("metadata"), @@ -1684,6 +1704,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1797,6 +1818,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, LDAPIDPTemplate: &LDAPIDPTemplate{ IDPID: "idp-id", Servers: []string{"server"}, @@ -1846,6 +1868,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1959,6 +1982,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, AppleIDPTemplate: &AppleIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1990,6 +2014,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2103,6 +2128,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, }, }, { @@ -2162,6 +2188,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2281,6 +2308,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, LDAPIDPTemplate: &LDAPIDPTemplate{ IDPID: "idp-id", Servers: []string{"server"}, @@ -2333,6 +2361,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2452,6 +2481,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, }, }, }, @@ -2478,6 +2508,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2589,6 +2620,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2700,6 +2732,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2811,6 +2844,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth "idp-id-oauth", "client_id", @@ -2922,6 +2956,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -3033,6 +3068,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -3152,6 +3188,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, LDAPIDPTemplate: &LDAPIDPTemplate{ IDPID: "idp-id-ldap", Servers: []string{"server"}, @@ -3193,6 +3230,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, SAMLIDPTemplate: &SAMLIDPTemplate{ IDPID: "idp-id-saml", Metadata: []byte("metadata"), @@ -3216,6 +3254,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GoogleIDPTemplate: &GoogleIDPTemplate{ IDPID: "idp-id-google", ClientID: "client_id", @@ -3238,6 +3277,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OAuthIDPTemplate: &OAuthIDPTemplate{ IDPID: "idp-id-oauth", ClientID: "client_id", @@ -3263,6 +3303,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OIDCIDPTemplate: &OIDCIDPTemplate{ IDPID: "idp-id-oidc", Issuer: "issuer", @@ -3286,6 +3327,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, JWTIDPTemplate: &JWTIDPTemplate{ IDPID: "idp-id-jwt", Issuer: "issuer", diff --git a/internal/query/idp_user_link_test.go b/internal/query/idp_user_link_test.go index af4e3a54d7..bcbe6c2062 100644 --- a/internal/query/idp_user_link_test.go +++ b/internal/query/idp_user_link_test.go @@ -14,14 +14,14 @@ import ( var ( idpUserLinksQuery = regexp.QuoteMeta(`SELECT projections.idp_user_links3.idp_id,` + ` projections.idp_user_links3.user_id,` + - ` projections.idp_templates5.name,` + + ` projections.idp_templates6.name,` + ` projections.idp_user_links3.external_user_id,` + ` projections.idp_user_links3.display_name,` + - ` projections.idp_templates5.type,` + + ` projections.idp_templates6.type,` + ` projections.idp_user_links3.resource_owner,` + ` COUNT(*) OVER ()` + ` FROM projections.idp_user_links3` + - ` LEFT JOIN projections.idp_templates5 ON projections.idp_user_links3.idp_id = projections.idp_templates5.id AND projections.idp_user_links3.instance_id = projections.idp_templates5.instance_id` + + ` LEFT JOIN projections.idp_templates6 ON projections.idp_user_links3.idp_id = projections.idp_templates6.id AND projections.idp_user_links3.instance_id = projections.idp_templates6.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) idpUserLinksCols = []string{ "idp_id", diff --git a/internal/query/instance_features.go b/internal/query/instance_features.go index ed806f355a..a362b6a5ad 100644 --- a/internal/query/instance_features.go +++ b/internal/query/instance_features.go @@ -13,6 +13,7 @@ type InstanceFeatures struct { LegacyIntrospection FeatureSource[bool] UserSchema FeatureSource[bool] TokenExchange FeatureSource[bool] + Actions FeatureSource[bool] } func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) { diff --git a/internal/query/instance_features_model.go b/internal/query/instance_features_model.go index 50beb1e36a..068e56a23c 100644 --- a/internal/query/instance_features_model.go +++ b/internal/query/instance_features_model.go @@ -62,6 +62,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder { feature_v2.InstanceLegacyIntrospectionEventType, feature_v2.InstanceUserSchemaEventType, feature_v2.InstanceTokenExchangeEventType, + feature_v2.InstanceActionsEventType, ). Builder().ResourceOwner(m.ResourceOwner) } @@ -75,6 +76,7 @@ func (m *InstanceFeaturesReadModel) reduceReset() { m.instance.LegacyIntrospection = FeatureSource[bool]{} m.instance.UserSchema = FeatureSource[bool]{} m.instance.TokenExchange = FeatureSource[bool]{} + m.instance.Actions = FeatureSource[bool]{} } func (m *InstanceFeaturesReadModel) populateFromSystem() bool { @@ -86,6 +88,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool { m.instance.LegacyIntrospection = m.system.LegacyIntrospection m.instance.UserSchema = m.system.UserSchema m.instance.TokenExchange = m.system.TokenExchange + m.instance.Actions = m.system.Actions return true } @@ -109,6 +112,8 @@ func (m *InstanceFeaturesReadModel) reduceBoolFeature(event *feature_v2.SetEvent dst = &m.instance.UserSchema case feature.KeyTokenExchange: dst = &m.instance.TokenExchange + case feature.KeyActions: + dst = &m.instance.Actions } *dst = FeatureSource[bool]{ Level: level, diff --git a/internal/query/instance_features_test.go b/internal/query/instance_features_test.go index 010a4d42e4..e182f4002f 100644 --- a/internal/query/instance_features_test.go +++ b/internal/query/instance_features_test.go @@ -105,6 +105,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) { ctx, aggregate, feature_v2.InstanceUserSchemaEventType, false, )), + eventFromEventPusher(feature_v2.NewSetEvent[bool]( + ctx, aggregate, + feature_v2.InstanceActionsEventType, false, + )), ), ), args: args{true}, @@ -128,6 +132,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) { Level: feature.LevelInstance, Value: false, }, + Actions: FeatureSource[bool]{ + Level: feature.LevelInstance, + Value: false, + }, }, }, { @@ -154,6 +162,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) { ctx, aggregate, feature_v2.InstanceUserSchemaEventType, false, )), + eventFromEventPusher(feature_v2.NewSetEvent[bool]( + ctx, aggregate, + feature_v2.InstanceActionsEventType, false, + )), eventFromEventPusher(feature_v2.NewResetEvent( ctx, aggregate, feature_v2.InstanceResetEventType, @@ -185,6 +197,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) { Level: feature.LevelUnspecified, Value: false, }, + Actions: FeatureSource[bool]{ + Level: feature.LevelUnspecified, + Value: false, + }, }, }, { @@ -207,6 +223,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) { ctx, aggregate, feature_v2.InstanceUserSchemaEventType, false, )), + eventFromEventPusher(feature_v2.NewSetEvent[bool]( + ctx, aggregate, + feature_v2.InstanceActionsEventType, false, + )), eventFromEventPusher(feature_v2.NewResetEvent( ctx, aggregate, feature_v2.InstanceResetEventType, @@ -238,6 +258,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) { Level: feature.LevelUnspecified, Value: false, }, + Actions: FeatureSource[bool]{ + Level: feature.LevelUnspecified, + Value: false, + }, }, }, } diff --git a/internal/query/introspection.go b/internal/query/introspection.go index 0e190da25d..a7fdaab718 100644 --- a/internal/query/introspection.go +++ b/internal/query/introspection.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/query/projection" @@ -26,18 +25,31 @@ var introspectionTriggerHandlers = sync.OnceValue(func() []*handler.Handler { ) }) +// TriggerIntrospectionProjections triggers all projections +// relevant to introspection queries concurrently. func TriggerIntrospectionProjections(ctx context.Context) { triggerBatch(ctx, introspectionTriggerHandlers()...) } +type AppType string + +const ( + AppTypeAPI = "api" + AppTypeOIDC = "oidc" +) + type IntrospectionClient struct { - ClientID string - ClientSecret *crypto.CryptoValue - ProjectID string - PublicKeys database.Map[[]byte] + AppID string + ClientID string + HashedSecret string + AppType AppType + ProjectID string + ResourceOwner string + ProjectRoleAssertion bool + PublicKeys database.Map[[]byte] } -//go:embed embed/introspection_client_by_id.sql +//go:embed introspection_client_by_id.sql var introspectionClientByIDQuery string func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID string, getKeys bool) (_ *IntrospectionClient, err error) { @@ -50,7 +62,16 @@ func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID strin ) err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { - return row.Scan(&client.ClientID, &client.ClientSecret, &client.ProjectID, &client.PublicKeys) + return row.Scan( + &client.AppID, + &client.ClientID, + &client.HashedSecret, + &client.AppType, + &client.ProjectID, + &client.ResourceOwner, + &client.ProjectRoleAssertion, + &client.PublicKeys, + ) }, introspectionClientByIDQuery, instanceID, clientID, getKeys, diff --git a/internal/query/introspection_client_by_id.sql b/internal/query/introspection_client_by_id.sql new file mode 100644 index 0000000000..5129d99a70 --- /dev/null +++ b/internal/query/introspection_client_by_id.sql @@ -0,0 +1,25 @@ +with config as ( + select instance_id, app_id, client_id, client_secret, 'api' as app_type + from projections.apps7_api_configs + where instance_id = $1 + and client_id = $2 + union + select instance_id, app_id, client_id, client_secret, 'oidc' as app_type + from projections.apps7_oidc_configs + where instance_id = $1 + and client_id = $2 +), +keys as ( + select identifier as client_id, json_object_agg(id, encode(public_key, 'base64')) as public_keys + from projections.authn_keys2 + where $3 = true -- when argument is false, don't waste time on trying to query for keys. + and instance_id = $1 + and identifier = $2 + and expiration > current_timestamp + group by identifier +) +select config.app_id, config.client_id, config.client_secret, config.app_type, apps.project_id, apps.resource_owner, p.project_role_assertion, keys.public_keys +from config +join projections.apps7 apps on apps.id = config.app_id and apps.instance_id = config.instance_id +join projections.projects4 p on p.id = apps.project_id and p.instance_id = $1 +left join keys on keys.client_id = config.client_id; diff --git a/internal/query/introspection_test.go b/internal/query/introspection_test.go index d666adc51f..6535bd1639 100644 --- a/internal/query/introspection_test.go +++ b/internal/query/introspection_test.go @@ -4,7 +4,6 @@ import ( "database/sql" "database/sql/driver" _ "embed" - "encoding/json" "regexp" "testing" @@ -12,20 +11,10 @@ import ( "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" ) func TestQueries_GetIntrospectionClientByID(t *testing.T) { - secret := &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "alg", - KeyID: "keyID", - Crypted: []byte("secret"), - } - encSecret, err := json.Marshal(secret) - require.NoError(t, err) - pubkeys := database.Map[[]byte]{ "key1": {1, 2, 3}, "key2": {4, 5, 6}, @@ -61,14 +50,18 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) { getKeys: false, }, mock: mockQuery(expQuery, - []string{"client_id", "client_secret", "project_id", "public_keys"}, - []driver.Value{"clientID", encSecret, "projectID", nil}, + []string{"app_id", "client_id", "client_secret", "app_type", "project_id", "resource_owner", "project_role_assertion", "public_keys"}, + []driver.Value{"appID", "clientID", "secret", "oidc", "projectID", "orgID", true, nil}, "instanceID", "clientID", false), want: &IntrospectionClient{ - ClientID: "clientID", - ClientSecret: secret, - ProjectID: "projectID", - PublicKeys: nil, + AppID: "appID", + ClientID: "clientID", + HashedSecret: "secret", + AppType: AppTypeOIDC, + ProjectID: "projectID", + ResourceOwner: "orgID", + ProjectRoleAssertion: true, + PublicKeys: nil, }, }, { @@ -78,14 +71,18 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) { getKeys: true, }, mock: mockQuery(expQuery, - []string{"client_id", "client_secret", "project_id", "public_keys"}, - []driver.Value{"clientID", nil, "projectID", encPubkeys}, + []string{"app_id", "client_id", "client_secret", "app_type", "project_id", "resource_owner", "project_role_assertion", "public_keys"}, + []driver.Value{"appID", "clientID", "", "oidc", "projectID", "orgID", true, encPubkeys}, "instanceID", "clientID", true), want: &IntrospectionClient{ - ClientID: "clientID", - ClientSecret: nil, - ProjectID: "projectID", - PublicKeys: pubkeys, + AppID: "appID", + ClientID: "clientID", + HashedSecret: "", + AppType: AppTypeOIDC, + ProjectID: "projectID", + ResourceOwner: "orgID", + ProjectRoleAssertion: true, + PublicKeys: pubkeys, }, }, } diff --git a/internal/query/lockout_policy.go b/internal/query/lockout_policy.go index e84750cc6d..64be9b0b76 100644 --- a/internal/query/lockout_policy.go +++ b/internal/query/lockout_policy.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" @@ -28,6 +27,7 @@ type LockoutPolicy struct { State domain.PolicyState MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShowFailures bool IsDefault bool @@ -70,6 +70,10 @@ var ( name: projection.LockoutPolicyMaxPasswordAttemptsCol, table: lockoutTable, } + LockoutColMaxOTPAttempts = Column{ + name: projection.LockoutPolicyMaxOTPAttemptsCol, + table: lockoutTable, + } LockoutColIsDefault = Column{ name: projection.LockoutPolicyIsDefaultCol, table: lockoutTable, @@ -78,13 +82,9 @@ var ( name: projection.LockoutPolicyStateCol, table: lockoutTable, } - LockoutPolicyOwnerRemoved = Column{ - name: projection.LockoutPolicyOwnerRemovedCol, - table: lockoutTable, - } ) -func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (policy *LockoutPolicy, err error) { +func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string) (policy *LockoutPolicy, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -97,9 +97,6 @@ func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool eq := sq.Eq{ LockoutColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[LockoutPolicyOwnerRemoved.identifier()] = false - } stmt, scan := prepareLockoutPolicyQuery(ctx, q.client) query, args, err := stmt.Where( @@ -154,6 +151,7 @@ func prepareLockoutPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele LockoutColResourceOwner.identifier(), LockoutColShowFailures.identifier(), LockoutColMaxPasswordAttempts.identifier(), + LockoutColMaxOTPAttempts.identifier(), LockoutColIsDefault.identifier(), LockoutColState.identifier(), ). @@ -169,6 +167,7 @@ func prepareLockoutPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele &policy.ResourceOwner, &policy.ShowFailures, &policy.MaxPasswordAttempts, + &policy.MaxOTPAttempts, &policy.IsDefault, &policy.State, ) diff --git a/internal/query/lockout_policy_test.go b/internal/query/lockout_policy_test.go index 044f6c291b..2805ef8fdc 100644 --- a/internal/query/lockout_policy_test.go +++ b/internal/query/lockout_policy_test.go @@ -13,16 +13,17 @@ import ( ) var ( - prepareLockoutPolicyStmt = `SELECT projections.lockout_policies2.id,` + - ` projections.lockout_policies2.sequence,` + - ` projections.lockout_policies2.creation_date,` + - ` projections.lockout_policies2.change_date,` + - ` projections.lockout_policies2.resource_owner,` + - ` projections.lockout_policies2.show_failure,` + - ` projections.lockout_policies2.max_password_attempts,` + - ` projections.lockout_policies2.is_default,` + - ` projections.lockout_policies2.state` + - ` FROM projections.lockout_policies2` + + prepareLockoutPolicyStmt = `SELECT projections.lockout_policies3.id,` + + ` projections.lockout_policies3.sequence,` + + ` projections.lockout_policies3.creation_date,` + + ` projections.lockout_policies3.change_date,` + + ` projections.lockout_policies3.resource_owner,` + + ` projections.lockout_policies3.show_failure,` + + ` projections.lockout_policies3.max_password_attempts,` + + ` projections.lockout_policies3.max_otp_attempts,` + + ` projections.lockout_policies3.is_default,` + + ` projections.lockout_policies3.state` + + ` FROM projections.lockout_policies3` + ` AS OF SYSTEM TIME '-1 ms'` prepareLockoutPolicyCols = []string{ @@ -33,6 +34,7 @@ var ( "resource_owner", "show_failure", "max_password_attempts", + "max_otp_attempts", "is_default", "state", } @@ -82,6 +84,7 @@ func Test_LockoutPolicyPrepares(t *testing.T) { "ro", true, 20, + 20, true, domain.PolicyStateActive, }, @@ -96,6 +99,7 @@ func Test_LockoutPolicyPrepares(t *testing.T) { State: domain.PolicyStateActive, ShowFailures: true, MaxPasswordAttempts: 20, + MaxOTPAttempts: 20, IsDefault: true, }, }, diff --git a/internal/query/login_policy.go b/internal/query/login_policy.go index 2eb3ecf39b..5ab54cfa55 100644 --- a/internal/query/login_policy.go +++ b/internal/query/login_policy.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/member.go b/internal/query/member.go index c343ea65a5..2c4b4db5fe 100644 --- a/internal/query/member.go +++ b/internal/query/member.go @@ -3,10 +3,10 @@ package query import ( "time" + sq "github.com/Masterminds/squirrel" + "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" - - sq "github.com/Masterminds/squirrel" ) type MembersQuery struct { diff --git a/internal/query/oidc_client.go b/internal/query/oidc_client.go index 7157f9e6ef..6669b398b5 100644 --- a/internal/query/oidc_client.go +++ b/internal/query/oidc_client.go @@ -8,7 +8,6 @@ import ( "time" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/telemetry/tracing" @@ -20,7 +19,7 @@ type OIDCClient struct { AppID string `json:"app_id,omitempty"` State domain.AppState `json:"state,omitempty"` ClientID string `json:"client_id,omitempty"` - ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"` + HashedSecret string `json:"client_secret,omitempty"` RedirectURIs []string `json:"redirect_uris,omitempty"` ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"` GrantTypes []domain.OIDCGrantType `json:"grant_types,omitempty"` @@ -36,11 +35,12 @@ type OIDCClient struct { AdditionalOrigins []string `json:"additional_origins,omitempty"` PublicKeys map[string][]byte `json:"public_keys,omitempty"` ProjectID string `json:"project_id,omitempty"` + ProjectRoleAssertion bool `json:"project_role_assertion,omitempty"` ProjectRoleKeys []string `json:"project_role_keys,omitempty"` Settings *OIDCSettings `json:"settings,omitempty"` } -//go:embed embed/oidc_client_by_id.sql +//go:embed oidc_client_by_id.sql var oidcClientQuery string func (q *Queries) GetOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) { diff --git a/internal/query/embed/oidc_client_by_id.sql b/internal/query/oidc_client_by_id.sql similarity index 78% rename from internal/query/embed/oidc_client_by_id.sql rename to internal/query/oidc_client_by_id.sql index 8759b513d9..3a0a0a0c95 100644 --- a/internal/query/embed/oidc_client_by_id.sql +++ b/internal/query/oidc_client_by_id.sql @@ -1,14 +1,12 @@ ---deallocate q; ---prepare q(text, text, boolean) as - with client as ( select c.instance_id, - c.app_id, c.client_id, c.client_secret, c.redirect_uris, c.response_types, c.grant_types, + c.app_id, a.state, c.client_id, c.client_secret, c.redirect_uris, c.response_types, c.grant_types, c.application_type, c.auth_method_type, c.post_logout_redirect_uris, c.is_dev_mode, c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion, - c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, a.state - from projections.apps6_oidc_configs c - join projections.apps6 a on a.id = c.app_id and a.instance_id = c.instance_id + c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion + from projections.apps7_oidc_configs c + join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id + join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id where c.instance_id = $1 and c.client_id = $2 ), @@ -45,7 +43,5 @@ select row_to_json(r) as client from ( from client c left join roles r on r.project_id = c.project_id left join keys k on k.client_id = c.client_id - left join settings s on s.instance_id = s.instance_id + left join settings s on s.instance_id = c.instance_id ) r; - ---execute q('230690539048009730', '236647088211951618@tests', true); \ No newline at end of file diff --git a/internal/query/oidc_client_test.go b/internal/query/oidc_client_test.go index 44f642ccea..93bd428015 100644 --- a/internal/query/oidc_client_test.go +++ b/internal/query/oidc_client_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" @@ -66,7 +65,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AppID: "236647088211886082", State: domain.AppStateActive, ClientID: "236647088211951618@tests", - ClientSecret: nil, + HashedSecret: "", RedirectURIs: []string{"http://localhost:9999/auth/callback"}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode, domain.OIDCGrantTypeRefreshToken}, @@ -81,6 +80,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx ClockSkew: 1000000000, AdditionalOrigins: []string{"https://example.com"}, ProjectID: "236645808328409090", + ProjectRoleAssertion: true, PublicKeys: map[string][]byte{"236647201860747266": []byte(pubkey)}, ProjectRoleKeys: []string{"role1", "role2"}, Settings: &OIDCSettings{ @@ -97,7 +97,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AppID: "236646457053020162", State: domain.AppStateActive, ClientID: "236646457053085698@tests", - ClientSecret: nil, + HashedSecret: "", RedirectURIs: []string{"http://localhost:9999/auth/callback"}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -113,6 +113,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AdditionalOrigins: nil, PublicKeys: nil, ProjectID: "236645808328409090", + ProjectRoleAssertion: true, ProjectRoleKeys: []string{"role1", "role2"}, Settings: &OIDCSettings{ AccessTokenLifetime: 43200000000000, @@ -124,15 +125,11 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx name: "secret client", mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientSecret}, "instanceID", "clientID", true), want: &OIDCClient{ - InstanceID: "230690539048009730", - AppID: "236646858984783874", - State: domain.AppStateActive, - ClientID: "236646858984849410@tests", - ClientSecret: &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte(`$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq`), - }, + InstanceID: "230690539048009730", + AppID: "236646858984783874", + State: domain.AppStateActive, + ClientID: "236646858984849410@tests", + HashedSecret: "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq", RedirectURIs: []string{"http://localhost:9999/auth/callback"}, ResponseTypes: []domain.OIDCResponseType{0}, GrantTypes: []domain.OIDCGrantType{0}, @@ -148,6 +145,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AdditionalOrigins: nil, PublicKeys: nil, ProjectID: "236645808328409090", + ProjectRoleAssertion: false, ProjectRoleKeys: []string{"role1", "role2"}, Settings: &OIDCSettings{ AccessTokenLifetime: 43200000000000, @@ -163,7 +161,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AppID: "239520764276441090", State: domain.AppStateActive, ClientID: "239520764779364354@zitadel", - ClientSecret: nil, + HashedSecret: "", RedirectURIs: []string{ "http://test2-qucuh5.localhost:9000/ui/console/auth/callback", "http://test.localhost.com:9000/ui/console/auth/callback"}, @@ -184,6 +182,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AdditionalOrigins: nil, PublicKeys: nil, ProjectID: "239520764276178946", + ProjectRoleAssertion: false, ProjectRoleKeys: nil, Settings: nil, }, diff --git a/internal/query/org.go b/internal/query/org.go index 65be2ff1a3..eaeaf6eb29 100644 --- a/internal/query/org.go +++ b/internal/query/org.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/org_member_test.go b/internal/query/org_member_test.go index 6bbac65772..8ac13e8d73 100644 --- a/internal/query/org_member_test.go +++ b/internal/query/org_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.org_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id " + - "AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id " + - "AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id " + - "AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id " + + "AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id " + + "AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id " + + "AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/org_metadata.go b/internal/query/org_metadata.go index 15007e0e56..1ce95e2880 100644 --- a/internal/query/org_metadata.go +++ b/internal/query/org_metadata.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/password_age_policy.go b/internal/query/password_age_policy.go index a74980686d..199b757959 100644 --- a/internal/query/password_age_policy.go +++ b/internal/query/password_age_policy.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/password_complexity_policy.go b/internal/query/password_complexity_policy.go index d9aba36c5c..76015af447 100644 --- a/internal/query/password_complexity_policy.go +++ b/internal/query/password_complexity_policy.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/privacy_policy.go b/internal/query/privacy_policy.go index 2705f2b753..5540c34f5f 100644 --- a/internal/query/privacy_policy.go +++ b/internal/query/privacy_policy.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/project.go b/internal/query/project.go index 756039e9ce..a92448f25d 100644 --- a/internal/query/project.go +++ b/internal/query/project.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/project_grant.go b/internal/query/project_grant.go index ac2d8696e1..1bc68e984a 100644 --- a/internal/query/project_grant.go +++ b/internal/query/project_grant.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/project_grant_member_test.go b/internal/query/project_grant_member_test.go index 70cdfb7c6a..6d0ec6d822 100644 --- a/internal/query/project_grant_member_test.go +++ b/internal/query/project_grant_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.project_grant_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id " + - "AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id " + - "AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id " + - "AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id " + + "AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id " + + "AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id " + + "AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/project_member_test.go b/internal/query/project_member_test.go index 4e7fe0b6e0..2662016564 100644 --- a/internal/query/project_member_test.go +++ b/internal/query/project_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.project_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id " + - "AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id " + - "AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id " + - "AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id " + + "AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id " + + "AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id " + + "AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/project_role.go b/internal/query/project_role.go index bca1957ef4..76e113da65 100644 --- a/internal/query/project_role.go +++ b/internal/query/project_role.go @@ -6,7 +6,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/projection/app.go b/internal/query/projection/app.go index 2d81627c7c..a162548dd4 100644 --- a/internal/query/projection/app.go +++ b/internal/query/projection/app.go @@ -3,6 +3,7 @@ package projection import ( "context" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -15,7 +16,7 @@ import ( ) const ( - AppProjectionTable = "projections.apps6" + AppProjectionTable = "projections.apps7" AppAPITable = AppProjectionTable + "_" + appAPITableSuffix AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix @@ -96,7 +97,7 @@ func (*appProjection) Init() *old_handler.Check { handler.NewColumn(AppAPIConfigColumnAppID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnClientID, handler.ColumnTypeText), - handler.NewColumn(AppAPIConfigColumnClientSecret, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(AppAPIConfigColumnClientSecret, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(AppAPIConfigColumnAuthMethod, handler.ColumnTypeEnum), }, handler.NewPrimaryKey(AppAPIConfigColumnInstanceID, AppAPIConfigColumnAppID), @@ -109,7 +110,7 @@ func (*appProjection) Init() *old_handler.Check { handler.NewColumn(AppOIDCConfigColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppOIDCConfigColumnVersion, handler.ColumnTypeEnum), handler.NewColumn(AppOIDCConfigColumnClientID, handler.ColumnTypeText), - handler.NewColumn(AppOIDCConfigColumnClientSecret, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(AppOIDCConfigColumnClientSecret, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnRedirectUris, handler.ColumnTypeTextArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnResponseTypes, handler.ColumnTypeEnumArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnGrantTypes, handler.ColumnTypeEnumArray, handler.Nullable()), @@ -186,6 +187,10 @@ func (p *appProjection) Reducers() []handler.AggregateReducer { Event: project.APIConfigSecretChangedType, Reduce: p.reduceAPIConfigSecretChanged, }, + { + Event: project.APIConfigSecretHashUpdatedType, + Reduce: p.reduceAPIConfigSecretHashUpdated, + }, { Event: project.OIDCConfigAddedType, Reduce: p.reduceOIDCConfigAdded, @@ -198,6 +203,10 @@ func (p *appProjection) Reducers() []handler.AggregateReducer { Event: project.OIDCConfigSecretChangedType, Reduce: p.reduceOIDCConfigSecretChanged, }, + { + Event: project.OIDCConfigSecretHashUpdatedType, + Reduce: p.reduceOIDCConfigSecretHashUpdated, + }, { Event: project.SAMLConfigAddedType, Reduce: p.reduceSAMLConfigAdded, @@ -350,7 +359,7 @@ func (p *appProjection) reduceAPIConfigAdded(event eventstore.Event) (*handler.S handler.NewCol(AppAPIConfigColumnAppID, e.AppID), handler.NewCol(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(AppAPIConfigColumnClientID, e.ClientID), - handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppAPIConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), handler.NewCol(AppAPIConfigColumnAuthMethod, e.AuthMethodType), }, handler.WithTableSuffix(appAPITableSuffix), @@ -374,9 +383,6 @@ func (p *appProjection) reduceAPIConfigChanged(event eventstore.Event) (*handler return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-vnZKi", "reduce.wrong.event.type %s", project.APIConfigChangedType) } cols := make([]handler.Column, 0, 2) - if e.ClientSecret != nil { - cols = append(cols, handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret)) - } if e.AuthMethodType != nil { cols = append(cols, handler.NewCol(AppAPIConfigColumnAuthMethod, *e.AuthMethodType)) } @@ -415,7 +421,37 @@ func (p *appProjection) reduceAPIConfigSecretChanged(event eventstore.Event) (*h e, handler.AddUpdateStatement( []handler.Column{ - handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppAPIConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), + }, + []handler.Condition{ + handler.NewCond(AppAPIConfigColumnAppID, e.AppID), + handler.NewCond(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(appAPITableSuffix), + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppColumnChangeDate, e.CreationDate()), + handler.NewCol(AppColumnSequence, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(AppColumnID, e.AppID), + handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID), + }, + ), + ), nil +} + +func (p *appProjection) reduceAPIConfigSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*project.APIConfigSecretHashUpdatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ttb0I", "reduce.wrong.event.type %s", project.APIConfigSecretHashUpdatedType) + } + return handler.NewMultiStatement( + e, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppAPIConfigColumnClientSecret, e.HashedSecret), }, []handler.Condition{ handler.NewCond(AppAPIConfigColumnAppID, e.AppID), @@ -449,7 +485,7 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler. handler.NewCol(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(AppOIDCConfigColumnVersion, e.Version), handler.NewCol(AppOIDCConfigColumnClientID, e.ClientID), - handler.NewCol(AppOIDCConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppOIDCConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), handler.NewCol(AppOIDCConfigColumnRedirectUris, database.TextArray[string](e.RedirectUris)), handler.NewCol(AppOIDCConfigColumnResponseTypes, database.NumberArray[domain.OIDCResponseType](e.ResponseTypes)), handler.NewCol(AppOIDCConfigColumnGrantTypes, database.NumberArray[domain.OIDCGrantType](e.GrantTypes)), @@ -569,7 +605,37 @@ func (p *appProjection) reduceOIDCConfigSecretChanged(event eventstore.Event) (* e, handler.AddUpdateStatement( []handler.Column{ - handler.NewCol(AppOIDCConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppOIDCConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), + }, + []handler.Condition{ + handler.NewCond(AppOIDCConfigColumnAppID, e.AppID), + handler.NewCond(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(appOIDCTableSuffix), + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppColumnChangeDate, e.CreationDate()), + handler.NewCol(AppColumnSequence, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(AppColumnID, e.AppID), + handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID), + }, + ), + ), nil +} + +func (p *appProjection) reduceOIDCConfigSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*project.OIDCConfigSecretHashUpdatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-toSh1", "reduce.wrong.event.type %s", project.OIDCConfigSecretHashUpdatedType) + } + return handler.NewMultiStatement( + e, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppOIDCConfigColumnClientSecret, e.HashedSecret), }, []handler.Condition{ handler.NewCond(AppOIDCConfigColumnAppID, e.AppID), diff --git a/internal/query/projection/app_test.go b/internal/query/projection/app_test.go index 6d08d2e98a..49979c4698 100644 --- a/internal/query/projection/app_test.go +++ b/internal/query/projection/app_test.go @@ -46,7 +46,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps6 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.apps7 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "app-id", "my-app", @@ -83,7 +83,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps7 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "my-app", anyArg{}, @@ -136,7 +136,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps7 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateInactive, anyArg{}, @@ -168,7 +168,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps7 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateActive, anyArg{}, @@ -200,7 +200,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -227,7 +227,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (project_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (project_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -254,7 +254,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -264,7 +264,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceAPIConfigAdded", + name: "project reduceAPIConfigAdded, v1 secret", args: args{ event: getEvent( testEvent( @@ -273,7 +273,7 @@ func TestAppProjection_reduces(t *testing.T) { []byte(`{ "appId": "app-id", "clientId": "client-id", - "clientSecret": {}, + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"}, "authMethodType": 1 }`), ), project.APIConfigAddedEventMapper), @@ -285,17 +285,61 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps6_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.apps7_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "app-id", "instance-id", "client-id", - anyArg{}, + "secret", domain.APIAuthMethodTypePrivateKeyJWT, }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceAPIConfigAdded, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.APIConfigAddedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "clientId": "client-id", + "hashedSecret": "secret", + "authMethodType": 1 + }`), + ), project.APIConfigAddedEventMapper), + }, + reduce: (&appProjection{}).reduceAPIConfigAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO projections.apps7_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", + expectedArgs: []interface{}{ + "app-id", + "instance-id", + "client-id", + "secret", + domain.APIAuthMethodTypePrivateKeyJWT, + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -317,7 +361,6 @@ func TestAppProjection_reduces(t *testing.T) { []byte(`{ "appId": "app-id", "clientId": "client-id", - "clientSecret": {}, "authMethodType": 1 }`), ), project.APIConfigChangedEventMapper), @@ -329,16 +372,15 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7_api_configs SET auth_method = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - anyArg{}, domain.APIAuthMethodTypePrivateKeyJWT, "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -372,16 +414,16 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceAPIConfigSecretChanged", + name: "project reduceAPIConfigSecretChanged, v1 secret", args: args{ event: getEvent( testEvent( project.APIConfigSecretChangedType, project.AggregateType, []byte(`{ - "appId": "app-id", - "client_secret": {} - }`), + "appId": "app-id", + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"} + }`), ), project.APIConfigSecretChangedEventMapper), }, reduce: (&appProjection{}).reduceAPIConfigSecretChanged, @@ -391,15 +433,15 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - anyArg{}, + "secret", "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -412,7 +454,87 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceOIDCConfigAdded", + name: "project reduceAPIConfigSecretChanged, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.APIConfigSecretChangedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), project.APIConfigSecretChangedEventMapper), + }, + reduce: (&appProjection{}).reduceAPIConfigSecretChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceAPIConfigSecretHashUpdated", + args: args{ + event: getEvent( + testEvent( + project.APIConfigSecretHashUpdatedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), eventstore.GenericEventMapper[project.APIConfigSecretHashUpdatedEvent]), + }, + reduce: (&appProjection{}).reduceAPIConfigSecretHashUpdated, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigAdded, v1 secret", args: args{ event: getEvent( testEvent( @@ -422,7 +544,7 @@ func TestAppProjection_reduces(t *testing.T) { "oidcVersion": 0, "appId": "app-id", "clientId": "client-id", - "clientSecret": {}, + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"}, "redirectUris": ["redirect.one.ch", "redirect.two.ch"], "responseTypes": [1,2], "grantTypes": [1,2], @@ -447,13 +569,13 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps6_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", expectedArgs: []interface{}{ "app-id", "instance-id", domain.OIDCVersionV1, "client-id", - anyArg{}, + "secret", database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, database.NumberArray[domain.OIDCResponseType]{1, 2}, database.NumberArray[domain.OIDCGrantType]{1, 2}, @@ -471,7 +593,79 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigAdded, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.OIDCConfigAddedType, + project.AggregateType, + []byte(`{ + "oidcVersion": 0, + "appId": "app-id", + "clientId": "client-id", + "hashedSecret": "secret", + "redirectUris": ["redirect.one.ch", "redirect.two.ch"], + "responseTypes": [1,2], + "grantTypes": [1,2], + "applicationType": 2, + "authMethodType": 2, + "postLogoutRedirectUris": ["logout.one.ch", "logout.two.ch"], + "devMode": true, + "accessTokenType": 1, + "accessTokenRoleAssertion": true, + "idTokenRoleAssertion": true, + "idTokenUserinfoAssertion": true, + "clockSkew": 1000, + "additionalOrigins": ["origin.one.ch", "origin.two.ch"], + "skipNativeAppSuccessPage": true + }`), + ), project.OIDCConfigAddedEventMapper), + }, + reduce: (&appProjection{}).reduceOIDCConfigAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedArgs: []interface{}{ + "app-id", + "instance-id", + domain.OIDCVersionV1, + "client-id", + "secret", + database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, + database.NumberArray[domain.OIDCResponseType]{1, 2}, + database.NumberArray[domain.OIDCGrantType]{1, 2}, + domain.OIDCApplicationTypeNative, + domain.OIDCAuthMethodTypeNone, + database.TextArray[string]{"logout.one.ch", "logout.two.ch"}, + true, + domain.OIDCTokenTypeJWT, + true, + true, + true, + 1 * time.Microsecond, + database.TextArray[string]{"origin.one.ch", "origin.two.ch"}, + true, + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -518,7 +712,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", + expectedStmt: "UPDATE projections.apps7_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", expectedArgs: []interface{}{ domain.OIDCVersionV1, database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, @@ -540,7 +734,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -574,7 +768,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceOIDCConfigSecretChanged", + name: "project reduceOIDCConfigSecretChanged, v1 secret", args: args{ event: getEvent( testEvent( @@ -582,7 +776,7 @@ func TestAppProjection_reduces(t *testing.T) { project.AggregateType, []byte(`{ "appId": "app-id", - "client_secret": {} + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"} }`), ), project.OIDCConfigSecretChangedEventMapper), }, @@ -593,15 +787,95 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - anyArg{}, + "secret", "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigSecretChanged, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.OIDCConfigSecretChangedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), project.OIDCConfigSecretChangedEventMapper), + }, + reduce: (&appProjection{}).reduceOIDCConfigSecretChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigSecretHashUpdated", + args: args{ + event: getEvent( + testEvent( + project.OIDCConfigSecretHashUpdatedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), eventstore.GenericEventMapper[project.OIDCConfigSecretHashUpdatedEvent]), + }, + reduce: (&appProjection{}).reduceOIDCConfigSecretHashUpdated, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -630,7 +904,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", diff --git a/internal/query/projection/debug_notification.go b/internal/query/projection/debug_notification.go index eca16dce0a..faf50d197f 100644 --- a/internal/query/projection/debug_notification.go +++ b/internal/query/projection/debug_notification.go @@ -4,12 +4,11 @@ import ( "context" "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/repository/settings" - "github.com/zitadel/zitadel/internal/eventstore" old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/repository/instance" + "github.com/zitadel/zitadel/internal/repository/settings" "github.com/zitadel/zitadel/internal/zerrors" ) diff --git a/internal/query/projection/device_auth.go b/internal/query/projection/device_auth.go index d8231004f9..88cc127f05 100644 --- a/internal/query/projection/device_auth.go +++ b/internal/query/projection/device_auth.go @@ -11,12 +11,13 @@ import ( ) const ( - DeviceAuthRequestProjectionTable = "projections.device_auth_requests" + DeviceAuthRequestProjectionTable = "projections.device_auth_requests1" DeviceAuthRequestColumnClientID = "client_id" DeviceAuthRequestColumnDeviceCode = "device_code" DeviceAuthRequestColumnUserCode = "user_code" DeviceAuthRequestColumnScopes = "scopes" + DeviceAuthRequestColumnAudience = "audience" DeviceAuthRequestColumnCreationDate = "creation_date" DeviceAuthRequestColumnChangeDate = "change_date" DeviceAuthRequestColumnSequence = "sequence" @@ -44,6 +45,7 @@ func (*deviceAuthRequestProjection) Init() *old_handler.Check { handler.NewColumn(DeviceAuthRequestColumnDeviceCode, handler.ColumnTypeText), handler.NewColumn(DeviceAuthRequestColumnUserCode, handler.ColumnTypeText), handler.NewColumn(DeviceAuthRequestColumnScopes, handler.ColumnTypeTextArray), + handler.NewColumn(DeviceAuthRequestColumnAudience, handler.ColumnTypeTextArray), handler.NewColumn(DeviceAuthRequestColumnCreationDate, handler.ColumnTypeTimestamp), handler.NewColumn(DeviceAuthRequestColumnChangeDate, handler.ColumnTypeTimestamp), handler.NewColumn(DeviceAuthRequestColumnSequence, handler.ColumnTypeInt64), @@ -89,6 +91,7 @@ func (p *deviceAuthRequestProjection) reduceAdded(event eventstore.Event) (*hand handler.NewCol(DeviceAuthRequestColumnDeviceCode, e.DeviceCode), handler.NewCol(DeviceAuthRequestColumnUserCode, e.UserCode), handler.NewCol(DeviceAuthRequestColumnScopes, e.Scopes), + handler.NewCol(DeviceAuthRequestColumnAudience, e.Audience), handler.NewCol(DeviceAuthRequestColumnCreationDate, e.CreationDate()), handler.NewCol(DeviceAuthRequestColumnChangeDate, e.CreationDate()), handler.NewCol(DeviceAuthRequestColumnSequence, e.Sequence()), diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go index 00cac448af..1ecd9d654f 100644 --- a/internal/query/projection/idp_template.go +++ b/internal/query/projection/idp_template.go @@ -17,7 +17,7 @@ import ( ) const ( - IDPTemplateTable = "projections.idp_templates5" + IDPTemplateTable = "projections.idp_templates6" IDPTemplateOAuthTable = IDPTemplateTable + "_" + IDPTemplateOAuthSuffix IDPTemplateOIDCTable = IDPTemplateTable + "_" + IDPTemplateOIDCSuffix IDPTemplateJWTTable = IDPTemplateTable + "_" + IDPTemplateJWTSuffix @@ -59,6 +59,7 @@ const ( IDPTemplateIsLinkingAllowedCol = "is_linking_allowed" IDPTemplateIsAutoCreationCol = "is_auto_creation" IDPTemplateIsAutoUpdateCol = "is_auto_update" + IDPTemplateAutoLinkingCol = "auto_linking" OAuthIDCol = "idp_id" OAuthInstanceIDCol = "instance_id" @@ -197,6 +198,7 @@ func (*idpTemplateProjection) Init() *old_handler.Check { handler.NewColumn(IDPTemplateIsLinkingAllowedCol, handler.ColumnTypeBool, handler.Default(false)), handler.NewColumn(IDPTemplateIsAutoCreationCol, handler.ColumnTypeBool, handler.Default(false)), handler.NewColumn(IDPTemplateIsAutoUpdateCol, handler.ColumnTypeBool, handler.Default(false)), + handler.NewColumn(IDPTemplateAutoLinkingCol, handler.ColumnTypeEnum, handler.Default(0)), }, handler.NewPrimaryKey(IDPTemplateInstanceIDCol, IDPTemplateIDCol), handler.WithIndex(handler.NewIndex("resource_owner", []string{IDPTemplateResourceOwnerCol})), @@ -700,6 +702,7 @@ func (p *idpTemplateProjection) reduceOAuthIDPAdded(event eventstore.Event) (*ha handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -792,6 +795,7 @@ func (p *idpTemplateProjection) reduceOIDCIDPAdded(event eventstore.Event) (*han handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -873,6 +877,7 @@ func (p *idpTemplateProjection) reduceOIDCIDPMigratedAzureAD(event eventstore.Ev handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, []handler.Condition{ handler.NewCond(IDPTemplateIDCol, idpEvent.ID), @@ -924,6 +929,7 @@ func (p *idpTemplateProjection) reduceOIDCIDPMigratedGoogle(event eventstore.Eve handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, []handler.Condition{ handler.NewCond(IDPTemplateIDCol, idpEvent.ID), @@ -982,6 +988,7 @@ func (p *idpTemplateProjection) reduceJWTIDPAdded(event eventstore.Event) (*hand handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1070,6 +1077,7 @@ func (p *idpTemplateProjection) reduceOldConfigAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, true), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.AutoRegister), handler.NewCol(IDPTemplateIsAutoUpdateCol, false), + handler.NewCol(IDPTemplateAutoLinkingCol, domain.AutoLinkingOptionUnspecified), }, ), nil } @@ -1328,6 +1336,7 @@ func (p *idpTemplateProjection) reduceAzureADIDPAdded(event eventstore.Event) (* handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1418,6 +1427,7 @@ func (p *idpTemplateProjection) reduceGitHubIDPAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1465,6 +1475,7 @@ func (p *idpTemplateProjection) reduceGitHubEnterpriseIDPAdded(event eventstore. handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1597,6 +1608,7 @@ func (p *idpTemplateProjection) reduceGitLabIDPAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1685,6 +1697,7 @@ func (p *idpTemplateProjection) reduceGitLabSelfHostedIDPAdded(event eventstore. handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1774,6 +1787,7 @@ func (p *idpTemplateProjection) reduceGoogleIDPAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1862,6 +1876,7 @@ func (p *idpTemplateProjection) reduceLDAPIDPAdded(event eventstore.Event) (*han handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1970,6 +1985,7 @@ func (p *idpTemplateProjection) reduceSAMLIDPAdded(event eventstore.Event) (*han handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -2061,6 +2077,7 @@ func (p *idpTemplateProjection) reduceAppleIDPAdded(event eventstore.Event) (*ha handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -2191,6 +2208,9 @@ func reduceIDPChangedTemplateColumns(name *string, creationDate time.Time, seque if optionChanges.IsAutoUpdate != nil { cols = append(cols, handler.NewCol(IDPTemplateIsAutoUpdateCol, *optionChanges.IsAutoUpdate)) } + if optionChanges.AutoLinkingOption != nil { + cols = append(cols, handler.NewCol(IDPTemplateAutoLinkingCol, *optionChanges.AutoLinkingOption)) + } return append(cols, handler.NewCol(IDPTemplateChangeDateCol, creationDate), handler.NewCol(IDPTemplateSequenceCol, sequence), diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go index 8490810d83..d463054af7 100644 --- a/internal/query/projection/idp_template_test.go +++ b/internal/query/projection/idp_template_test.go @@ -15,12 +15,12 @@ import ( ) var ( - idpTemplateInsertStmt = `INSERT INTO projections.idp_templates5` + - ` (id, creation_date, change_date, sequence, resource_owner, instance_id, state, name, owner_type, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update)` + - ` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)` - idpTemplateUpdateMinimalStmt = `UPDATE projections.idp_templates5 SET (is_creation_allowed, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)` - idpTemplateUpdateStmt = `UPDATE projections.idp_templates5 SET (name, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, change_date, sequence)` + - ` = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (instance_id = $9)` + idpTemplateInsertStmt = `INSERT INTO projections.idp_templates6` + + ` (id, creation_date, change_date, sequence, resource_owner, instance_id, state, name, owner_type, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking)` + + ` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)` + idpTemplateUpdateMinimalStmt = `UPDATE projections.idp_templates6 SET (is_creation_allowed, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)` + idpTemplateUpdateStmt = `UPDATE projections.idp_templates6 SET (name, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking, change_date, sequence)` + + ` = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)` ) func TestIDPTemplateProjection_reducesRemove(t *testing.T) { @@ -51,7 +51,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -77,7 +77,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -106,7 +106,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -135,7 +135,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -195,7 +195,8 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OAuthIDPAddedEventMapper), }, @@ -222,10 +223,11 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.idp_templates6_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -266,7 +268,8 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OAuthIDPAddedEventMapper), }, @@ -293,10 +296,11 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.idp_templates6_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -344,7 +348,7 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oauth2 SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_oauth2 SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -379,7 +383,8 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OAuthIDPChangedEventMapper), }, @@ -397,6 +402,7 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -404,7 +410,7 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oauth2 SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) = ($1, $2, $3, $4, $5, $6, $7) WHERE (idp_id = $8) AND (instance_id = $9)", + expectedStmt: "UPDATE projections.idp_templates6_oauth2 SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) = ($1, $2, $3, $4, $5, $6, $7) WHERE (idp_id = $8) AND (instance_id = $9)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -489,10 +495,11 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { false, false, false, + domain.AutoLinkingOptionUnspecified, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -529,7 +536,8 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AzureADIDPAddedEventMapper), }, @@ -556,10 +564,11 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -596,7 +605,8 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.AzureADIDPAddedEventMapper), }, @@ -623,10 +633,11 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -672,7 +683,7 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_azure SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_azure SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -705,7 +716,8 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AzureADIDPChangedEventMapper), }, @@ -723,6 +735,7 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -730,7 +743,7 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_azure SET (client_id, client_secret, scopes, tenant, is_email_verified) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_azure SET (client_id, client_secret, scopes, tenant, is_email_verified) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -791,7 +804,8 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubIDPAddedEventMapper), }, @@ -818,10 +832,11 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -854,7 +869,8 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitHubIDPAddedEventMapper), }, @@ -881,10 +897,11 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -928,7 +945,7 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_github SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -959,7 +976,8 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubIDPChangedEventMapper), }, @@ -977,6 +995,7 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -984,7 +1003,7 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6_github SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -1046,7 +1065,8 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubEnterpriseIDPAddedEventMapper), }, @@ -1073,10 +1093,11 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedStmt: "INSERT INTO projections.idp_templates6_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1115,7 +1136,8 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitHubEnterpriseIDPAddedEventMapper), }, @@ -1142,10 +1164,11 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedStmt: "INSERT INTO projections.idp_templates6_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1192,7 +1215,7 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github_enterprise SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_github_enterprise SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -1226,7 +1249,8 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubEnterpriseIDPChangedEventMapper), }, @@ -1244,6 +1268,7 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -1251,7 +1276,7 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github_enterprise SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) = ($1, $2, $3, $4, $5, $6) WHERE (idp_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.idp_templates6_github_enterprise SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) = ($1, $2, $3, $4, $5, $6) WHERE (idp_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -1312,7 +1337,8 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabIDPAddedEventMapper), }, @@ -1339,10 +1365,11 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1374,7 +1401,8 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitLabIDPAddedEventMapper), }, @@ -1401,10 +1429,11 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1448,7 +1477,7 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -1479,7 +1508,8 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabIDPChangedEventMapper), }, @@ -1497,6 +1527,7 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -1504,7 +1535,7 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -1564,7 +1595,8 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabSelfHostedIDPAddedEventMapper), }, @@ -1591,10 +1623,11 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1629,7 +1662,8 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitLabSelfHostedIDPAddedEventMapper), }, @@ -1656,10 +1690,11 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1704,7 +1739,7 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab_self_hosted SET issuer = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab_self_hosted SET issuer = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "issuer", "idp-id", @@ -1736,7 +1771,8 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabSelfHostedIDPChangedEventMapper), }, @@ -1754,6 +1790,7 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -1761,7 +1798,7 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab_self_hosted SET (issuer, client_id, client_secret, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab_self_hosted SET (issuer, client_id, client_secret, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "issuer", "client_id", @@ -1820,7 +1857,8 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GoogleIDPAddedEventMapper), }, @@ -1847,10 +1885,11 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1882,7 +1921,8 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GoogleIDPAddedEventMapper), }, @@ -1909,10 +1949,11 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1956,7 +1997,7 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_google SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_google SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -1987,7 +2028,8 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GoogleIDPChangedEventMapper), }, @@ -2005,6 +2047,7 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2012,7 +2055,7 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_google SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6_google SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -2090,7 +2133,8 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.LDAPIDPAddedEventMapper), }, @@ -2117,10 +2161,11 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", + expectedStmt: "INSERT INTO projections.idp_templates6_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2191,7 +2236,8 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.LDAPIDPAddedEventMapper), }, @@ -2218,10 +2264,11 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", + expectedStmt: "INSERT INTO projections.idp_templates6_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2274,7 +2321,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "custom-zitadel-instance", anyArg{}, @@ -2284,7 +2331,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "basedn", "idp-id", @@ -2334,7 +2381,8 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.LDAPIDPChangedEventMapper), }, @@ -2352,6 +2400,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2359,7 +2408,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)", + expectedStmt: "UPDATE projections.idp_templates6_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)", expectedArgs: []interface{}{ database.TextArray[string]{"server"}, false, @@ -2408,7 +2457,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -2464,7 +2513,8 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AppleIDPAddedEventMapper), }, @@ -2491,10 +2541,11 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2529,7 +2580,8 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.AppleIDPAddedEventMapper), }, @@ -2556,10 +2608,11 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2604,7 +2657,7 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_apple SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_apple SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -2636,7 +2689,8 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AppleIDPChangedEventMapper), }, @@ -2654,6 +2708,7 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2661,7 +2716,7 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_apple SET (client_id, team_id, key_id, private_key, scopes) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_apple SET (client_id, team_id, key_id, private_key, scopes) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ "client_id", "team_id", @@ -2723,7 +2778,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.SAMLIDPAddedEventMapper), }, @@ -2750,10 +2806,11 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2789,7 +2846,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.SAMLIDPAddedEventMapper), }, @@ -2816,10 +2874,11 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2854,7 +2913,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "custom-zitadel-instance", anyArg{}, @@ -2864,7 +2923,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_saml SET binding = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_saml SET binding = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "binding", "idp-id", @@ -2896,7 +2955,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.SAMLIDPChangedEventMapper), }, @@ -2914,6 +2974,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2921,7 +2982,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_saml SET (metadata, key, certificate, binding, with_signed_request) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_saml SET (metadata, key, certificate, binding, with_signed_request) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ []byte("metadata"), anyArg{}, @@ -2952,7 +3013,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -3009,7 +3070,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPAddedEventMapper), }, @@ -3036,10 +3098,11 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3075,7 +3138,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OIDCIDPAddedEventMapper), }, @@ -3102,10 +3166,11 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3151,7 +3216,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -3184,7 +3249,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPChangedEventMapper), }, @@ -3202,6 +3268,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -3209,7 +3276,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET (client_id, client_secret, issuer, scopes, id_token_mapping) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET (client_id, client_secret, issuer, scopes, id_token_mapping) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -3245,7 +3312,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPMigratedAzureADEventMapper), }, @@ -3256,7 +3324,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3266,19 +3334,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3314,7 +3383,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OIDCIDPMigratedAzureADEventMapper), }, @@ -3325,7 +3395,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3335,19 +3405,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3381,7 +3452,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPMigratedGoogleEventMapper), }, @@ -3392,7 +3464,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3402,19 +3474,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3446,7 +3519,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OIDCIDPMigratedGoogleEventMapper), }, @@ -3457,7 +3531,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3467,19 +3541,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3557,6 +3632,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { true, true, false, + domain.AutoLinkingOptionUnspecified, }, }, }, @@ -3602,6 +3678,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { true, true, false, + domain.AutoLinkingOptionUnspecified, }, }, }, @@ -3630,7 +3707,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "custom-zitadel-instance", true, @@ -3666,7 +3743,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "custom-zitadel-instance", true, @@ -3709,7 +3786,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3719,7 +3796,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -3763,7 +3840,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3773,7 +3850,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -3817,7 +3894,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3826,7 +3903,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "client-id", anyArg{}, @@ -3869,7 +3946,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3878,7 +3955,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "client-id", anyArg{}, @@ -3915,7 +3992,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3925,7 +4002,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -3963,7 +4040,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3973,7 +4050,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -4010,7 +4087,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -4019,7 +4096,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "https://api.zitadel.ch/jwt", "https://api.zitadel.ch/keys", @@ -4056,7 +4133,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -4065,7 +4142,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "https://api.zitadel.ch/jwt", "https://api.zitadel.ch/keys", @@ -4121,7 +4198,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.JWTIDPAddedEventMapper), }, @@ -4148,10 +4226,11 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -4181,7 +4260,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.JWTIDPAddedEventMapper), }, @@ -4208,10 +4288,11 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -4256,7 +4337,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET jwt_endpoint = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET jwt_endpoint = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "jwt", "idp-id", @@ -4283,7 +4364,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.JWTIDPChangedEventMapper), }, @@ -4294,12 +4376,13 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, change_date, sequence) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.idp_templates6 SET (is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking, change_date, sequence) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (instance_id = $9)", expectedArgs: []interface{}{ true, true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -4307,7 +4390,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "jwt", "keys", diff --git a/internal/query/projection/instance_features.go b/internal/query/projection/instance_features.go index 22e47cbfed..ebb388da89 100644 --- a/internal/query/projection/instance_features.go +++ b/internal/query/projection/instance_features.go @@ -79,6 +79,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer { Event: feature_v2.InstanceTokenExchangeEventType, Reduce: reduceInstanceSetFeature[bool], }, + { + Event: feature_v2.InstanceActionsEventType, + Reduce: reduceInstanceSetFeature[bool], + }, { Event: instance.InstanceRemovedEventType, Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol), diff --git a/internal/query/projection/lockout_policy.go b/internal/query/projection/lockout_policy.go index ceb99c2aa0..4412308b89 100644 --- a/internal/query/projection/lockout_policy.go +++ b/internal/query/projection/lockout_policy.go @@ -14,7 +14,7 @@ import ( ) const ( - LockoutPolicyTable = "projections.lockout_policies2" + LockoutPolicyTable = "projections.lockout_policies3" LockoutPolicyIDCol = "id" LockoutPolicyCreationDateCol = "creation_date" @@ -25,8 +25,8 @@ const ( LockoutPolicyResourceOwnerCol = "resource_owner" LockoutPolicyInstanceIDCol = "instance_id" LockoutPolicyMaxPasswordAttemptsCol = "max_password_attempts" + LockoutPolicyMaxOTPAttemptsCol = "max_otp_attempts" LockoutPolicyShowLockOutFailuresCol = "show_failure" - LockoutPolicyOwnerRemovedCol = "owner_removed" ) type lockoutPolicyProjection struct{} @@ -51,11 +51,10 @@ func (*lockoutPolicyProjection) Init() *old_handler.Check { handler.NewColumn(LockoutPolicyResourceOwnerCol, handler.ColumnTypeText), handler.NewColumn(LockoutPolicyInstanceIDCol, handler.ColumnTypeText), handler.NewColumn(LockoutPolicyMaxPasswordAttemptsCol, handler.ColumnTypeInt64), + handler.NewColumn(LockoutPolicyMaxOTPAttemptsCol, handler.ColumnTypeInt64, handler.Default(0)), handler.NewColumn(LockoutPolicyShowLockOutFailuresCol, handler.ColumnTypeBool), - handler.NewColumn(LockoutPolicyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(LockoutPolicyInstanceIDCol, LockoutPolicyIDCol), - handler.WithIndex(handler.NewIndex("owner_removed", []string{LockoutPolicyOwnerRemovedCol})), ), ) } @@ -125,6 +124,7 @@ func (p *lockoutPolicyProjection) reduceAdded(event eventstore.Event) (*handler. handler.NewCol(LockoutPolicyIDCol, policyEvent.Aggregate().ID), handler.NewCol(LockoutPolicyStateCol, domain.PolicyStateActive), handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, policyEvent.MaxPasswordAttempts), + handler.NewCol(LockoutPolicyMaxOTPAttemptsCol, policyEvent.MaxOTPAttempts), handler.NewCol(LockoutPolicyShowLockOutFailuresCol, policyEvent.ShowLockOutFailures), handler.NewCol(LockoutPolicyIsDefaultCol, isDefault), handler.NewCol(LockoutPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner), @@ -149,6 +149,9 @@ func (p *lockoutPolicyProjection) reduceChanged(event eventstore.Event) (*handle if policyEvent.MaxPasswordAttempts != nil { cols = append(cols, handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, *policyEvent.MaxPasswordAttempts)) } + if policyEvent.MaxOTPAttempts != nil { + cols = append(cols, handler.NewCol(LockoutPolicyMaxOTPAttemptsCol, *policyEvent.MaxOTPAttempts)) + } if policyEvent.ShowLockOutFailures != nil { cols = append(cols, handler.NewCol(LockoutPolicyShowLockOutFailuresCol, *policyEvent.ShowLockOutFailures)) } diff --git a/internal/query/projection/lockout_policy_test.go b/internal/query/projection/lockout_policy_test.go index 781f3a48c4..f44e1f4f0b 100644 --- a/internal/query/projection/lockout_policy_test.go +++ b/internal/query/projection/lockout_policy_test.go @@ -30,6 +30,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { org.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), org.LockoutPolicyAddedEventMapper), @@ -41,7 +42,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.lockout_policies2 (creation_date, change_date, sequence, id, state, max_password_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.lockout_policies3 (creation_date, change_date, sequence, id, state, max_password_attempts, max_otp_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -49,6 +50,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { "agg-id", domain.PolicyStateActive, uint64(10), + uint64(10), true, false, "ro-id", @@ -69,6 +71,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { org.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), org.LockoutPolicyChangedEventMapper), @@ -79,11 +82,12 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.lockout_policies2 SET (change_date, sequence, max_password_attempts, show_failure) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.lockout_policies3 SET (change_date, sequence, max_password_attempts, max_otp_attempts, show_failure) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ anyArg{}, uint64(15), uint64(10), + uint64(10), true, "agg-id", "instance-id", @@ -110,7 +114,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -137,7 +141,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -156,6 +160,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { instance.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), instance.LockoutPolicyAddedEventMapper), @@ -166,7 +171,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.lockout_policies2 (creation_date, change_date, sequence, id, state, max_password_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.lockout_policies3 (creation_date, change_date, sequence, id, state, max_password_attempts, max_otp_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -174,6 +179,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { "agg-id", domain.PolicyStateActive, uint64(10), + uint64(10), true, true, "ro-id", @@ -194,6 +200,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { instance.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), instance.LockoutPolicyChangedEventMapper), @@ -204,11 +211,12 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.lockout_policies2 SET (change_date, sequence, max_password_attempts, show_failure) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.lockout_policies3 SET (change_date, sequence, max_password_attempts, max_otp_attempts, show_failure) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ anyArg{}, uint64(15), uint64(10), + uint64(10), true, "agg-id", "instance-id", @@ -235,7 +243,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", diff --git a/internal/query/projection/smtp.go b/internal/query/projection/smtp.go index b753df7f57..9b53da4150 100644 --- a/internal/query/projection/smtp.go +++ b/internal/query/projection/smtp.go @@ -3,6 +3,7 @@ package projection import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" @@ -11,14 +12,13 @@ import ( ) const ( - SMTPConfigProjectionTable = "projections.smtp_configs1" - - SMTPConfigColumnAggregateID = "aggregate_id" + SMTPConfigProjectionTable = "projections.smtp_configs2" + SMTPConfigColumnInstanceID = "instance_id" + SMTPConfigColumnResourceOwner = "resource_owner" + SMTPConfigColumnID = "id" SMTPConfigColumnCreationDate = "creation_date" SMTPConfigColumnChangeDate = "change_date" SMTPConfigColumnSequence = "sequence" - SMTPConfigColumnResourceOwner = "resource_owner" - SMTPConfigColumnInstanceID = "instance_id" SMTPConfigColumnTLS = "tls" SMTPConfigColumnSenderAddress = "sender_address" SMTPConfigColumnSenderName = "sender_name" @@ -26,6 +26,8 @@ const ( SMTPConfigColumnSMTPHost = "host" SMTPConfigColumnSMTPUser = "username" SMTPConfigColumnSMTPPassword = "password" + SMTPConfigColumnState = "state" + SMTPConfigColumnDescription = "description" ) type smtpConfigProjection struct{} @@ -41,7 +43,7 @@ func (*smtpConfigProjection) Name() string { func (*smtpConfigProjection) Init() *old_handler.Check { return handler.NewTableCheck( handler.NewTable([]*handler.InitColumn{ - handler.NewColumn(SMTPConfigColumnAggregateID, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigColumnID, handler.ColumnTypeText), handler.NewColumn(SMTPConfigColumnCreationDate, handler.ColumnTypeTimestamp), handler.NewColumn(SMTPConfigColumnChangeDate, handler.ColumnTypeTimestamp), handler.NewColumn(SMTPConfigColumnSequence, handler.ColumnTypeInt64), @@ -54,8 +56,10 @@ func (*smtpConfigProjection) Init() *old_handler.Check { handler.NewColumn(SMTPConfigColumnSMTPHost, handler.ColumnTypeText), handler.NewColumn(SMTPConfigColumnSMTPUser, handler.ColumnTypeText), handler.NewColumn(SMTPConfigColumnSMTPPassword, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(SMTPConfigColumnState, handler.ColumnTypeEnum), + handler.NewColumn(SMTPConfigColumnDescription, handler.ColumnTypeText), }, - handler.NewPrimaryKey(SMTPConfigColumnInstanceID, SMTPConfigColumnAggregateID), + handler.NewPrimaryKey(SMTPConfigColumnInstanceID, SMTPConfigColumnResourceOwner, SMTPConfigColumnID), ), ) } @@ -77,6 +81,14 @@ func (p *smtpConfigProjection) Reducers() []handler.AggregateReducer { Event: instance.SMTPConfigPasswordChangedEventType, Reduce: p.reduceSMTPConfigPasswordChanged, }, + { + Event: instance.SMTPConfigActivatedEventType, + Reduce: p.reduceSMTPConfigActivated, + }, + { + Event: instance.SMTPConfigDeactivatedEventType, + Reduce: p.reduceSMTPConfigDeactivated, + }, { Event: instance.SMTPConfigRemovedEventType, Reduce: p.reduceSMTPConfigRemoved, @@ -95,15 +107,26 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-sk99F", "reduce.wrong.event.type %s", instance.SMTPConfigAddedEventType) } + + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + description := e.Description + state := domain.SMTPConfigStateInactive + if e.ID == "" { + id = e.Aggregate().ResourceOwner + description = "generic" + state = domain.SMTPConfigStateActive + } + return handler.NewCreateStatement( e, []handler.Column{ - handler.NewCol(SMTPConfigColumnAggregateID, e.Aggregate().ID), handler.NewCol(SMTPConfigColumnCreationDate, e.CreationDate()), handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), handler.NewCol(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), handler.NewCol(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnID, id), handler.NewCol(SMTPConfigColumnTLS, e.TLS), handler.NewCol(SMTPConfigColumnSenderAddress, e.SenderAddress), handler.NewCol(SMTPConfigColumnSenderName, e.SenderName), @@ -111,6 +134,8 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h handler.NewCol(SMTPConfigColumnSMTPHost, e.Host), handler.NewCol(SMTPConfigColumnSMTPUser, e.User), handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password), + handler.NewCol(SMTPConfigColumnState, state), + handler.NewCol(SMTPConfigColumnDescription, description), }, ), nil } @@ -124,6 +149,13 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) ( columns := make([]handler.Column, 0, 8) columns = append(columns, handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), handler.NewCol(SMTPConfigColumnSequence, e.Sequence())) + + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + if e.ID == "" { + id = e.Aggregate().ResourceOwner + } + if e.TLS != nil { columns = append(columns, handler.NewCol(SMTPConfigColumnTLS, *e.TLS)) } @@ -142,11 +174,18 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) ( if e.User != nil { columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPUser, *e.User)) } + if e.Password != nil { + columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPPassword, *e.Password)) + } + if e.Description != nil { + columns = append(columns, handler.NewCol(SMTPConfigColumnDescription, *e.Description)) + } return handler.NewUpdateStatement( e, columns, []handler.Condition{ - handler.NewCond(SMTPConfigColumnAggregateID, e.Aggregate().ID), + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), }, ), nil @@ -158,6 +197,12 @@ func (p *smtpConfigProjection) reduceSMTPConfigPasswordChanged(event eventstore. return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fk02f", "reduce.wrong.event.type %s", instance.SMTPConfigChangedEventType) } + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + if e.ID == "" { + id = e.Aggregate().ResourceOwner + } + return handler.NewUpdateStatement( e, []handler.Column{ @@ -166,7 +211,62 @@ func (p *smtpConfigProjection) reduceSMTPConfigPasswordChanged(event eventstore. handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password), }, []handler.Condition{ - handler.NewCond(SMTPConfigColumnAggregateID, e.Aggregate().ID), + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + ), nil +} + +func (p *smtpConfigProjection) reduceSMTPConfigActivated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*instance.SMTPConfigActivatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fq92r", "reduce.wrong.event.type %s", instance.SMTPConfigActivatedEventType) + } + + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + if e.ID == "" { + id = e.Aggregate().ResourceOwner + } + + return handler.NewUpdateStatement( + e, + []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnState, domain.SMTPConfigStateActive), + }, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + ), nil +} + +func (p *smtpConfigProjection) reduceSMTPConfigDeactivated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*instance.SMTPConfigDeactivatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-hv89j", "reduce.wrong.event.type %s", instance.SMTPConfigDeactivatedEventType) + } + + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + if e.ID == "" { + id = e.Aggregate().ResourceOwner + } + + return handler.NewUpdateStatement( + e, + []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnState, domain.SMTPConfigStateInactive), + }, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), }, ), nil @@ -177,10 +277,18 @@ func (p *smtpConfigProjection) reduceSMTPConfigRemoved(event eventstore.Event) ( if err != nil { return nil, err } + + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + if e.ID == "" { + id = e.Aggregate().ResourceOwner + } + return handler.NewDeleteStatement( e, []handler.Condition{ - handler.NewCond(SMTPConfigColumnAggregateID, e.Aggregate().ID), + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), }, ), nil diff --git a/internal/query/projection/smtp_test.go b/internal/query/projection/smtp_test.go index 3fd78c7bc8..4d7b5e4a99 100644 --- a/internal/query/projection/smtp_test.go +++ b/internal/query/projection/smtp_test.go @@ -3,6 +3,7 @@ package projection import ( "testing" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/repository/instance" @@ -27,12 +28,16 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigChangedEventType, instance.AggregateType, []byte(`{ + "description": "test", "tls": true, "senderAddress": "sender", "senderName": "name", "replyToAddress": "reply-to", "host": "host", - "user": "user" + "user": "user", + "id": "44444", + "resource_owner": "ro-id", + "instance_id": "instance-id" }`, ), ), instance.SMTPConfigChangedEventMapper), @@ -44,7 +49,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.smtp_configs1 SET (change_date, sequence, tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, tls, sender_address, sender_name, reply_to_address, host, username, description) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (resource_owner = $11) AND (instance_id = $12)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -54,7 +59,9 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { "reply-to", "host", "user", - "agg-id", + "test", + "44444", + "ro-id", "instance-id", }, }, @@ -71,6 +78,8 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.AggregateType, []byte(`{ "tls": true, + "id": "id", + "description": "test", "senderAddress": "sender", "senderName": "name", "replyToAddress": "reply-to", @@ -91,14 +100,14 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.smtp_configs1 (aggregate_id, creation_date, change_date, resource_owner, instance_id, sequence, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", + expectedStmt: "INSERT INTO projections.smtp_configs2 (creation_date, change_date, resource_owner, instance_id, sequence, id, tls, sender_address, sender_name, reply_to_address, host, username, password, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)", expectedArgs: []interface{}{ - "agg-id", anyArg{}, anyArg{}, "ro-id", "instance-id", uint64(15), + "id", true, "sender", "name", @@ -106,6 +115,72 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { "host", "user", anyArg{}, + domain.SMTPConfigState(3), + "test", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigActivated", + args: args{ + event: getEvent(testEvent( + instance.SMTPConfigActivatedEventType, + instance.AggregateType, + []byte(`{ + "id": "config-id" + }`), + ), instance.SMTPConfigActivatedEventMapper), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigActivated, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (resource_owner = $5) AND (instance_id = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SMTPConfigStateActive, + "config-id", + "ro-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigDeactivated", + args: args{ + event: getEvent(testEvent( + instance.SMTPConfigDeactivatedEventType, + instance.AggregateType, + []byte(`{ + "id": "config-id" + }`), + ), instance.SMTPConfigDeactivatedEventMapper), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigDeactivated, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (resource_owner = $5) AND (instance_id = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SMTPConfigStateInactive, + "config-id", + "ro-id", + "instance-id", }, }, }, @@ -120,6 +195,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigPasswordChangedEventType, instance.AggregateType, []byte(`{ + "id": "config-id", "password": { "cryptoType": 0, "algorithm": "RSA-265", @@ -135,12 +211,13 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.smtp_configs1 SET (change_date, sequence, password) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, password) = ($1, $2, $3) WHERE (id = $4) AND (resource_owner = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), anyArg{}, - "agg-id", + "config-id", + "ro-id", "instance-id", }, }, @@ -154,7 +231,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { event: getEvent(testEvent( instance.SMTPConfigRemovedEventType, instance.AggregateType, - []byte(`{}`), + []byte(`{ "id": "config-id"}`), ), instance.SMTPConfigRemovedEventMapper), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigRemoved, @@ -164,9 +241,10 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.smtp_configs1 WHERE (aggregate_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.smtp_configs2 WHERE (id = $1) AND (resource_owner = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - "agg-id", + "config-id", + "ro-id", "instance-id", }, }, @@ -191,7 +269,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.smtp_configs1 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.smtp_configs2 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/projection/system_features.go b/internal/query/projection/system_features.go index ee3772a19c..94c6282c49 100644 --- a/internal/query/projection/system_features.go +++ b/internal/query/projection/system_features.go @@ -71,6 +71,10 @@ func (*systemFeatureProjection) Reducers() []handler.AggregateReducer { Event: feature_v2.SystemTokenExchangeEventType, Reduce: reduceSystemSetFeature[bool], }, + { + Event: feature_v2.SystemActionsEventType, + Reduce: reduceSystemSetFeature[bool], + }, }, }} } diff --git a/internal/query/projection/user.go b/internal/query/projection/user.go index 0a75abc368..d9c5f78b22 100644 --- a/internal/query/projection/user.go +++ b/internal/query/projection/user.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" @@ -15,7 +16,7 @@ import ( ) const ( - UserTable = "projections.users11" + UserTable = "projections.users12" UserHumanTable = UserTable + "_" + UserHumanSuffix UserMachineTable = UserTable + "_" + UserMachineSuffix UserNotifyTable = UserTable + "_" + UserNotifySuffix @@ -125,7 +126,7 @@ func (*userProjection) Init() *old_handler.Check { handler.NewColumn(MachineUserInstanceIDCol, handler.ColumnTypeText), handler.NewColumn(MachineNameCol, handler.ColumnTypeText), handler.NewColumn(MachineDescriptionCol, handler.ColumnTypeText, handler.Nullable()), - handler.NewColumn(MachineSecretCol, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(MachineSecretCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(MachineAccessTokenTypeCol, handler.ColumnTypeEnum, handler.Default(0)), }, handler.NewPrimaryKey(MachineUserInstanceIDCol, MachineUserIDCol), @@ -285,6 +286,10 @@ func (p *userProjection) Reducers() []handler.AggregateReducer { Event: user.MachineSecretSetType, Reduce: p.reduceMachineSecretSet, }, + { + Event: user.MachineSecretHashUpdatedType, + Reduce: p.reduceMachineSecretHashUpdated, + }, { Event: user.MachineSecretRemovedType, Reduce: p.reduceMachineSecretRemoved, @@ -354,7 +359,7 @@ func (p *userProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(NotifyLastEmailCol, e.EmailAddress), handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}), - handler.NewCol(NotifyPasswordSetCol, user.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), + handler.NewCol(NotifyPasswordSetCol, crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), }, handler.WithTableSuffix(UserNotifySuffix), ), @@ -403,7 +408,7 @@ func (p *userProjection) reduceHumanRegistered(event eventstore.Event) (*handler handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(NotifyLastEmailCol, e.EmailAddress), handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}), - handler.NewCol(NotifyPasswordSetCol, user.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), + handler.NewCol(NotifyPasswordSetCol, crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), }, handler.WithTableSuffix(UserNotifySuffix), ), @@ -952,7 +957,37 @@ func (p *userProjection) reduceMachineSecretSet(event eventstore.Event) (*handle ), handler.AddUpdateStatement( []handler.Column{ - handler.NewCol(MachineSecretCol, e.ClientSecret), + handler.NewCol(MachineSecretCol, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), + }, + []handler.Condition{ + handler.NewCond(MachineUserIDCol, e.Aggregate().ID), + handler.NewCond(MachineUserInstanceIDCol, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(UserMachineSuffix), + ), + ), nil +} + +func (p *userProjection) reduceMachineSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.MachineSecretHashUpdatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Wieng4u", "reduce.wrong.event.type %s", user.MachineSecretHashUpdatedType) + } + return handler.NewMultiStatement( + e, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(UserChangeDateCol, e.CreationDate()), + handler.NewCol(UserSequenceCol, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(UserIDCol, e.Aggregate().ID), + handler.NewCond(UserInstanceIDCol, e.Aggregate().InstanceID), + }, + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(MachineSecretCol, e.HashedSecret), }, []handler.Condition{ handler.NewCond(MachineUserIDCol, e.Aggregate().ID), diff --git a/internal/query/projection/user_test.go b/internal/query/projection/user_test.go index 2ef6caff93..1359b624e4 100644 --- a/internal/query/projection/user_test.go +++ b/internal/query/projection/user_test.go @@ -4,7 +4,6 @@ import ( "database/sql" "testing" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" @@ -52,7 +51,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -66,7 +65,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -82,7 +81,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -122,7 +121,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -136,7 +135,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -152,7 +151,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -187,7 +186,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -201,7 +200,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -217,7 +216,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -258,7 +257,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -272,7 +271,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -288,7 +287,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -328,7 +327,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -342,7 +341,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -358,7 +357,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -393,7 +392,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -407,7 +406,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -423,7 +422,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -453,7 +452,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateInitial, "agg-id", @@ -481,7 +480,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateInitial, "agg-id", @@ -509,7 +508,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateActive, "agg-id", @@ -537,7 +536,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateActive, "agg-id", @@ -565,7 +564,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateLocked, @@ -595,7 +594,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateActive, @@ -625,7 +624,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateInactive, @@ -655,7 +654,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateActive, @@ -685,7 +684,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users11 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.users12 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -714,7 +713,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, "username", @@ -746,7 +745,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, "id@temporary.domain", @@ -783,7 +782,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -792,7 +791,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.users12_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "first-name", "last-name", @@ -832,7 +831,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -841,7 +840,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.users12_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "first-name", "last-name", @@ -876,7 +875,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -885,7 +884,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.PhoneNumber("+41 00 000 00 00"), false, @@ -894,7 +893,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "+41 00 000 00 00", Valid: true}, "agg-id", @@ -924,7 +923,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -933,7 +932,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.PhoneNumber("+41 00 000 00 00"), false, @@ -942,7 +941,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "+41 00 000 00 00", Valid: true}, "agg-id", @@ -970,7 +969,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -979,7 +978,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -988,7 +987,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1017,7 +1016,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1026,7 +1025,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1035,7 +1034,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1064,7 +1063,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1073,7 +1072,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1081,7 +1080,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1108,7 +1107,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1117,7 +1116,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1125,7 +1124,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1154,7 +1153,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1163,7 +1162,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.EmailAddress("email@zitadel.com"), false, @@ -1172,7 +1171,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "email@zitadel.com", Valid: true}, "agg-id", @@ -1202,7 +1201,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1211,7 +1210,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.EmailAddress("email@zitadel.com"), false, @@ -1220,7 +1219,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "email@zitadel.com", Valid: true}, "agg-id", @@ -1248,7 +1247,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1257,7 +1256,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1265,7 +1264,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1292,7 +1291,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1301,7 +1300,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1309,7 +1308,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1338,7 +1337,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1347,7 +1346,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "users/agg-id/avatar", "agg-id", @@ -1375,7 +1374,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1384,7 +1383,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ nil, "agg-id", @@ -1414,7 +1413,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1422,7 +1421,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1452,7 +1451,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ false, "agg-id", @@ -1460,7 +1459,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1491,7 +1490,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1505,7 +1504,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1539,7 +1538,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1553,7 +1552,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1586,7 +1585,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1595,7 +1594,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ "machine-name", "description", @@ -1626,7 +1625,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1635,7 +1634,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "machine-name", "agg-id", @@ -1665,7 +1664,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1674,7 +1673,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "description", "agg-id", @@ -1705,14 +1704,14 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - name: "reduceMachineSecretSet", + name: "reduceMachineSecretSet v1 value", args: args{ event: getEvent( testEvent( user.MachineSecretSetType, user.AggregateType, []byte(`{ - "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"} + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"} }`), ), user.MachineSecretSetEventMapper), }, @@ -1723,7 +1722,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1732,13 +1731,87 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte{117, 230, 157, 109, 231, 159}, - }, + "secret", + "agg-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceMachineSecretSet v2 value", + args: args{ + event: getEvent( + testEvent( + user.MachineSecretSetType, + user.AggregateType, + []byte(`{ + "hashedSecret": "secret" + }`), + ), user.MachineSecretSetEventMapper), + }, + reduce: (&userProjection{}).reduceMachineSecretSet, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "agg-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "agg-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceMachineSecretHashUpdated", + args: args{ + event: getEvent( + testEvent( + user.MachineSecretHashUpdatedType, + user.AggregateType, + []byte(`{ + "hashedSecret": "secret" + }`), + ), eventstore.GenericEventMapper[user.MachineSecretHashUpdatedEvent]), + }, + reduce: (&userProjection{}).reduceMachineSecretHashUpdated, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "agg-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", "agg-id", "instance-id", }, @@ -1764,7 +1837,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1773,7 +1846,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ nil, "agg-id", @@ -1801,7 +1874,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users11 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.users12 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -1828,7 +1901,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users11 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.users12 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/secret_generators.go b/internal/query/secret_generators.go index d8f838b82a..9517f53f4c 100644 --- a/internal/query/secret_generators.go +++ b/internal/query/secret_generators.go @@ -118,22 +118,6 @@ func (q *Queries) InitEncryptionGenerator(ctx context.Context, generatorType dom return crypto.NewEncryptionGenerator(cryptoConfig, algorithm), nil } -func (q *Queries) InitHashGenerator(ctx context.Context, generatorType domain.SecretGeneratorType, algorithm crypto.HashAlgorithm) (crypto.Generator, error) { - generatorConfig, err := q.SecretGeneratorByType(ctx, generatorType) - if err != nil { - return nil, err - } - cryptoConfig := crypto.GeneratorConfig{ - Length: generatorConfig.Length, - Expiry: generatorConfig.Expiry, - IncludeLowerLetters: generatorConfig.IncludeLowerLetters, - IncludeUpperLetters: generatorConfig.IncludeUpperLetters, - IncludeDigits: generatorConfig.IncludeDigits, - IncludeSymbols: generatorConfig.IncludeSymbols, - } - return crypto.NewHashGenerator(cryptoConfig, algorithm), nil -} - func (q *Queries) SecretGeneratorByType(ctx context.Context, generatorType domain.SecretGeneratorType) (generator *SecretGenerator, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/query/session.go b/internal/query/session.go index 5aa7c177b1..54afbde064 100644 --- a/internal/query/session.go +++ b/internal/query/session.go @@ -9,7 +9,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/sessions_test.go b/internal/query/sessions_test.go index 6f6a330e03..9b08b43441 100644 --- a/internal/query/sessions_test.go +++ b/internal/query/sessions_test.go @@ -31,7 +31,7 @@ var ( ` projections.sessions8.user_resource_owner,` + ` projections.sessions8.user_checked_at,` + ` projections.login_names3.login_name,` + - ` projections.users11_humans.display_name,` + + ` projections.users12_humans.display_name,` + ` projections.sessions8.password_checked_at,` + ` projections.sessions8.intent_checked_at,` + ` projections.sessions8.webauthn_checked_at,` + @@ -48,8 +48,8 @@ var ( ` projections.sessions8.expiration` + ` FROM projections.sessions8` + ` LEFT JOIN projections.login_names3 ON projections.sessions8.user_id = projections.login_names3.user_id AND projections.sessions8.instance_id = projections.login_names3.instance_id` + - ` LEFT JOIN projections.users11_humans ON projections.sessions8.user_id = projections.users11_humans.user_id AND projections.sessions8.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11 ON projections.sessions8.user_id = projections.users11.id AND projections.sessions8.instance_id = projections.users11.instance_id` + + ` LEFT JOIN projections.users12_humans ON projections.sessions8.user_id = projections.users12_humans.user_id AND projections.sessions8.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12 ON projections.sessions8.user_id = projections.users12.id AND projections.sessions8.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedSessionsQuery = regexp.QuoteMeta(`SELECT projections.sessions8.id,` + ` projections.sessions8.creation_date,` + @@ -62,7 +62,7 @@ var ( ` projections.sessions8.user_resource_owner,` + ` projections.sessions8.user_checked_at,` + ` projections.login_names3.login_name,` + - ` projections.users11_humans.display_name,` + + ` projections.users12_humans.display_name,` + ` projections.sessions8.password_checked_at,` + ` projections.sessions8.intent_checked_at,` + ` projections.sessions8.webauthn_checked_at,` + @@ -75,8 +75,8 @@ var ( ` COUNT(*) OVER ()` + ` FROM projections.sessions8` + ` LEFT JOIN projections.login_names3 ON projections.sessions8.user_id = projections.login_names3.user_id AND projections.sessions8.instance_id = projections.login_names3.instance_id` + - ` LEFT JOIN projections.users11_humans ON projections.sessions8.user_id = projections.users11_humans.user_id AND projections.sessions8.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11 ON projections.sessions8.user_id = projections.users11.id AND projections.sessions8.instance_id = projections.users11.instance_id` + + ` LEFT JOIN projections.users12_humans ON projections.sessions8.user_id = projections.users12_humans.user_id AND projections.sessions8.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12 ON projections.sessions8.user_id = projections.users12.id AND projections.sessions8.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) sessionCols = []string{ diff --git a/internal/query/smtp.go b/internal/query/smtp.go index 81202f2e51..b9dd3e0bff 100644 --- a/internal/query/smtp.go +++ b/internal/query/smtp.go @@ -11,20 +11,27 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/call" "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) +type SMTPConfigsSearchQueries struct { + SearchRequest + Queries []SearchQuery +} + +type SMTPConfigs struct { + SearchResponse + Configs []*SMTPConfig +} + var ( smtpConfigsTable = table{ name: projection.SMTPConfigProjectionTable, instanceIDCol: projection.SMTPConfigColumnInstanceID, } - SMTPConfigColumnAggregateID = Column{ - name: projection.SMTPConfigColumnAggregateID, - table: smtpConfigsTable, - } SMTPConfigColumnCreationDate = Column{ name: projection.SMTPConfigColumnCreationDate, table: smtpConfigsTable, @@ -73,20 +80,25 @@ var ( name: projection.SMTPConfigColumnSMTPPassword, table: smtpConfigsTable, } + SMTPConfigColumnID = Column{ + name: projection.SMTPConfigColumnID, + table: smtpConfigsTable, + } + SMTPConfigColumnState = Column{ + name: projection.SMTPConfigColumnState, + table: smtpConfigsTable, + } + SMTPConfigColumnDescription = Column{ + name: projection.SMTPConfigColumnDescription, + table: smtpConfigsTable, + } ) -type SMTPConfigs struct { - SearchResponse - SMTPConfigs []*SMTPConfig -} - type SMTPConfig struct { - AggregateID string - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - Sequence uint64 - + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + Sequence uint64 TLS bool SenderAddress string SenderName string @@ -94,19 +106,44 @@ type SMTPConfig struct { Host string User string Password *crypto.CryptoValue + ID string + State domain.SMTPConfigState + Description string } -func (q *Queries) SMTPConfigByAggregateID(ctx context.Context, aggregateID string) (config *SMTPConfig, err error) { +func (q *Queries) SMTPConfigActive(ctx context.Context, resourceOwner string) (config *SMTPConfig, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareSMTPConfigQuery(ctx, q.client) query, args, err := stmt.Where(sq.Eq{ - SMTPConfigColumnAggregateID.identifier(): aggregateID, - SMTPConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), + SMTPConfigColumnResourceOwner.identifier(): resourceOwner, + SMTPConfigColumnInstanceID.identifier(): resourceOwner, + SMTPConfigColumnState.identifier(): domain.SMTPConfigStateActive, }).ToSql() if err != nil { - return nil, zerrors.ThrowInternal(err, "QUERY-3m9sl", "Errors.Query.SQLStatment") + return nil, zerrors.ThrowInternal(err, "QUERY-3m9sl", "Errors.Query.SQLStatement") + } + + err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { + config, err = scan(row) + return err + }, query, args...) + return config, err +} + +func (q *Queries) SMTPConfigByID(ctx context.Context, instanceID, resourceOwner, id string) (config *SMTPConfig, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + stmt, scan := prepareSMTPConfigQuery(ctx, q.client) + query, args, err := stmt.Where(sq.Eq{ + SMTPConfigColumnResourceOwner.identifier(): resourceOwner, + SMTPConfigColumnInstanceID.identifier(): instanceID, + SMTPConfigColumnID.identifier(): id, + }).ToSql() + if err != nil { + return nil, zerrors.ThrowInternal(err, "QUERY-8f8gw", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { @@ -120,7 +157,6 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB password := new(crypto.CryptoValue) return sq.Select( - SMTPConfigColumnAggregateID.identifier(), SMTPConfigColumnCreationDate.identifier(), SMTPConfigColumnChangeDate.identifier(), SMTPConfigColumnResourceOwner.identifier(), @@ -131,13 +167,15 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB SMTPConfigColumnReplyToAddress.identifier(), SMTPConfigColumnSMTPHost.identifier(), SMTPConfigColumnSMTPUser.identifier(), - SMTPConfigColumnSMTPPassword.identifier()). + SMTPConfigColumnSMTPPassword.identifier(), + SMTPConfigColumnID.identifier(), + SMTPConfigColumnState.identifier(), + SMTPConfigColumnDescription.identifier()). From(smtpConfigsTable.identifier() + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*SMTPConfig, error) { config := new(SMTPConfig) err := row.Scan( - &config.AggregateID, &config.CreationDate, &config.ChangeDate, &config.ResourceOwner, @@ -149,6 +187,9 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB &config.Host, &config.User, &password, + &config.ID, + &config.State, + &config.Description, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -160,3 +201,79 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB return config, nil } } + +func prepareSMTPConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*SMTPConfigs, error)) { + return sq.Select( + SMTPConfigColumnCreationDate.identifier(), + SMTPConfigColumnChangeDate.identifier(), + SMTPConfigColumnResourceOwner.identifier(), + SMTPConfigColumnSequence.identifier(), + SMTPConfigColumnTLS.identifier(), + SMTPConfigColumnSenderAddress.identifier(), + SMTPConfigColumnSenderName.identifier(), + SMTPConfigColumnReplyToAddress.identifier(), + SMTPConfigColumnSMTPHost.identifier(), + SMTPConfigColumnSMTPUser.identifier(), + SMTPConfigColumnSMTPPassword.identifier(), + SMTPConfigColumnID.identifier(), + SMTPConfigColumnState.identifier(), + SMTPConfigColumnDescription.identifier(), + countColumn.identifier()). + From(smtpConfigsTable.identifier() + db.Timetravel(call.Took(ctx))). + PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*SMTPConfigs, error) { + configs := &SMTPConfigs{Configs: []*SMTPConfig{}} + for rows.Next() { + config := new(SMTPConfig) + err := rows.Scan( + &config.CreationDate, + &config.ChangeDate, + &config.ResourceOwner, + &config.Sequence, + &config.TLS, + &config.SenderAddress, + &config.SenderName, + &config.ReplyToAddress, + &config.Host, + &config.User, + &config.Password, + &config.ID, + &config.State, + &config.Description, + &configs.Count, + ) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, zerrors.ThrowNotFound(err, "QUERY-fwofw", "Errors.SMTPConfig.NotFound") + } + return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal") + } + configs.Configs = append(configs.Configs, config) + } + return configs, nil + } +} + +func (q *Queries) SearchSMTPConfigs(ctx context.Context, queries *SMTPConfigsSearchQueries) (configs *SMTPConfigs, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + query, scan := prepareSMTPConfigsQuery(ctx, q.client) + stmt, args, err := queries.toQuery(query). + Where(sq.Eq{ + SMTPConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), + }).ToSql() + if err != nil { + return nil, zerrors.ThrowInvalidArgument(err, "QUERY-sZ7Cx", "Errors.Query.InvalidRequest") + } + + err = q.client.QueryContext(ctx, func(rows *sql.Rows) error { + configs, err = scan(rows) + return err + }, stmt, args...) + if err != nil { + return nil, zerrors.ThrowInternal(err, "QUERY-tOpKN", "Errors.Internal") + } + configs.State, err = q.latestState(ctx, smsConfigsTable) + return configs, err +} diff --git a/internal/query/smtp_test.go b/internal/query/smtp_test.go index 1e72fcdde3..e1824fd277 100644 --- a/internal/query/smtp_test.go +++ b/internal/query/smtp_test.go @@ -9,26 +9,28 @@ import ( "testing" "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" ) var ( - prepareSMTPConfigStmt = `SELECT projections.smtp_configs1.aggregate_id,` + - ` projections.smtp_configs1.creation_date,` + - ` projections.smtp_configs1.change_date,` + - ` projections.smtp_configs1.resource_owner,` + - ` projections.smtp_configs1.sequence,` + - ` projections.smtp_configs1.tls,` + - ` projections.smtp_configs1.sender_address,` + - ` projections.smtp_configs1.sender_name,` + - ` projections.smtp_configs1.reply_to_address,` + - ` projections.smtp_configs1.host,` + - ` projections.smtp_configs1.username,` + - ` projections.smtp_configs1.password` + - ` FROM projections.smtp_configs1` + + prepareSMTPConfigStmt = `SELECT projections.smtp_configs2.creation_date,` + + ` projections.smtp_configs2.change_date,` + + ` projections.smtp_configs2.resource_owner,` + + ` projections.smtp_configs2.sequence,` + + ` projections.smtp_configs2.tls,` + + ` projections.smtp_configs2.sender_address,` + + ` projections.smtp_configs2.sender_name,` + + ` projections.smtp_configs2.reply_to_address,` + + ` projections.smtp_configs2.host,` + + ` projections.smtp_configs2.username,` + + ` projections.smtp_configs2.password,` + + ` projections.smtp_configs2.id,` + + ` projections.smtp_configs2.state,` + + ` projections.smtp_configs2.description` + + ` FROM projections.smtp_configs2` + ` AS OF SYSTEM TIME '-1 ms'` prepareSMTPConfigCols = []string{ - "aggregate_id", "creation_date", "change_date", "resource_owner", @@ -40,6 +42,9 @@ var ( "smtp_host", "smtp_user", "smtp_password", + "id", + "state", + "description", } ) @@ -80,7 +85,6 @@ func Test_SMTPConfigsPrepares(t *testing.T) { regexp.QuoteMeta(prepareSMTPConfigStmt), prepareSMTPConfigCols, []driver.Value{ - "agg-id", testNow, testNow, "ro", @@ -92,11 +96,13 @@ func Test_SMTPConfigsPrepares(t *testing.T) { "host", "user", &crypto.CryptoValue{}, + "2232323", + domain.SMTPConfigStateActive, + "test", }, ), }, object: &SMTPConfig{ - AggregateID: "agg-id", CreationDate: testNow, ChangeDate: testNow, ResourceOwner: "ro", @@ -108,6 +114,93 @@ func Test_SMTPConfigsPrepares(t *testing.T) { Host: "host", User: "user", Password: &crypto.CryptoValue{}, + ID: "2232323", + State: domain.SMTPConfigStateActive, + Description: "test", + }, + }, + { + name: "prepareSMTPConfigQuery another config found", + prepare: prepareSMTPConfigQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(prepareSMTPConfigStmt), + prepareSMTPConfigCols, + []driver.Value{ + testNow, + testNow, + "ro", + uint64(20211109), + true, + "sender2", + "name2", + "reply-to2", + "host2", + "user2", + &crypto.CryptoValue{}, + "44442323", + domain.SMTPConfigStateInactive, + "test2", + }, + ), + }, + object: &SMTPConfig{ + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + TLS: true, + SenderAddress: "sender2", + SenderName: "name2", + ReplyToAddress: "reply-to2", + Host: "host2", + User: "user2", + Password: &crypto.CryptoValue{}, + ID: "44442323", + State: domain.SMTPConfigStateInactive, + Description: "test2", + }, + }, + { + name: "prepareSMTPConfigQuery yet another config found", + prepare: prepareSMTPConfigQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(prepareSMTPConfigStmt), + prepareSMTPConfigCols, + []driver.Value{ + testNow, + testNow, + "ro", + uint64(20211109), + true, + "sender3", + "name3", + "reply-to3", + "host3", + "user3", + &crypto.CryptoValue{}, + "23234444", + domain.SMTPConfigStateInactive, + "test3", + }, + ), + }, + object: &SMTPConfig{ + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + TLS: true, + SenderAddress: "sender3", + SenderName: "name3", + ReplyToAddress: "reply-to3", + Host: "host3", + User: "user3", + Password: &crypto.CryptoValue{}, + ID: "23234444", + State: domain.SMTPConfigStateInactive, + Description: "test3", }, }, { diff --git a/internal/query/system_features.go b/internal/query/system_features.go index 0c18219e6e..65087e92dc 100644 --- a/internal/query/system_features.go +++ b/internal/query/system_features.go @@ -20,6 +20,7 @@ type SystemFeatures struct { LegacyIntrospection FeatureSource[bool] UserSchema FeatureSource[bool] TokenExchange FeatureSource[bool] + Actions FeatureSource[bool] } func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) { diff --git a/internal/query/system_features_model.go b/internal/query/system_features_model.go index 4067b94532..243dc4e6c4 100644 --- a/internal/query/system_features_model.go +++ b/internal/query/system_features_model.go @@ -50,6 +50,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder { feature_v2.SystemLegacyIntrospectionEventType, feature_v2.SystemUserSchemaEventType, feature_v2.SystemTokenExchangeEventType, + feature_v2.SystemActionsEventType, ). Builder().ResourceOwner(m.ResourceOwner) } @@ -78,6 +79,8 @@ func (m *SystemFeaturesReadModel) reduceBoolFeature(event *feature_v2.SetEvent[b dst = &m.system.UserSchema case feature.KeyTokenExchange: dst = &m.system.TokenExchange + case feature.KeyActions: + dst = &m.system.Actions } *dst = FeatureSource[bool]{ diff --git a/internal/query/system_features_test.go b/internal/query/system_features_test.go index f1dc184158..e460d38cec 100644 --- a/internal/query/system_features_test.go +++ b/internal/query/system_features_test.go @@ -61,6 +61,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) { context.Background(), aggregate, feature_v2.SystemUserSchemaEventType, false, )), + eventFromEventPusher(feature_v2.NewSetEvent[bool]( + context.Background(), aggregate, + feature_v2.SystemActionsEventType, true, + )), ), ), want: &SystemFeatures{ @@ -83,6 +87,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) { Level: feature.LevelSystem, Value: false, }, + Actions: FeatureSource[bool]{ + Level: feature.LevelSystem, + Value: true, + }, }, }, { @@ -105,6 +113,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) { context.Background(), aggregate, feature_v2.SystemUserSchemaEventType, false, )), + eventFromEventPusher(feature_v2.NewSetEvent[bool]( + context.Background(), aggregate, + feature_v2.SystemActionsEventType, false, + )), eventFromEventPusher(feature_v2.NewResetEvent( context.Background(), aggregate, feature_v2.SystemResetEventType, @@ -135,6 +147,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) { Level: feature.LevelUnspecified, Value: false, }, + Actions: FeatureSource[bool]{ + Level: feature.LevelUnspecified, + Value: false, + }, }, }, { @@ -157,6 +173,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) { context.Background(), aggregate, feature_v2.SystemUserSchemaEventType, false, )), + eventFromEventPusher(feature_v2.NewSetEvent[bool]( + context.Background(), aggregate, + feature_v2.SystemActionsEventType, false, + )), eventFromEventPusher(feature_v2.NewResetEvent( context.Background(), aggregate, feature_v2.SystemResetEventType, @@ -187,6 +207,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) { Level: feature.LevelUnspecified, Value: false, }, + Actions: FeatureSource[bool]{ + Level: feature.LevelUnspecified, + Value: false, + }, }, }, } diff --git a/internal/query/testdata/oidc_client_jwt.json b/internal/query/testdata/oidc_client_jwt.json index df871815dd..1bca6044d4 100644 --- a/internal/query/testdata/oidc_client_jwt.json +++ b/internal/query/testdata/oidc_client_jwt.json @@ -1,6 +1,7 @@ { "instance_id": "230690539048009730", "app_id": "236647088211886082", + "state": 1, "client_id": "236647088211951618@tests", "client_secret": null, "redirect_uris": ["http://localhost:9999/auth/callback"], @@ -17,7 +18,7 @@ "clock_skew": 1000000000, "additional_origins": ["https://example.com"], "project_id": "236645808328409090", - "state": 1, + "project_role_assertion": true, "project_role_keys": ["role1", "role2"], "public_keys": { "236647201860747266": "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFB\nT0NBUThBTUlJQkNnS0NBUUVBMnVmQUwxYjcyYkl5MWFyK1dzNmIKR29oSkpRRkI3ZGZSYXBEcWVx\nTThVa3A2Q1ZkUHpxL3BPejF2aUFxNTB5eldaSnJ5Risyd3NoRkFLR0Y5QTIvQgoyWWY5YkpYUFov\nS2JrRnJZVDNOVHZZRGt2bGFTVGw5bU1uenJVMjlzNDhGMVBUV0tmQitDM2FNc09FRzFCdWZWCnM2\nM3FGNG5yRVBqU2JobGpJY285RlpxNFhwcEl6aE1RMGZEZEEvK1h5Z0NKcXZ1YUwwTGliTTFLcmxV\nZG51NzEKWWVraFNKakVQbnZPaXNYSWs0SVh5d29HSU93dGp4a0R2Tkl0UXZhTVZsZHI0L2tiNnV2\nYmdkV3dxNUV3QlpYcQpsb3cya3lKb3YzOFY0VWsySThrdVhwTGNucnB3NVRpbzJvb2lVRTI3YjB2\nSFpxQktPZWk5VW84OHFDcm4zRUt4CjZRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0t\nLS0K" diff --git a/internal/query/testdata/oidc_client_no_settings.json b/internal/query/testdata/oidc_client_no_settings.json index 83d810d669..59aff6ea42 100644 --- a/internal/query/testdata/oidc_client_no_settings.json +++ b/internal/query/testdata/oidc_client_no_settings.json @@ -1,6 +1,7 @@ { "instance_id": "239520764275982338", "app_id": "239520764276441090", + "state": 1, "client_id": "239520764779364354@zitadel", "client_secret": null, "redirect_uris": [ @@ -23,7 +24,7 @@ "clock_skew": 0, "additional_origins": null, "project_id": "239520764276178946", - "state": 1, + "project_role_assertion": false, "project_role_keys": null, "public_keys": null, "settings": null diff --git a/internal/query/testdata/oidc_client_public.json b/internal/query/testdata/oidc_client_public.json index 47cf750c8b..020c60311b 100644 --- a/internal/query/testdata/oidc_client_public.json +++ b/internal/query/testdata/oidc_client_public.json @@ -1,6 +1,7 @@ { "instance_id": "230690539048009730", "app_id": "236646457053020162", + "state": 1, "client_id": "236646457053085698@tests", "client_secret": null, "redirect_uris": ["http://localhost:9999/auth/callback"], @@ -17,7 +18,7 @@ "clock_skew": 0, "additional_origins": null, "project_id": "236645808328409090", - "state": 1, + "project_role_assertion": true, "project_role_keys": ["role1", "role2"], "public_keys": null, "settings": { diff --git a/internal/query/testdata/oidc_client_secret.json b/internal/query/testdata/oidc_client_secret.json index e7d5926f7f..0fb1d6f830 100644 --- a/internal/query/testdata/oidc_client_secret.json +++ b/internal/query/testdata/oidc_client_secret.json @@ -1,13 +1,9 @@ { "instance_id": "230690539048009730", "app_id": "236646858984783874", + "state": 1, "client_id": "236646858984849410@tests", - "client_secret": { - "KeyID": "", - "Crypted": "JDJhJDE0JE96WjBYRVpaRXREMTNweS9FUGJhMmV2c1M2V2NLWjVvclZNajlwV0hFR0VIbUx1MmgzUEZx", - "Algorithm": "bcrypt", - "CryptoType": 1 - }, + "client_secret": "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq", "redirect_uris": ["http://localhost:9999/auth/callback"], "response_types": [0], "grant_types": [0], @@ -22,7 +18,7 @@ "clock_skew": 0, "additional_origins": null, "project_id": "236645808328409090", - "state": 1, + "project_role_assertion": false, "project_role_keys": ["role1", "role2"], "public_keys": null, "settings": { diff --git a/internal/query/user.go b/internal/query/user.go index a6bfaedf0f..cb48346e45 100644 --- a/internal/query/user.go +++ b/internal/query/user.go @@ -13,7 +13,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/call" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query/projection" @@ -94,7 +93,7 @@ type Phone struct { type Machine struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` - Secret *crypto.CryptoValue `json:"secret,omitempty"` + EncodedSecret string `json:"encoded_hash,omitempty"` AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"` } @@ -827,7 +826,7 @@ func scanUser(row *sql.Row) (*User, error) { machineID := sql.NullString{} name := sql.NullString{} description := sql.NullString{} - var secret *crypto.CryptoValue + encodedHash := sql.NullString{} accessTokenType := sql.NullInt32{} err := row.Scan( @@ -857,7 +856,7 @@ func scanUser(row *sql.Row) (*User, error) { &machineID, &name, &description, - &secret, + &encodedHash, &accessTokenType, &count, ) @@ -890,7 +889,7 @@ func scanUser(row *sql.Row) (*User, error) { u.Machine = &Machine{ Name: name.String, Description: description.String, - Secret: secret, + EncodedSecret: encodedHash.String, AccessTokenType: domain.OIDCTokenType(accessTokenType.Int32), } } @@ -1360,7 +1359,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde machineID := sql.NullString{} name := sql.NullString{} description := sql.NullString{} - secret := new(crypto.CryptoValue) + encodedHash := sql.NullString{} accessTokenType := sql.NullInt32{} err := rows.Scan( @@ -1390,7 +1389,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde &machineID, &name, &description, - secret, + &encodedHash, &accessTokenType, &count, ) @@ -1422,7 +1421,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde u.Machine = &Machine{ Name: name.String, Description: description.String, - Secret: secret, + EncodedSecret: encodedHash.String, AccessTokenType: domain.OIDCTokenType(accessTokenType.Int32), } } diff --git a/internal/query/user_auth_method_test.go b/internal/query/user_auth_method_test.go index 66433c208f..a4a66cac04 100644 --- a/internal/query/user_auth_method_test.go +++ b/internal/query/user_auth_method_test.go @@ -39,38 +39,38 @@ var ( "method_type", "count", } - prepareActiveAuthMethodTypesStmt = `SELECT projections.users11_notifications.password_set,` + + prepareActiveAuthMethodTypesStmt = `SELECT projections.users12_notifications.password_set,` + ` auth_method_types.method_type,` + ` user_idps_count.count` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_notifications ON projections.users11.id = projections.users11_notifications.user_id AND projections.users11.instance_id = projections.users11_notifications.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id` + ` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` + ` WHERE auth_method_types.state = $1) AS auth_method_types` + - ` ON auth_method_types.user_id = projections.users11.id AND auth_method_types.instance_id = projections.users11.instance_id` + + ` ON auth_method_types.user_id = projections.users12.id AND auth_method_types.instance_id = projections.users12.instance_id` + ` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` + ` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` + - ` ON user_idps_count.user_id = projections.users11.id AND user_idps_count.instance_id = projections.users11.instance_id` + + ` ON user_idps_count.user_id = projections.users12.id AND user_idps_count.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms` prepareActiveAuthMethodTypesCols = []string{ "password_set", "method_type", "idps_count", } - prepareAuthMethodTypesRequiredStmt = `SELECT projections.users11_notifications.password_set,` + + prepareAuthMethodTypesRequiredStmt = `SELECT projections.users12_notifications.password_set,` + ` auth_method_types.method_type,` + ` user_idps_count.count,` + ` auth_methods_force_mfa.force_mfa,` + ` auth_methods_force_mfa.force_mfa_local_only` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_notifications ON projections.users11.id = projections.users11_notifications.user_id AND projections.users11.instance_id = projections.users11_notifications.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id` + ` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` + ` WHERE auth_method_types.state = $1) AS auth_method_types` + - ` ON auth_method_types.user_id = projections.users11.id AND auth_method_types.instance_id = projections.users11.instance_id` + + ` ON auth_method_types.user_id = projections.users12.id AND auth_method_types.instance_id = projections.users12.instance_id` + ` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` + ` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` + - ` ON user_idps_count.user_id = projections.users11.id AND user_idps_count.instance_id = projections.users11.instance_id` + + ` ON user_idps_count.user_id = projections.users12.id AND user_idps_count.instance_id = projections.users12.instance_id` + ` LEFT JOIN (SELECT auth_methods_force_mfa.force_mfa, auth_methods_force_mfa.force_mfa_local_only, auth_methods_force_mfa.instance_id, auth_methods_force_mfa.aggregate_id FROM projections.login_policies5 AS auth_methods_force_mfa ORDER BY auth_methods_force_mfa.is_default) AS auth_methods_force_mfa` + - ` ON (auth_methods_force_mfa.aggregate_id = projections.users11.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users11.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users11.instance_id` + + ` ON (auth_methods_force_mfa.aggregate_id = projections.users12.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users12.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms ` prepareAuthMethodTypesRequiredCols = []string{ diff --git a/internal/query/user_by_id.sql b/internal/query/user_by_id.sql index de96daab80..766464605d 100644 --- a/internal/query/user_by_id.sql +++ b/internal/query/user_by_id.sql @@ -65,14 +65,14 @@ SELECT , m.secret , m.access_token_type , count(*) OVER () -FROM projections.users11 u +FROM projections.users12 u LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON u.id = h.user_id AND u.instance_id = h.instance_id LEFT JOIN - projections.users11_machines m + projections.users12_machines m ON u.id = m.user_id AND u.instance_id = m.instance_id diff --git a/internal/query/user_by_login_name.sql b/internal/query/user_by_login_name.sql index 08f46aa105..fb513a953b 100644 --- a/internal/query/user_by_login_name.sql +++ b/internal/query/user_by_login_name.sql @@ -4,32 +4,25 @@ WITH found_users AS ( , u.instance_id , u.resource_owner , u.user_name + , COALESCE(p_custom.must_be_domain, p_default.must_be_domain) as must_be_domain FROM projections.login_names3_users u - JOIN lateral ( - SELECT - p.must_be_domain - FROM - projections.login_names3_policies p - WHERE - u.instance_id = p.instance_id + LEFT JOIN projections.login_names3_policies p_custom + ON u.instance_id = p_custom.instance_id + AND p_custom.instance_id = $4 AND p_custom.resource_owner = u.resource_owner + LEFT JOIN projections.login_names3_policies p_default + ON u.instance_id = p_default.instance_id + AND p_default.instance_id = $4 AND p_default.is_default IS TRUE AND ( - (p.is_default IS TRUE AND p.instance_id = $4) - OR (p.instance_id = $4 AND p.resource_owner = u.resource_owner) + (COALESCE(p_custom.must_be_domain, p_default.must_be_domain) IS TRUE AND u.user_name_lower = $1) + OR (COALESCE(p_custom.must_be_domain, p_default.must_be_domain) IS FALSE AND u.user_name_lower = $3) ) - AND ( - (p.must_be_domain IS TRUE AND user_name_lower = $1) - OR (p.must_be_domain IS FALSE AND user_name_lower = $3) - ) - ORDER BY is_default - LIMIT 1 - ) p ON TRUE JOIN projections.login_names3_domains d ON u.instance_id = d.instance_id AND u.resource_owner = d.resource_owner - AND CASE WHEN p.must_be_domain THEN d.name_lower = $2 ELSE TRUE END + AND CASE WHEN COALESCE(p_custom.must_be_domain, p_default.must_be_domain) THEN d.name_lower = $2 ELSE TRUE END WHERE u.instance_id = $4 AND u.user_name_lower IN ( @@ -44,27 +37,13 @@ login_names AS (SELECT , fu.user_name , d.name domain_name , d.is_primary - , p.must_be_domain - , CASE WHEN p.must_be_domain + , fu.must_be_domain + , CASE WHEN fu.must_be_domain THEN concat(fu.user_name, '@', d.name) ELSE fu.user_name END login_name FROM found_users fu - JOIN lateral ( - SELECT - p.must_be_domain - FROM - projections.login_names3_policies p - WHERE - fu.instance_id = p.instance_id - AND ( - (p.is_default IS TRUE AND p.instance_id = $4) - OR (p.instance_id = $4 AND p.resource_owner = fu.resource_owner) - ) - ORDER BY is_default - LIMIT 1 - ) p ON TRUE JOIN projections.login_names3_domains d ON @@ -103,17 +82,17 @@ SELECT , count(*) OVER () FROM found_users fu JOIN - projections.users11 u + projections.users12 u ON fu.id = u.id AND fu.instance_id = u.instance_id LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON fu.id = h.user_id AND fu.instance_id = h.instance_id LEFT JOIN - projections.users11_machines m + projections.users12_machines m ON fu.id = m.user_id AND fu.instance_id = m.instance_id diff --git a/internal/query/user_grant.go b/internal/query/user_grant.go index a6d92c3eaf..71031b5136 100644 --- a/internal/query/user_grant.go +++ b/internal/query/user_grant.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/user_grant_test.go b/internal/query/user_grant_test.go index c1753c34af..2e5b1c3bf6 100644 --- a/internal/query/user_grant_test.go +++ b/internal/query/user_grant_test.go @@ -23,14 +23,14 @@ var ( ", projections.user_grants5.roles" + ", projections.user_grants5.state" + ", projections.user_grants5.user_id" + - ", projections.users11.username" + - ", projections.users11.type" + - ", projections.users11.resource_owner" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.display_name" + - ", projections.users11_humans.avatar_key" + + ", projections.users12.username" + + ", projections.users12.type" + + ", projections.users12.resource_owner" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.email" + + ", projections.users12_humans.display_name" + + ", projections.users12_humans.avatar_key" + ", projections.login_names3.login_name" + ", projections.user_grants5.resource_owner" + ", projections.orgs1.name" + @@ -41,11 +41,11 @@ var ( ", granted_orgs.name" + ", granted_orgs.primary_domain" + " FROM projections.user_grants5" + - " LEFT JOIN projections.users11 ON projections.user_grants5.user_id = projections.users11.id AND projections.user_grants5.instance_id = projections.users11.instance_id" + - " LEFT JOIN projections.users11_humans ON projections.user_grants5.user_id = projections.users11_humans.user_id AND projections.user_grants5.instance_id = projections.users11_humans.instance_id" + + " LEFT JOIN projections.users12 ON projections.user_grants5.user_id = projections.users12.id AND projections.user_grants5.instance_id = projections.users12.instance_id" + + " LEFT JOIN projections.users12_humans ON projections.user_grants5.user_id = projections.users12_humans.user_id AND projections.user_grants5.instance_id = projections.users12_humans.instance_id" + " LEFT JOIN projections.orgs1 ON projections.user_grants5.resource_owner = projections.orgs1.id AND projections.user_grants5.instance_id = projections.orgs1.instance_id" + " LEFT JOIN projections.projects4 ON projections.user_grants5.project_id = projections.projects4.id AND projections.user_grants5.instance_id = projections.projects4.instance_id" + - " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users11.resource_owner = granted_orgs.id AND projections.users11.instance_id = granted_orgs.instance_id" + + " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users12.resource_owner = granted_orgs.id AND projections.users12.instance_id = granted_orgs.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" + ` AS OF SYSTEM TIME '-1 ms' ` + " WHERE projections.login_names3.is_primary = $1") @@ -85,14 +85,14 @@ var ( ", projections.user_grants5.roles" + ", projections.user_grants5.state" + ", projections.user_grants5.user_id" + - ", projections.users11.username" + - ", projections.users11.type" + - ", projections.users11.resource_owner" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.display_name" + - ", projections.users11_humans.avatar_key" + + ", projections.users12.username" + + ", projections.users12.type" + + ", projections.users12.resource_owner" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.email" + + ", projections.users12_humans.display_name" + + ", projections.users12_humans.avatar_key" + ", projections.login_names3.login_name" + ", projections.user_grants5.resource_owner" + ", projections.orgs1.name" + @@ -104,11 +104,11 @@ var ( ", granted_orgs.primary_domain" + ", COUNT(*) OVER ()" + " FROM projections.user_grants5" + - " LEFT JOIN projections.users11 ON projections.user_grants5.user_id = projections.users11.id AND projections.user_grants5.instance_id = projections.users11.instance_id" + - " LEFT JOIN projections.users11_humans ON projections.user_grants5.user_id = projections.users11_humans.user_id AND projections.user_grants5.instance_id = projections.users11_humans.instance_id" + + " LEFT JOIN projections.users12 ON projections.user_grants5.user_id = projections.users12.id AND projections.user_grants5.instance_id = projections.users12.instance_id" + + " LEFT JOIN projections.users12_humans ON projections.user_grants5.user_id = projections.users12_humans.user_id AND projections.user_grants5.instance_id = projections.users12_humans.instance_id" + " LEFT JOIN projections.orgs1 ON projections.user_grants5.resource_owner = projections.orgs1.id AND projections.user_grants5.instance_id = projections.orgs1.instance_id" + " LEFT JOIN projections.projects4 ON projections.user_grants5.project_id = projections.projects4.id AND projections.user_grants5.instance_id = projections.projects4.instance_id" + - " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users11.resource_owner = granted_orgs.id AND projections.users11.instance_id = granted_orgs.instance_id" + + " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users12.resource_owner = granted_orgs.id AND projections.users12.instance_id = granted_orgs.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" + ` AS OF SYSTEM TIME '-1 ms' ` + " WHERE projections.login_names3.is_primary = $1") diff --git a/internal/query/user_metadata.go b/internal/query/user_metadata.go index d61d17b6b9..3aadefd01c 100644 --- a/internal/query/user_metadata.go +++ b/internal/query/user_metadata.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/user_notify_by_id.sql b/internal/query/user_notify_by_id.sql index 84c4b93e0b..94134c2b9f 100644 --- a/internal/query/user_notify_by_id.sql +++ b/internal/query/user_notify_by_id.sql @@ -62,14 +62,14 @@ SELECT , n.verified_phone , n.password_set , count(*) OVER () -FROM projections.users11 u +FROM projections.users12 u LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON u.id = h.user_id AND u.instance_id = h.instance_id LEFT JOIN - projections.users11_notifications n + projections.users12_notifications n ON u.id = n.user_id AND u.instance_id = n.instance_id diff --git a/internal/query/user_notify_by_login_name.sql b/internal/query/user_notify_by_login_name.sql index 34e699ee8a..74c5e330a0 100644 --- a/internal/query/user_notify_by_login_name.sql +++ b/internal/query/user_notify_by_login_name.sql @@ -4,74 +4,53 @@ WITH found_users AS ( , u.instance_id , u.resource_owner , u.user_name - FROM + , COALESCE(p_custom.must_be_domain, p_default.must_be_domain) as must_be_domain + FROM projections.login_names3_users u - JOIN lateral ( - SELECT - p.must_be_domain - FROM - projections.login_names3_policies p - WHERE - u.instance_id = p.instance_id + LEFT JOIN projections.login_names3_policies p_custom + ON u.instance_id = p_custom.instance_id + AND p_custom.instance_id = $4 AND p_custom.resource_owner = u.resource_owner + LEFT JOIN projections.login_names3_policies p_default + ON u.instance_id = p_default.instance_id + AND p_default.instance_id = $4 AND p_default.is_default IS TRUE AND ( - (p.is_default IS TRUE AND p.instance_id = $4) - OR (p.instance_id = $4 AND p.resource_owner = u.resource_owner) + (COALESCE(p_custom.must_be_domain, p_default.must_be_domain) IS TRUE AND u.user_name_lower = $1) + OR (COALESCE(p_custom.must_be_domain, p_default.must_be_domain) IS FALSE AND u.user_name_lower = $3) ) - AND ( - (p.must_be_domain IS TRUE AND u.user_name_lower = $1) - OR (p.must_be_domain IS FALSE AND u.user_name_lower = $3) - ) - ORDER BY is_default - LIMIT 1 - ) p ON TRUE - JOIN + JOIN projections.login_names3_domains d - ON + ON u.instance_id = d.instance_id AND u.resource_owner = d.resource_owner - AND CASE WHEN p.must_be_domain THEN d.name_lower = $2 ELSE TRUE END - WHERE + AND CASE WHEN COALESCE(p_custom.must_be_domain, p_default.must_be_domain) THEN d.name_lower = $2 ELSE TRUE END + WHERE u.instance_id = $4 AND u.user_name_lower IN ( - $1, + $1, $3 ) ), -login_names AS (SELECT +login_names AS (SELECT fu.id user_id , fu.instance_id , fu.resource_owner , fu.user_name , d.name domain_name , d.is_primary - , p.must_be_domain - , CASE WHEN p.must_be_domain + , fu.must_be_domain + , CASE WHEN fu.must_be_domain THEN concat(fu.user_name, '@', d.name) ELSE fu.user_name END login_name - FROM + FROM found_users fu - JOIN lateral ( - SELECT - p.must_be_domain - FROM - projections.login_names3_policies p - WHERE - fu.instance_id = p.instance_id - AND ( - (p.is_default IS TRUE AND p.instance_id = $4) - OR (p.instance_id = $4 AND p.resource_owner = fu.resource_owner) - ) - ORDER BY is_default - LIMIT 1 - ) p ON TRUE - JOIN + JOIN projections.login_names3_domains d - ON + ON fu.instance_id = d.instance_id AND fu.resource_owner = d.resource_owner ) -SELECT +SELECT u.id , u.creation_date , u.change_date @@ -99,20 +78,20 @@ SELECT , count(*) OVER () FROM found_users fu JOIN - projections.users11 u + projections.users12 u ON fu.id = u.id AND fu.instance_id = u.instance_id LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON fu.id = h.user_id AND fu.instance_id = h.instance_id LEFT JOIN - projections.users11_notifications n + projections.users12_notifications n ON fu.id = n.user_id AND fu.instance_id = n.instance_id -WHERE +WHERE u.instance_id = $4 ; \ No newline at end of file diff --git a/internal/query/user_password.go b/internal/query/user_password.go index aa0bc60d2c..ed77d0d3ae 100644 --- a/internal/query/user_password.go +++ b/internal/query/user_password.go @@ -72,11 +72,11 @@ func (wm *HumanPasswordReadModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { case *user.HumanAddedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanRegisteredEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanInitialCodeAddedEvent: @@ -84,7 +84,7 @@ func (wm *HumanPasswordReadModel) Reduce() error { case *user.HumanInitializedCheckSucceededEvent: wm.UserState = domain.UserStateActive case *user.HumanPasswordChangedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.Code = nil wm.PasswordCheckFailedCount = 0 diff --git a/internal/query/user_personal_access_token.go b/internal/query/user_personal_access_token.go index 47f92ffc8d..dadd635b6a 100644 --- a/internal/query/user_personal_access_token.go +++ b/internal/query/user_personal_access_token.go @@ -7,7 +7,6 @@ import ( "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" diff --git a/internal/query/user_test.go b/internal/query/user_test.go index 0119fe1c13..55dd1f1b69 100644 --- a/internal/query/user_test.go +++ b/internal/query/user_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/text/language" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" @@ -148,44 +147,44 @@ var ( preferredLoginNameQuery = `SELECT preferred_login_name.user_id, preferred_login_name.login_name, preferred_login_name.instance_id` + ` FROM projections.login_names3 AS preferred_login_name` + ` WHERE preferred_login_name.is_primary = $1` - userQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11.state,` + - ` projections.users11.type,` + - ` projections.users11.username,` + + userQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12.state,` + + ` projections.users12.type,` + + ` projections.users12.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified,` + - ` projections.users11_humans.phone,` + - ` projections.users11_humans.is_phone_verified,` + - ` projections.users11_humans.password_change_required,` + - ` projections.users11_machines.user_id,` + - ` projections.users11_machines.name,` + - ` projections.users11_machines.description,` + - ` projections.users11_machines.secret,` + - ` projections.users11_machines.access_token_type,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified,` + + ` projections.users12_humans.phone,` + + ` projections.users12_humans.is_phone_verified,` + + ` projections.users12_humans.password_change_required,` + + ` projections.users12_machines.user_id,` + + ` projections.users12_machines.name,` + + ` projections.users12_machines.description,` + + ` projections.users12_machines.secret,` + + ` projections.users12_machines.access_token_type,` + ` COUNT(*) OVER ()` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11_machines ON projections.users11.id = projections.users11_machines.user_id AND projections.users11.instance_id = projections.users11_machines.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12_machines ON projections.users12.id = projections.users12_machines.user_id AND projections.users12.instance_id = projections.users12_machines.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users11.id AND login_names.instance_id = projections.users11.instance_id` + + ` ON login_names.user_id = projections.users12.id AND login_names.instance_id = projections.users12.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users11.id AND preferred_login_name.instance_id = projections.users11.instance_id` + + ` ON preferred_login_name.user_id = projections.users12.id AND preferred_login_name.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` userCols = []string{ "id", @@ -220,21 +219,21 @@ var ( "access_token_type", "count", } - profileQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + profileQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` profileCols = []string{ "id", @@ -251,16 +250,16 @@ var ( "gender", "avatar_key", } - emailQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + emailQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` emailCols = []string{ "id", @@ -272,16 +271,16 @@ var ( "email", "is_email_verified", } - phoneQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.phone,` + - ` projections.users11_humans.is_phone_verified` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + phoneQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.phone,` + + ` projections.users12_humans.is_phone_verified` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` phoneCols = []string{ "id", @@ -293,14 +292,14 @@ var ( "phone", "is_phone_verified", } - userUniqueQuery = `SELECT projections.users11.id,` + - ` projections.users11.state,` + - ` projections.users11.username,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + userUniqueQuery = `SELECT projections.users12.id,` + + ` projections.users12.state,` + + ` projections.users12.username,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` userUniqueCols = []string{ "id", @@ -310,40 +309,40 @@ var ( "email", "is_email_verified", } - notifyUserQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11.state,` + - ` projections.users11.type,` + - ` projections.users11.username,` + + notifyUserQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12.state,` + + ` projections.users12.type,` + + ` projections.users12.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key,` + - ` projections.users11_notifications.user_id,` + - ` projections.users11_notifications.last_email,` + - ` projections.users11_notifications.verified_email,` + - ` projections.users11_notifications.last_phone,` + - ` projections.users11_notifications.verified_phone,` + - ` projections.users11_notifications.password_set,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key,` + + ` projections.users12_notifications.user_id,` + + ` projections.users12_notifications.last_email,` + + ` projections.users12_notifications.verified_email,` + + ` projections.users12_notifications.last_phone,` + + ` projections.users12_notifications.verified_phone,` + + ` projections.users12_notifications.password_set,` + ` COUNT(*) OVER ()` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11_notifications ON projections.users11.id = projections.users11_notifications.user_id AND projections.users11.instance_id = projections.users11_notifications.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users11.id AND login_names.instance_id = projections.users11.instance_id` + + ` ON login_names.user_id = projections.users12.id AND login_names.instance_id = projections.users12.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users11.id AND preferred_login_name.instance_id = projections.users11.instance_id` + + ` ON preferred_login_name.user_id = projections.users12.id AND preferred_login_name.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` notifyUserCols = []string{ "id", @@ -374,44 +373,44 @@ var ( "password_set", "count", } - usersQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11.state,` + - ` projections.users11.type,` + - ` projections.users11.username,` + + usersQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12.state,` + + ` projections.users12.type,` + + ` projections.users12.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified,` + - ` projections.users11_humans.phone,` + - ` projections.users11_humans.is_phone_verified,` + - ` projections.users11_humans.password_change_required,` + - ` projections.users11_machines.user_id,` + - ` projections.users11_machines.name,` + - ` projections.users11_machines.description,` + - ` projections.users11_machines.secret,` + - ` projections.users11_machines.access_token_type,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified,` + + ` projections.users12_humans.phone,` + + ` projections.users12_humans.is_phone_verified,` + + ` projections.users12_humans.password_change_required,` + + ` projections.users12_machines.user_id,` + + ` projections.users12_machines.name,` + + ` projections.users12_machines.description,` + + ` projections.users12_machines.secret,` + + ` projections.users12_machines.access_token_type,` + ` COUNT(*) OVER ()` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11_machines ON projections.users11.id = projections.users11_machines.user_id AND projections.users11.instance_id = projections.users11_machines.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12_machines ON projections.users12.id = projections.users12_machines.user_id AND projections.users12.instance_id = projections.users12_machines.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users11.id AND login_names.instance_id = projections.users11.instance_id` + + ` ON login_names.user_id = projections.users12.id AND login_names.instance_id = projections.users12.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users11.id AND preferred_login_name.instance_id = projections.users11.instance_id` + + ` ON preferred_login_name.user_id = projections.users12.id AND preferred_login_name.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` usersCols = []string{ "id", @@ -602,7 +601,7 @@ func Test_UserPrepares(t *testing.T) { Machine: &Machine{ Name: "name", Description: "description", - Secret: nil, + EncodedSecret: "", AccessTokenType: domain.OIDCTokenTypeBearer, }, }, @@ -643,7 +642,7 @@ func Test_UserPrepares(t *testing.T) { "id", "name", "description", - `{"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}`, + "secret", domain.OIDCTokenTypeBearer, 1, }, @@ -661,13 +660,9 @@ func Test_UserPrepares(t *testing.T) { LoginNames: database.TextArray[string]{"login_name1", "login_name2"}, PreferredLoginName: "login_name1", Machine: &Machine{ - Name: "name", - Description: "description", - Secret: &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte{117, 230, 157, 109, 231, 159}, - }, + Name: "name", + Description: "description", + EncodedSecret: "secret", AccessTokenType: domain.OIDCTokenTypeBearer, }, }, @@ -1344,7 +1339,7 @@ func Test_UserPrepares(t *testing.T) { "id", "name", "description", - `{"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}`, + "secret", domain.OIDCTokenTypeBearer, }, }, @@ -1393,13 +1388,9 @@ func Test_UserPrepares(t *testing.T) { LoginNames: database.TextArray[string]{"login_name1", "login_name2"}, PreferredLoginName: "login_name1", Machine: &Machine{ - Name: "name", - Description: "description", - Secret: &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte{117, 230, 157, 109, 231, 159}, - }, + Name: "name", + Description: "description", + EncodedSecret: "secret", AccessTokenType: domain.OIDCTokenTypeBearer, }, }, diff --git a/internal/query/userinfo.go b/internal/query/userinfo.go index 2e2c27f9bc..3231817511 100644 --- a/internal/query/userinfo.go +++ b/internal/query/userinfo.go @@ -29,11 +29,13 @@ var oidcUserInfoTriggerHandlers = sync.OnceValue(func() []*handler.Handler { } }) +// TriggerOIDCUserInfoProjections triggers all projections +// relevant to userinfo queries concurrently. func TriggerOIDCUserInfoProjections(ctx context.Context) { triggerBatch(ctx, oidcUserInfoTriggerHandlers()...) } -//go:embed embed/userinfo_by_id.sql +//go:embed userinfo_by_id.sql var oidcUserInfoQuery string func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string, roleAudience []string) (_ *OIDCUserInfo, err error) { @@ -68,3 +70,25 @@ type UserInfoOrg struct { Name string `json:"name,omitempty"` PrimaryDomain string `json:"primary_domain,omitempty"` } + +//go:embed userinfo_client_by_id.sql +var oidcUserinfoClientQuery string + +func (q *Queries) GetOIDCUserinfoClientByID(ctx context.Context, clientID string) (projectID string, projectRoleAssertion bool, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + scan := func(row *sql.Row) error { + err := row.Scan(&projectID, &projectRoleAssertion) + return err + } + + err = q.client.QueryRowContext(ctx, scan, oidcUserinfoClientQuery, authz.GetInstance(ctx).InstanceID(), clientID) + if errors.Is(err, sql.ErrNoRows) { + return "", false, zerrors.ThrowNotFound(err, "QUERY-beeW8", "Errors.App.NotFound") + } + if err != nil { + return "", false, zerrors.ThrowInternal(err, "QUERY-Ais4r", "Errors.Internal") + } + return projectID, projectRoleAssertion, nil +} diff --git a/internal/query/embed/userinfo_by_id.sql b/internal/query/userinfo_by_id.sql similarity index 96% rename from internal/query/embed/userinfo_by_id.sql rename to internal/query/userinfo_by_id.sql index 58e0ae10dd..95e6ad90c7 100644 --- a/internal/query/embed/userinfo_by_id.sql +++ b/internal/query/userinfo_by_id.sql @@ -1,6 +1,6 @@ with usr as ( select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name - from projections.users11 u + from projections.users12 u left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id where u.id = $1 and u.instance_id = $2 @@ -9,7 +9,7 @@ with usr as ( human as ( select $1 as user_id, row_to_json(r) as human from ( select first_name, last_name, nick_name, display_name, avatar_key, preferred_language, gender, email, is_email_verified, phone, is_phone_verified - from projections.users11_humans + from projections.users12_humans where user_id = $1 and instance_id = $2 ) r @@ -17,7 +17,7 @@ human as ( machine as ( select $1 as user_id, row_to_json(r) as machine from ( select name, description - from projections.users11_machines + from projections.users12_machines where user_id = $1 and instance_id = $2 ) r diff --git a/internal/query/userinfo_client_by_id.sql b/internal/query/userinfo_client_by_id.sql new file mode 100644 index 0000000000..615709c6df --- /dev/null +++ b/internal/query/userinfo_client_by_id.sql @@ -0,0 +1,6 @@ +select a.project_id, p.project_role_assertion +from projections.apps7_oidc_configs c +join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id +join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id +where c.instance_id = $1 + and c.client_id = $2; diff --git a/internal/query/userinfo_test.go b/internal/query/userinfo_test.go index 04a9edccb7..29d94d0baf 100644 --- a/internal/query/userinfo_test.go +++ b/internal/query/userinfo_test.go @@ -338,3 +338,50 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) { }) } } + +func TestQueries_GetOIDCUserinfoClientByID(t *testing.T) { + expQuery := regexp.QuoteMeta(oidcUserinfoClientQuery) + cols := []string{"project_id", "project_role_assertion"} + + tests := []struct { + name string + mock sqlExpectation + wantProjectID string + wantProjectRoleAssertion bool + wantErr error + }{ + { + name: "no rows", + mock: mockQueryErr(expQuery, sql.ErrNoRows, "instanceID", "clientID"), + wantErr: zerrors.ThrowNotFound(sql.ErrNoRows, "QUERY-beeW8", "Errors.App.NotFound"), + }, + { + name: "internal error", + mock: mockQueryErr(expQuery, sql.ErrConnDone, "instanceID", "clientID"), + wantErr: zerrors.ThrowInternal(sql.ErrConnDone, "QUERY-Ais4r", "Errors.Internal"), + }, + { + name: "found", + mock: mockQuery(expQuery, cols, []driver.Value{"projectID", true}, "instanceID", "clientID"), + wantProjectID: "projectID", + wantProjectRoleAssertion: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + execMock(t, tt.mock, func(db *sql.DB) { + q := &Queries{ + client: &database.DB{ + DB: db, + Database: &prepareDB{}, + }, + } + ctx := authz.NewMockContext("instanceID", "orgID", "loginClient") + gotProjectID, gotProjectRoleAssertion, err := q.GetOIDCUserinfoClientByID(ctx, "clientID") + require.ErrorIs(t, err, tt.wantErr) + assert.Equal(t, tt.wantProjectID, gotProjectID) + assert.Equal(t, tt.wantProjectRoleAssertion, gotProjectRoleAssertion) + }) + }) + } +} diff --git a/internal/repository/deviceauth/device_auth.go b/internal/repository/deviceauth/device_auth.go index 54f8eb623e..4822c0b332 100644 --- a/internal/repository/deviceauth/device_auth.go +++ b/internal/repository/deviceauth/device_auth.go @@ -23,6 +23,7 @@ type AddedEvent struct { UserCode string Expires time.Time Scopes []string + Audience []string State domain.DeviceAuthState } @@ -46,12 +47,13 @@ func NewAddedEvent( userCode string, expires time.Time, scopes []string, + audience []string, ) *AddedEvent { return &AddedEvent{ eventstore.NewBaseEventForPush( ctx, aggregate, AddedEventType, ), - clientID, deviceCode, userCode, expires, scopes, domain.DeviceAuthStateInitiated} + clientID, deviceCode, userCode, expires, scopes, audience, domain.DeviceAuthStateInitiated} } type ApprovedEvent struct { diff --git a/internal/repository/feature/feature_v2/eventstore.go b/internal/repository/feature/feature_v2/eventstore.go index 7b8d7e2a01..26f33661fe 100644 --- a/internal/repository/feature/feature_v2/eventstore.go +++ b/internal/repository/feature/feature_v2/eventstore.go @@ -11,10 +11,12 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, SystemLegacyIntrospectionEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, SystemUserSchemaEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, SystemTokenExchangeEventType, eventstore.GenericEventMapper[SetEvent[bool]]) + eventstore.RegisterFilterEventMapper(AggregateType, SystemActionsEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceTriggerIntrospectionProjectionsEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceLegacyIntrospectionEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceUserSchemaEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceTokenExchangeEventType, eventstore.GenericEventMapper[SetEvent[bool]]) + eventstore.RegisterFilterEventMapper(AggregateType, InstanceActionsEventType, eventstore.GenericEventMapper[SetEvent[bool]]) } diff --git a/internal/repository/feature/feature_v2/feature.go b/internal/repository/feature/feature_v2/feature.go index 1c6ee9e3e1..c1f9add2d7 100644 --- a/internal/repository/feature/feature_v2/feature.go +++ b/internal/repository/feature/feature_v2/feature.go @@ -17,6 +17,7 @@ var ( SystemLegacyIntrospectionEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyLegacyIntrospection) SystemUserSchemaEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyUserSchema) SystemTokenExchangeEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyTokenExchange) + SystemActionsEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyActions) InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance) InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg) @@ -24,6 +25,7 @@ var ( InstanceLegacyIntrospectionEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLegacyIntrospection) InstanceUserSchemaEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyUserSchema) InstanceTokenExchangeEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyTokenExchange) + InstanceActionsEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyActions) ) const ( diff --git a/internal/repository/idp/idp.go b/internal/repository/idp/idp.go index 341caebb85..41ea7c3d23 100644 --- a/internal/repository/idp/idp.go +++ b/internal/repository/idp/idp.go @@ -1,22 +1,25 @@ package idp import ( + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) type Options struct { - IsCreationAllowed bool `json:"isCreationAllowed,omitempty"` - IsLinkingAllowed bool `json:"isLinkingAllowed,omitempty"` - IsAutoCreation bool `json:"isAutoCreation,omitempty"` - IsAutoUpdate bool `json:"isAutoUpdate,omitempty"` + IsCreationAllowed bool `json:"isCreationAllowed,omitempty"` + IsLinkingAllowed bool `json:"isLinkingAllowed,omitempty"` + IsAutoCreation bool `json:"isAutoCreation,omitempty"` + IsAutoUpdate bool `json:"isAutoUpdate,omitempty"` + AutoLinkingOption domain.AutoLinkingOption `json:"autoLinkingOption,omitempty"` } type OptionChanges struct { - IsCreationAllowed *bool `json:"isCreationAllowed,omitempty"` - IsLinkingAllowed *bool `json:"isLinkingAllowed,omitempty"` - IsAutoCreation *bool `json:"isAutoCreation,omitempty"` - IsAutoUpdate *bool `json:"isAutoUpdate,omitempty"` + IsCreationAllowed *bool `json:"isCreationAllowed,omitempty"` + IsLinkingAllowed *bool `json:"isLinkingAllowed,omitempty"` + IsAutoCreation *bool `json:"isAutoCreation,omitempty"` + IsAutoUpdate *bool `json:"isAutoUpdate,omitempty"` + AutoLinkingOption *domain.AutoLinkingOption `json:"autoLinkingOption,omitempty"` } func (o *Options) Changes(options Options) OptionChanges { @@ -33,6 +36,9 @@ func (o *Options) Changes(options Options) OptionChanges { if o.IsAutoUpdate != options.IsAutoUpdate { opts.IsAutoUpdate = &options.IsAutoUpdate } + if o.AutoLinkingOption != options.AutoLinkingOption { + opts.AutoLinkingOption = &options.AutoLinkingOption + } return opts } @@ -49,10 +55,13 @@ func (o *Options) ReduceChanges(changes OptionChanges) { if changes.IsAutoUpdate != nil { o.IsAutoUpdate = *changes.IsAutoUpdate } + if changes.AutoLinkingOption != nil { + o.AutoLinkingOption = *changes.AutoLinkingOption + } } func (o *OptionChanges) IsZero() bool { - return o.IsCreationAllowed == nil && o.IsLinkingAllowed == nil && o.IsAutoCreation == nil && o.IsAutoUpdate == nil + return o.IsCreationAllowed == nil && o.IsLinkingAllowed == nil && o.IsAutoCreation == nil && o.IsAutoUpdate == nil && o.AutoLinkingOption == nil } type RemovedEvent struct { diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go index 6b21ab6f29..d17dd2aa25 100644 --- a/internal/repository/instance/eventstore.go +++ b/internal/repository/instance/eventstore.go @@ -14,6 +14,8 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, SecretGeneratorRemovedEventType, SecretGeneratorRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigAddedEventType, SMTPConfigAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigChangedEventType, SMTPConfigChangedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigActivatedEventType, SMTPConfigActivatedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigDeactivatedEventType, SMTPConfigDeactivatedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigPasswordChangedEventType, SMTPConfigPasswordChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigRemovedEventType, SMTPConfigRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioAddedEventType, SMSConfigTwilioAddedEventMapper) diff --git a/internal/repository/instance/policy_password_lockout.go b/internal/repository/instance/policy_password_lockout.go index 671b539610..b05f992668 100644 --- a/internal/repository/instance/policy_password_lockout.go +++ b/internal/repository/instance/policy_password_lockout.go @@ -19,7 +19,8 @@ type LockoutPolicyAddedEvent struct { func NewLockoutPolicyAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool, ) *LockoutPolicyAddedEvent { return &LockoutPolicyAddedEvent{ @@ -28,7 +29,8 @@ func NewLockoutPolicyAddedEvent( ctx, aggregate, LockoutPolicyAddedEventType), - maxAttempts, + maxPasswordAttempts, + maxOTPAttempts, showLockoutFailure), } } diff --git a/internal/repository/instance/smtp_config.go b/internal/repository/instance/smtp_config.go index b0da86cd2d..907375160b 100644 --- a/internal/repository/instance/smtp_config.go +++ b/internal/repository/instance/smtp_config.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -14,23 +15,29 @@ const ( SMTPConfigChangedEventType = instanceEventTypePrefix + smtpConfigPrefix + "changed" SMTPConfigPasswordChangedEventType = instanceEventTypePrefix + smtpConfigPrefix + "password.changed" SMTPConfigRemovedEventType = instanceEventTypePrefix + smtpConfigPrefix + "removed" + SMTPConfigActivatedEventType = instanceEventTypePrefix + smtpConfigPrefix + "activated" + SMTPConfigDeactivatedEventType = instanceEventTypePrefix + smtpConfigPrefix + "deactivated" ) type SMTPConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - SenderAddress string `json:"senderAddress,omitempty"` - SenderName string `json:"senderName,omitempty"` - ReplyToAddress string `json:"replyToAddress,omitempty"` - TLS bool `json:"tls,omitempty"` - Host string `json:"host,omitempty"` - User string `json:"user,omitempty"` - Password *crypto.CryptoValue `json:"password,omitempty"` + ID string `json:"id,omitempty"` + Description string `json:"description,omitempty"` + SenderAddress string `json:"senderAddress,omitempty"` + SenderName string `json:"senderName,omitempty"` + ReplyToAddress string `json:"replyToAddress,omitempty"` + TLS bool `json:"tls,omitempty"` + Host string `json:"host,omitempty"` + User string `json:"user,omitempty"` + Password *crypto.CryptoValue `json:"password,omitempty"` + State domain.SMTPConfigState `json:"state,omitempty"` } func NewSMTPConfigAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, + id, description string, tls bool, senderAddress, senderName, @@ -45,6 +52,8 @@ func NewSMTPConfigAddedEvent( aggregate, SMTPConfigAddedEventType, ), + ID: id, + Description: description, TLS: tls, SenderAddress: senderAddress, SenderName: senderName, @@ -77,13 +86,15 @@ func SMTPConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error type SMTPConfigChangedEvent struct { eventstore.BaseEvent `json:"-"` - - FromAddress *string `json:"senderAddress,omitempty"` - FromName *string `json:"senderName,omitempty"` - ReplyToAddress *string `json:"replyToAddress,omitempty"` - TLS *bool `json:"tls,omitempty"` - Host *string `json:"host,omitempty"` - User *string `json:"user,omitempty"` + ID string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + FromAddress *string `json:"senderAddress,omitempty"` + FromName *string `json:"senderName,omitempty"` + ReplyToAddress *string `json:"replyToAddress,omitempty"` + TLS *bool `json:"tls,omitempty"` + Host *string `json:"host,omitempty"` + User *string `json:"user,omitempty"` + Password *crypto.CryptoValue `json:"password,omitempty"` } func (e *SMTPConfigChangedEvent) Payload() interface{} { @@ -97,6 +108,7 @@ func (e *SMTPConfigChangedEvent) UniqueConstraints() []*eventstore.UniqueConstra func NewSMTPConfigChangeEvent( ctx context.Context, aggregate *eventstore.Aggregate, + id string, changes []SMTPConfigChanges, ) (*SMTPConfigChangedEvent, error) { if len(changes) == 0 { @@ -108,6 +120,7 @@ func NewSMTPConfigChangeEvent( aggregate, SMTPConfigChangedEventType, ), + ID: id, } for _, change := range changes { change(changeEvent) @@ -117,6 +130,18 @@ func NewSMTPConfigChangeEvent( type SMTPConfigChanges func(event *SMTPConfigChangedEvent) +func ChangeSMTPConfigID(id string) func(event *SMTPConfigChangedEvent) { + return func(e *SMTPConfigChangedEvent) { + e.ID = id + } +} + +func ChangeSMTPConfigDescription(description string) func(event *SMTPConfigChangedEvent) { + return func(e *SMTPConfigChangedEvent) { + e.Description = &description + } +} + func ChangeSMTPConfigTLS(tls bool) func(event *SMTPConfigChangedEvent) { return func(e *SMTPConfigChangedEvent) { e.TLS = &tls @@ -153,6 +178,12 @@ func ChangeSMTPConfigSMTPUser(smtpUser string) func(event *SMTPConfigChangedEven } } +func ChangeSMTPConfigSMTPPassword(password *crypto.CryptoValue) func(event *SMTPConfigChangedEvent) { + return func(e *SMTPConfigChangedEvent) { + e.Password = password + } +} + func SMTPConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { e := &SMTPConfigChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), @@ -168,13 +199,14 @@ func SMTPConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, err type SMTPConfigPasswordChangedEvent struct { eventstore.BaseEvent `json:"-"` - - Password *crypto.CryptoValue `json:"password,omitempty"` + ID string `json:"id,omitempty"` + Password *crypto.CryptoValue `json:"password,omitempty"` } func NewSMTPConfigPasswordChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, + id string, password *crypto.CryptoValue, ) *SMTPConfigPasswordChangedEvent { return &SMTPConfigPasswordChangedEvent{ @@ -196,24 +228,106 @@ func (e *SMTPConfigPasswordChangedEvent) UniqueConstraints() []*eventstore.Uniqu } func SMTPConfigPasswordChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { - smtpConfigPasswordChagned := &SMTPConfigPasswordChangedEvent{ + smtpConfigPasswordChanged := &SMTPConfigPasswordChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), } - err := event.Unmarshal(smtpConfigPasswordChagned) + err := event.Unmarshal(smtpConfigPasswordChanged) if err != nil { return nil, zerrors.ThrowInternal(err, "IAM-99iNF", "unable to unmarshal smtp config password changed") } - return smtpConfigPasswordChagned, nil + return smtpConfigPasswordChanged, nil +} + +type SMTPConfigActivatedEvent struct { + eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` +} + +func NewSMTPConfigActivatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id string, +) *SMTPConfigActivatedEvent { + return &SMTPConfigActivatedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + SMTPConfigActivatedEventType, + ), + ID: id, + } +} + +func (e *SMTPConfigActivatedEvent) Payload() interface{} { + return e +} + +func (e *SMTPConfigActivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func SMTPConfigActivatedEventMapper(event eventstore.Event) (eventstore.Event, error) { + smtpConfigActivated := &SMTPConfigActivatedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := event.Unmarshal(smtpConfigActivated) + if err != nil { + return nil, zerrors.ThrowInternal(err, "IAM-KPr5t", "unable to unmarshal smtp config removed") + } + + return smtpConfigActivated, nil +} + +type SMTPConfigDeactivatedEvent struct { + eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` +} + +func NewSMTPConfigDeactivatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id string, +) *SMTPConfigDeactivatedEvent { + return &SMTPConfigDeactivatedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + SMTPConfigDeactivatedEventType, + ), + ID: id, + } +} + +func (e *SMTPConfigDeactivatedEvent) Payload() interface{} { + return e +} + +func (e *SMTPConfigDeactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func SMTPConfigDeactivatedEventMapper(event eventstore.Event) (eventstore.Event, error) { + smtpConfigDeactivated := &SMTPConfigDeactivatedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := event.Unmarshal(smtpConfigDeactivated) + if err != nil { + return nil, zerrors.ThrowInternal(err, "IAM-KPr5t", "unable to unmarshal smtp config removed") + } + + return smtpConfigDeactivated, nil } type SMTPConfigRemovedEvent struct { eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` } func NewSMTPConfigRemovedEvent( ctx context.Context, aggregate *eventstore.Aggregate, + id string, ) *SMTPConfigRemovedEvent { return &SMTPConfigRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -221,6 +335,7 @@ func NewSMTPConfigRemovedEvent( aggregate, SMTPConfigRemovedEventType, ), + ID: id, } } diff --git a/internal/repository/org/policy_password_lockout.go b/internal/repository/org/policy_password_lockout.go index e95d86eb79..8ada7f2320 100644 --- a/internal/repository/org/policy_password_lockout.go +++ b/internal/repository/org/policy_password_lockout.go @@ -20,7 +20,8 @@ type LockoutPolicyAddedEvent struct { func NewLockoutPolicyAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool, ) *LockoutPolicyAddedEvent { return &LockoutPolicyAddedEvent{ @@ -29,7 +30,8 @@ func NewLockoutPolicyAddedEvent( ctx, aggregate, LockoutPolicyAddedEventType), - maxAttempts, + maxPasswordAttempts, + maxOTPAttempts, showLockoutFailure), } } diff --git a/internal/repository/policy/policy_password_lockout.go b/internal/repository/policy/policy_password_lockout.go index 8ca0737674..ec500144ef 100644 --- a/internal/repository/policy/policy_password_lockout.go +++ b/internal/repository/policy/policy_password_lockout.go @@ -15,6 +15,7 @@ type LockoutPolicyAddedEvent struct { eventstore.BaseEvent `json:"-"` MaxPasswordAttempts uint64 `json:"maxPasswordAttempts,omitempty"` + MaxOTPAttempts uint64 `json:"maxOTPAttempts,omitempty"` ShowLockOutFailures bool `json:"showLockOutFailures,omitempty"` } @@ -28,13 +29,15 @@ func (e *LockoutPolicyAddedEvent) UniqueConstraints() []*eventstore.UniqueConstr func NewLockoutPolicyAddedEvent( base *eventstore.BaseEvent, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockOutFailures bool, ) *LockoutPolicyAddedEvent { return &LockoutPolicyAddedEvent{ BaseEvent: *base, - MaxPasswordAttempts: maxAttempts, + MaxPasswordAttempts: maxPasswordAttempts, + MaxOTPAttempts: maxOTPAttempts, ShowLockOutFailures: showLockOutFailures, } } @@ -56,6 +59,7 @@ type LockoutPolicyChangedEvent struct { eventstore.BaseEvent `json:"-"` MaxPasswordAttempts *uint64 `json:"maxPasswordAttempts,omitempty"` + MaxOTPAttempts *uint64 `json:"maxOTPAttempts,omitempty"` ShowLockOutFailures *bool `json:"showLockOutFailures,omitempty"` } @@ -85,12 +89,18 @@ func NewLockoutPolicyChangedEvent( type LockoutPolicyChanges func(*LockoutPolicyChangedEvent) -func ChangeMaxAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) { +func ChangeMaxPasswordAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) { return func(e *LockoutPolicyChangedEvent) { e.MaxPasswordAttempts = &maxAttempts } } +func ChangeMaxOTPAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) { + return func(e *LockoutPolicyChangedEvent) { + e.MaxOTPAttempts = &maxAttempts + } +} + func ChangeShowLockOutFailures(showLockOutFailures bool) func(*LockoutPolicyChangedEvent) { return func(e *LockoutPolicyChangedEvent) { e.ShowLockOutFailures = &showLockOutFailures diff --git a/internal/repository/project/api_config.go b/internal/repository/project/api_config.go index 7da3103125..eac410847b 100644 --- a/internal/repository/project/api_config.go +++ b/internal/repository/project/api_config.go @@ -15,14 +15,20 @@ const ( APIConfigSecretChangedType = applicationEventTypePrefix + "config.api.secret.changed" APIClientSecretCheckSucceededType = applicationEventTypePrefix + "api.secret.check.succeeded" APIClientSecretCheckFailedType = applicationEventTypePrefix + "api.secret.check.failed" + APIConfigSecretHashUpdatedType = applicationEventTypePrefix + "config.api.secret.updated" ) type APIConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - AppID string `json:"appId"` - ClientID string `json:"clientId,omitempty"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + AppID string `json:"appId"` + ClientID string `json:"clientId,omitempty"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` + AuthMethodType domain.APIAuthMethodType `json:"authMethodType,omitempty"` } @@ -39,7 +45,7 @@ func NewAPIConfigAddedEvent( aggregate *eventstore.Aggregate, appID, clientID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, authMethodType domain.APIAuthMethodType, ) *APIConfigAddedEvent { return &APIConfigAddedEvent{ @@ -50,7 +56,7 @@ func NewAPIConfigAddedEvent( ), AppID: appID, ClientID: clientID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, AuthMethodType: authMethodType, } } @@ -91,7 +97,6 @@ type APIConfigChangedEvent struct { eventstore.BaseEvent `json:"-"` AppID string `json:"appId"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` AuthMethodType *domain.APIAuthMethodType `json:"authMethodType,omitempty"` } @@ -151,8 +156,12 @@ func APIConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, erro type APIConfigSecretChangedEvent struct { eventstore.BaseEvent `json:"-"` - AppID string `json:"appId"` + AppID string `json:"appId"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` } func (e *APIConfigSecretChangedEvent) Payload() interface{} { @@ -167,7 +176,7 @@ func NewAPIConfigSecretChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, appID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, ) *APIConfigSecretChangedEvent { return &APIConfigSecretChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -176,7 +185,7 @@ func NewAPIConfigSecretChangedEvent( APIConfigSecretChangedType, ), AppID: appID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, } } @@ -276,3 +285,39 @@ func APIConfigSecretCheckFailedEventMapper(event eventstore.Event) (eventstore.E return e, nil } + +type APIConfigSecretHashUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` + + AppID string `json:"appId"` + HashedSecret string `json:"hashedSecret,omitempty"` +} + +func NewAPIConfigSecretHashUpdatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + appID string, + hashedSecret string, +) *APIConfigSecretHashUpdatedEvent { + return &APIConfigSecretHashUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + APIConfigSecretHashUpdatedType, + ), + AppID: appID, + HashedSecret: hashedSecret, + } +} + +func (e *APIConfigSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *APIConfigSecretHashUpdatedEvent) Payload() interface{} { + return e +} + +func (e *APIConfigSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} diff --git a/internal/repository/project/eventstore.go b/internal/repository/project/eventstore.go index 6e9bccbd3f..fe8e14a508 100644 --- a/internal/repository/project/eventstore.go +++ b/internal/repository/project/eventstore.go @@ -37,9 +37,11 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, OIDCConfigSecretChangedType, OIDCConfigSecretChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, OIDCClientSecretCheckSucceededType, OIDCConfigSecretCheckSucceededEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, OIDCClientSecretCheckFailedType, OIDCConfigSecretCheckFailedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, OIDCConfigSecretHashUpdatedType, eventstore.GenericEventMapper[OIDCConfigSecretHashUpdatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, APIConfigAddedType, APIConfigAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, APIConfigChangedType, APIConfigChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, APIConfigSecretChangedType, APIConfigSecretChangedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, APIConfigSecretHashUpdatedType, eventstore.GenericEventMapper[APIConfigSecretHashUpdatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, ApplicationKeyAddedEventType, ApplicationKeyAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, ApplicationKeyRemovedEventType, ApplicationKeyRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SAMLConfigAddedType, SAMLConfigAddedEventMapper) diff --git a/internal/repository/project/oidc_config.go b/internal/repository/project/oidc_config.go index 5534bd571e..68f2a59949 100644 --- a/internal/repository/project/oidc_config.go +++ b/internal/repository/project/oidc_config.go @@ -16,15 +16,21 @@ const ( OIDCConfigSecretChangedType = applicationEventTypePrefix + "config.oidc.secret.changed" OIDCClientSecretCheckSucceededType = applicationEventTypePrefix + "oidc.secret.check.succeeded" OIDCClientSecretCheckFailedType = applicationEventTypePrefix + "oidc.secret.check.failed" + OIDCConfigSecretHashUpdatedType = applicationEventTypePrefix + "config.oidc.secret.updated" ) type OIDCConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - Version domain.OIDCVersion `json:"oidcVersion,omitempty"` - AppID string `json:"appId"` - ClientID string `json:"clientId,omitempty"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + Version domain.OIDCVersion `json:"oidcVersion,omitempty"` + AppID string `json:"appId"` + ClientID string `json:"clientId,omitempty"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` + RedirectUris []string `json:"redirectUris,omitempty"` ResponseTypes []domain.OIDCResponseType `json:"responseTypes,omitempty"` GrantTypes []domain.OIDCGrantType `json:"grantTypes,omitempty"` @@ -55,7 +61,7 @@ func NewOIDCConfigAddedEvent( version domain.OIDCVersion, appID string, clientID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, redirectUris []string, responseTypes []domain.OIDCResponseType, grantTypes []domain.OIDCGrantType, @@ -80,7 +86,7 @@ func NewOIDCConfigAddedEvent( Version: version, AppID: appID, ClientID: clientID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, RedirectUris: redirectUris, ResponseTypes: responseTypes, GrantTypes: grantTypes, @@ -357,8 +363,12 @@ func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, err type OIDCConfigSecretChangedEvent struct { eventstore.BaseEvent `json:"-"` - AppID string `json:"appId"` + AppID string `json:"appId"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` } func (e *OIDCConfigSecretChangedEvent) Payload() interface{} { @@ -373,7 +383,7 @@ func NewOIDCConfigSecretChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, appID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, ) *OIDCConfigSecretChangedEvent { return &OIDCConfigSecretChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -382,7 +392,7 @@ func NewOIDCConfigSecretChangedEvent( OIDCConfigSecretChangedType, ), AppID: appID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, } } @@ -482,3 +492,39 @@ func OIDCConfigSecretCheckFailedEventMapper(event eventstore.Event) (eventstore. return e, nil } + +type OIDCConfigSecretHashUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` + + AppID string `json:"appId"` + HashedSecret string `json:"hashedSecret,omitempty"` +} + +func NewOIDCConfigSecretHashUpdatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + appID string, + hashedSecret string, +) *OIDCConfigSecretHashUpdatedEvent { + return &OIDCConfigSecretHashUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCConfigSecretHashUpdatedType, + ), + AppID: appID, + HashedSecret: hashedSecret, + } +} + +func (e *OIDCConfigSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *OIDCConfigSecretHashUpdatedEvent) Payload() interface{} { + return e +} + +func (e *OIDCConfigSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} diff --git a/internal/repository/user/eventstore.go b/internal/repository/user/eventstore.go index d9e88c427d..02df90f369 100644 --- a/internal/repository/user/eventstore.go +++ b/internal/repository/user/eventstore.go @@ -135,4 +135,5 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretRemovedType, MachineSecretRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckSucceededType, MachineSecretCheckSucceededEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckFailedType, MachineSecretCheckFailedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretHashUpdatedType, eventstore.GenericEventMapper[MachineSecretHashUpdatedEvent]) } diff --git a/internal/repository/user/human_password.go b/internal/repository/user/human_password.go index 9df7e6c7fd..e468cc8324 100644 --- a/internal/repository/user/human_password.go +++ b/internal/repository/user/human_password.go @@ -314,12 +314,3 @@ func NewHumanPasswordHashUpdatedEvent( EncodedHash: encoded, } } - -// SecretOrEncodedHash returns the legacy *crypto.CryptoValue if it is not nil. -// orherwise it will returns the encoded hash string. -func SecretOrEncodedHash(secret *crypto.CryptoValue, encoded string) string { - if secret != nil { - return string(secret.Crypted) - } - return encoded -} diff --git a/internal/repository/user/human_profile.go b/internal/repository/user/human_profile.go index 1c73b2dce2..266677848b 100644 --- a/internal/repository/user/human_profile.go +++ b/internal/repository/user/human_profile.go @@ -3,10 +3,11 @@ package user import ( "context" + "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" - "golang.org/x/text/language" ) const ( diff --git a/internal/repository/user/machine_secret.go b/internal/repository/user/machine_secret.go index 9d2f9fa97f..3231eacd29 100644 --- a/internal/repository/user/machine_secret.go +++ b/internal/repository/user/machine_secret.go @@ -11,6 +11,7 @@ import ( const ( machineSecretPrefix = machineEventPrefix + "secret." MachineSecretSetType = machineSecretPrefix + "set" + MachineSecretHashUpdatedType = machineSecretPrefix + "updated" MachineSecretRemovedType = machineSecretPrefix + "removed" MachineSecretCheckSucceededType = machineSecretPrefix + "check.succeeded" MachineSecretCheckFailedType = machineSecretPrefix + "check.failed" @@ -19,7 +20,10 @@ const ( type MachineSecretSetEvent struct { eventstore.BaseEvent `json:"-"` + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` } func (e *MachineSecretSetEvent) Payload() interface{} { @@ -33,7 +37,7 @@ func (e *MachineSecretSetEvent) UniqueConstraints() []*eventstore.UniqueConstrai func NewMachineSecretSetEvent( ctx context.Context, aggregate *eventstore.Aggregate, - clientSecret *crypto.CryptoValue, + hashedSecret string, ) *MachineSecretSetEvent { return &MachineSecretSetEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -41,7 +45,7 @@ func NewMachineSecretSetEvent( aggregate, MachineSecretSetType, ), - ClientSecret: clientSecret, + HashedSecret: hashedSecret, } } @@ -167,3 +171,35 @@ func MachineSecretCheckFailedEventMapper(event eventstore.Event) (eventstore.Eve return check, nil } + +type MachineSecretHashUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` + HashedSecret string `json:"hashedSecret,omitempty"` +} + +func NewMachineSecretHashUpdatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + encoded string, +) *MachineSecretHashUpdatedEvent { + return &MachineSecretHashUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + MachineSecretHashUpdatedType, + ), + HashedSecret: encoded, + } +} + +func (e *MachineSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *MachineSecretHashUpdatedEvent) Payload() interface{} { + return e +} + +func (e *MachineSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} diff --git a/internal/repository/usergrant/user_grant.go b/internal/repository/usergrant/user_grant.go index bd0908f60e..4f4b27572f 100644 --- a/internal/repository/usergrant/user_grant.go +++ b/internal/repository/usergrant/user_grant.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/zerrors" ) diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index b414b20a2d..b786966ac8 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: SMTP конфигурацията не е намерена AlreadyExists: SMTP конфигурация вече съществува + AlreadyDeactivated: SMTP конфигурацията вече е деактивирана SenderAdressNotCustomDomain: >- Адресът на изпращача трябва да бъде конфигуриран като персонализиран домейн в екземпляра. @@ -599,6 +600,15 @@ AggregateTypes: target: Целта execution: Екзекуция user_schema: Потребителска схема + auth_request: Заявка за удостоверяване + device_auth: Устройство за удостоверяване + idpintent: IDP Intent + limits: Ограничения + milestone: Етап + oidc_session: OIDC сесия + restrictions: Ограничения + system: Система + session: Сесия EventTypes: execution: @@ -644,6 +654,7 @@ EventTypes: removed: Ключът е премахнат secret: set: Секретен комплект + updated: Тайният хеш е актуализиран removed: Тайната е премахната check: succeeded: Тайната проверка е успешна @@ -1020,6 +1031,11 @@ EventTypes: check: succeeded: Проверката на OIDC Client Secret е успешна failed: Проверката на OIDC Client Secret е неуспешна + api: + secret: + check: + succeeded: Тайната проверка на API е успешна + failed: Тайната проверка на API е неуспешна key: added: Добавен ключ за приложение removed: Ключът на приложението е премахнат @@ -1032,11 +1048,13 @@ EventTypes: changed: Конфигурацията на OIDC е променена secret: changed: Тайната на OIDC е променена + updated: Тайният хеш на OIDC е актуализиран api: added: Добавена е конфигурация на API changed: Променена конфигурация на API secret: changed: Тайната на API е променена + updated: Тайният хеш на API е актуализиран policy: password: complexity: @@ -1135,6 +1153,9 @@ EventTypes: config: added: Добавена е SMTP конфигурация changed: SMTP конфигурацията е променена + activated: SMTP конфигурацията е активирана + deactivated: SMTP конфигурацията е деактивирана + removed: Премахната SMTP конфигурация password: changed: Тайната на конфигурацията на SMTP е променена sms: @@ -1289,6 +1310,8 @@ EventTypes: config: added: Добавена е SMTP конфигурация changed: SMTP конфигурацията е променена + activated: SMTP конфигурацията е активирана + deactivated: SMTP конфигурацията е деактивирана password: changed: Паролата на SMTP конфигурацията е променена removed: Премахната SMTP конфигурация diff --git a/internal/static/i18n/cs.yaml b/internal/static/i18n/cs.yaml index c29ab68d3b..c9c7951b64 100644 --- a/internal/static/i18n/cs.yaml +++ b/internal/static/i18n/cs.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: Konfigurace SMTP nebyla nalezena AlreadyExists: Konfigurace SMTP již existuje + AlreadyDeactivated: Konfigurace SMTP je již deaktivována SenderAdressNotCustomDomain: Adresa odesílatele musí být nakonfigurována jako vlastní doména na instanci. Notification: NoDomain: Pro zprávu nebyla nalezena žádná doména @@ -580,6 +581,15 @@ AggregateTypes: target: Cíl execution: Provedení user_schema: Uživatelské schéma + auth_request: Požadavek na autentizaci + device_auth: Ověření zařízení + idpintent: Záměr IDP + limits: Limit + milestone: Milník + oidc_session: OIDC sezení + restrictions: Omezení + system: Systém + session: Sezení EventTypes: execution: @@ -625,6 +635,7 @@ EventTypes: removed: Klíč odstraněn secret: set: Tajemství nastaveno + updated: Tajný hash aktualizován removed: Tajemství odstraněno check: succeeded: Kontrola tajemství byla úspěšná @@ -999,6 +1010,11 @@ EventTypes: check: succeeded: Kontrola tajného klíče OIDC klienta byla úspěšná failed: Kontrola tajného klíče OIDC klienta selhala + api: + secret: + check: + succeeded: Tajná kontrola API byla úspěšná + failed: Tajná kontrola API se nezdařila key: added: Klíč aplikace přidán removed: Klíč aplikace odstraněn @@ -1011,11 +1027,13 @@ EventTypes: changed: Konfigurace OIDC změněna secret: changed: Tajný klíč OIDC změněn + updated: Tajný hash OIDC byl aktualizován api: added: Konfigurace API přidána changed: Konfigurace API změněna secret: changed: Tajný klíč API změněn + updated: Tajný hash API byl aktualizován policy: password: complexity: @@ -1110,6 +1128,9 @@ EventTypes: config: added: Konfigurace SMTP přidána changed: Konfigurace SMTP změněna + activated: Konfigurace SMTP aktivována + deactivated: Konfigurace SMTP deaktivována + removed: Konfigurace SMTP odstraněna password: changed: Tajemství konfigurace SMTP změněno sms: @@ -1255,6 +1276,8 @@ EventTypes: config: added: Konfigurace SMTP přidána changed: Konfigurace SMTP změněna + activated: Konfigurace SMTP aktivována + deactivated: Konfigurace SMTP deaktivována password: changed: Heslo konfigurace SMTP změněno removed: Konfigurace SMTP odstraněna diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index fb4208e1a7..e9e9f0c190 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: SMTP Konfiguration nicht gefunden AlreadyExists: SMTP Konfiguration existiert bereits + AlreadyDeactivated: SMTP-Konfiguration bereits deaktiviert SenderAdressNotCustomDomain: Die Sender Adresse muss als Custom Domain auf der Instanz registriert sein. Notification: NoDomain: Keine Domäne für Nachricht gefunden @@ -582,6 +583,15 @@ AggregateTypes: target: Ziel execution: Ausführung user_schema: Benutzerschema + auth_request: Authentifizierungsanfrage + device_auth: Geräteauthentifizierung + idpintent: IDP Intent + limits: Limits + milestone: Milestone + oidc_session: OIDC Sitzung + restrictions: Restriktionen + system: System + session: Session EventTypes: execution: @@ -627,6 +637,7 @@ EventTypes: removed: Key entfernt secret: set: Secret gesetzt + updated: Geheimer Hash aktualisiert removed: Secret entfernt check: succeeded: Secret Überprüfung erfolgreich @@ -1001,6 +1012,11 @@ EventTypes: check: succeeded: OIDC Client Secret Validierung erfolgreich failed: OIDC Client Secret Validierung fehlgeschlagen + api: + secret: + check: + succeeded: Überprüfung des API-Geheimnisses erfolgreich + failed: Die Überprüfung des API-Geheimnisses ist fehlgeschlagen key: added: Applikations Schlüssel hinzugefügt removed: Applikations Schlüssel entfernt @@ -1013,11 +1029,13 @@ EventTypes: changed: OIDC Konfiguration geändert secret: changed: OIDC Client Secret geändert + updated: OIDC-Geheim-Hash aktualisiert api: added: API Konfiguration hinzugefügt changed: API Konfiguration geändert secret: changed: API Client Secret geändert + updated: API-Geheimnis-Hash aktualisiert policy: password: complexity: @@ -1112,6 +1130,9 @@ EventTypes: config: added: SMTP Konfiguration hinzugefügt changed: SMTP Konfiguration geändert + activated: SMTP Konfiguration aktiviert + deactivated: SMTP Konfiguration deaktiviert + removed: SMTP Konfiguration entfernt password: changed: SMTP Konfigurations Passwort geändert sms: @@ -1257,6 +1278,8 @@ EventTypes: config: added: SMTP Konfiguration hinzugefügt changed: SMTP Konfiguration geändert + activated: SMTP Konfiguration aktiviert + deactivated: SMTP Konfiguration deaktiviert password: changed: Passwort von SMTP Konfiguration geändert removed: SMTP Konfiguration gelöscht diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index e6e1688ab7..fabefe18e0 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: SMTP configuration not found AlreadyExists: SMTP configuration already exists + AlreadyDeactivated: SMTP configuration already deactivated SenderAdressNotCustomDomain: The sender address must be configured as custom domain on the instance. Notification: NoDomain: No Domain found for message @@ -582,6 +583,15 @@ AggregateTypes: target: Target execution: Execution user_schema: User Schema + auth_request: Auth Request + device_auth: Device Auth + idpintent: IDP Intent + limits: Limits + milestone: Milestone + oidc_session: OIDC Session + restrictions: Restrictions + system: System + session: Session EventTypes: execution: @@ -627,6 +637,7 @@ EventTypes: removed: Key removed secret: set: Secret set + updated: Secret hash updated removed: Secret removed check: succeeded: Secret check succeeded @@ -778,7 +789,7 @@ EventTypes: code: added: Phone number code generated sent: Phone number code sent - removed: Phone number removed + profile: changed: User profile changed address: @@ -1001,6 +1012,11 @@ EventTypes: check: succeeded: OIDC Client Secret check succeeded failed: OIDC Client Secret check failed + api: + secret: + check: + succeeded: API secret check succeeded + failed: API secret check failed key: added: Application key added removed: Application key removed @@ -1013,11 +1029,13 @@ EventTypes: changed: OIDC Configuration changed secret: changed: OIDC secret changed + updated: OIDC secret hash updated api: added: API Configuration added changed: API Configuration changed secret: changed: API secret changed + updated: API secret hash updated policy: password: complexity: @@ -1112,6 +1130,9 @@ EventTypes: config: added: SMTP configuration added changed: SMTP configuration changed + activated: SMTP configuration activated + deactivated: SMTP configuration deactivated + removed: SMTP configuration removed password: changed: SMTP configuration secret changed sms: @@ -1257,6 +1278,8 @@ EventTypes: config: added: SMTP configuration added changed: SMTP configuration changed + activated: SMTP configuration activated + deactivated: SMTP configuration deactivated password: changed: Password of SMTP configuration changed removed: SMTP configuration removed diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index 8c4b8df73f..d5f81ad733 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: configuración SMTP no encontrada AlreadyExists: la configuración SMTP ya existe + AlreadyDeactivated: la configuración SMTP ya está desactivada SenderAdressNotCustomDomain: La dirección del remitente debe configurarse como un dominio personalizado en la instancia. Notification: NoDomain: No se encontró el dominio para el mensaje @@ -582,6 +583,15 @@ AggregateTypes: target: Objectivo execution: Ejecución user_schema: Esquema de usuario + auth_request: Solicitud de autenticación + device_auth: Autenticación de dispositivo + idpintent: Intento de IDP + limits: Límites + milestone: Hito + oidc_session: Sesión OIDC + restrictions: Restricciones + system: Sistema + session: Sesión EventTypes: execution: @@ -627,6 +637,7 @@ EventTypes: removed: Clave eliminada secret: set: Secreto establecido + updated: Hash secreto actualizado removed: Secreto eliminado check: succeeded: Comprobación exitosa del secreto @@ -1001,6 +1012,11 @@ EventTypes: check: succeeded: Comprobación con éxito del secreto del cliente OIDC failed: Comprobación fallida del secreto del cliente OIDC + api: + secret: + check: + succeeded: La verificación del secreto de API fue exitosa + failed: Error en la comprobación del secreto de API key: added: Clave de aplicación añadida removed: Clave de aplicación eliminada @@ -1013,11 +1029,13 @@ EventTypes: changed: Configuracion OIDC modificada secret: changed: Secreto OIDC modificado + updated: Hash secreto OIDC actualizado api: added: Configuración API añadida changed: Configuración API modificada secret: changed: Configuración de secreto API modificada + updated: Hash secreto de API actualizado policy: password: complexity: @@ -1112,6 +1130,9 @@ EventTypes: config: added: Configuración SMTP añadida changed: Configuración SMTP modificada + activated: Configuración SMTP activada + deactivated: Configuración SMTP desactivada + removed: Configuración SMTP eliminada password: changed: Configuración de secreto SMTP modificada sms: @@ -1257,6 +1278,8 @@ EventTypes: config: added: Configuración SMTP añadida changed: Configuración SMTP modificada + activated: Configuración SMTP activada + deactivated: Configuración SMTP desactivada password: changed: Contraseña de configuración SMTP modificada removed: Configuración SMTP eliminada diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index 03eea1b948..c3a0120b5f 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: Configuration SMTP non trouvée AlreadyExists: La configuration SMTP existe déjà + AlreadyDeactivated: Configuration SMTP déjà désactivée SenderAdressNotCustomDomain: L'adresse de l'expéditeur doit être configurée comme un domaine personnalisé sur l'instance. Notification: NoDomain: Aucun domaine trouvé pour le message @@ -582,6 +583,15 @@ AggregateTypes: target: Cible execution: Exécution user_schema: Schéma utilisateur + auth_request: Auth Request + device_auth: Authentification de l'appareil + idpintent: IDP Intent + limits: Limites + milestone: Étape + oidc_session: Session OIDC + restrictions: Restrictions + system: Système + session: Session EventTypes: execution: @@ -625,6 +635,7 @@ EventTypes: removed: Clé supprimée secret: set: Secret défini + updated: Hachage secret mis à jour removed: Secret supprimée check: succeeded: La vérification de Secret réussie @@ -807,6 +818,12 @@ EventTypes: set: Ensemble de métadonnées de l'utilisateur removed: Métadonnées de l'utilisateur supprimées removed.all: Suppression de toutes les métadonnées utilisateur + domain: + claimed: Domaine revendiqué + claimed.sent: Notification de domaine revendiqué envoyée + pat: + added: Personal Access Token added + removed: Personal Access Token removed org: added: Organisation ajoutée changed: Organisation modifiée @@ -853,6 +870,10 @@ EventTypes: config: added: Configuration IDP SAML ajoutée changed: Modification de la configuration IDP SAML + jwt: + config: + added: Configuration JWT IDP ajoutée + changed: La configuration du fournisseur d'identité JWT a été modifiée customtext: set: Jeu de texte personnalisé removed: Texte personnalisé supprimé @@ -866,6 +887,8 @@ EventTypes: idpprovider: added: Fournisseur d'Idp ajouté à la politique de connexion removed: Idp Provider supprimé de la politique de connexion + cascade: + removed: Cascade de fournisseurs d'identité supprimée de la stratégie de connexion secondfactor: added: Second factor ajouté à la politique de connexion removed: Second facteur supprimé de la politique de connexion @@ -911,6 +934,14 @@ EventTypes: added: Politique de confidentialité et CGU ajoutés changed: Politique de confidentialité et CGU modifiées removed: Politique de confidentialité et conditions d'utilisation supprimées + domain: + added: Politique de domaine ajoutée + changed: Politique de domaine modifiée + removed: Politique de domaine supprimée + lockout: + added: Politique de verrouillage ajoutée + changed: La politique de verrouillage a été modifiée + removed: Politique de verrouillage supprimée notification: added: Politique de notification ajoutée changed: Politique de notification modifiée @@ -921,6 +952,20 @@ EventTypes: cascade: removed: Cascade d'actions supprimée removed: Actions supprimées + cleared: Flux effacé + mail: + template: + added: Modèle de courrier électronique ajouté + changed: Modèle d'e-mail modifié + removed: Modèle d'e-mail supprimé + text: + added: Texte de l'e-mail ajouté + changed: Le texte de l'e-mail a été modifié + removed: Texte de l'e-mail supprimé + metadata: + removed: Metadata removed + removed.all: All metadata removed + set: Metadata set project: added: Projet ajouté changed: Projet modifié @@ -962,6 +1007,11 @@ EventTypes: verified: check: Vérification du secret du client OIDC réussie failed: La vérification du secret du client OIDC a échoué + api: + secret: + check: + succeeded: La vérification du secret de l'API a réussi + failed: La vérification du secret de l'API a échoué key: added: Clé d'application ajoutée removed: Clé d'application supprimée @@ -974,11 +1024,13 @@ EventTypes: changed: Modification de la configuration de l'OIDC secret: changed: Le secret de l'OIDC a été modifié + updated: Hachage secret OIDC mis à jour api: added: Configuration API ajoutée changed: La configuration de l'API a été modifiée secret: changed: Le secret de l'API a été modifié + updated: Hachage secret de l'API mis à jour policy: password: complexity: @@ -1020,6 +1072,10 @@ EventTypes: saml: config: added: Ajout de la configuration SAML IDP + changed: La configuration SAML IDP a été modifiée + jwt: + config: + added: Configuration JWT du fournisseur d'identité ajoutée changed: Modification de la configuration de SAML IDP customtext: set: Le texte a été mis en place @@ -1069,6 +1125,9 @@ EventTypes: config: added: Ajout de la configuration SMTP changed: Modification de la configuration SMTP + activated: Configuration SMTP activée + deactivated: Configuration SMTP désactivée + removed: Configuration SMTP supprimée password: changed: Modification du secret de la configuration SMTP sms: @@ -1083,6 +1142,8 @@ EventTypes: deactivated: Fournisseur de SMS Twilio désactivé key_pair: added: Paire de clés ajoutée + certificate: + added: Certificat ajouté action: added: Action ajoutée changed: Action modifiée @@ -1095,7 +1156,134 @@ EventTypes: deactivated: Schéma utilisateur désactivé reactivated: Schéma utilisateur réactivé deleted: Schéma utilisateur supprimé +instance: + added: Instance ajoutée + changed: Instance modifiée + customtext: + removed: Texte personnalisé supprimé + set: Ensemble de texte personnalisé + template: + removed: Modèle de texte personnalisé supprimé + default: + language: + set: Langue par défaut définie + org: + set: Ensemble d'organisation par défaut + domain: + added: Domaine ajouté + primary: + set: Ensemble de domaines principal + removed: Domaine supprimé + iam: + console: + set: Ensemble d'applications Console ZITADEL + project: + set: ZITADEL project set + mail: + template: + added: Modèle de courrier électronique ajouté + changed: Modèle d'e-mail modifié + text: + added: Texte de l'e-mail ajouté + changed: Le texte de l'e-mail a été modifié + member: + added: Membre de l'instance ajouté + changed: Membre de l'instance modifié + removed: Membre de l'instance supprimé + cascade: + removed: Cascade de membres de l'instance supprimée + notification: + provider: + debug: + fileadded: Fournisseur de notification de débogage de fichiers ajouté + filechanged: Le fournisseur de notification de débogage de fichier a été modifié + fileremoved: Fournisseur de notification de débogage de fichier supprimé + logadded: Fournisseur de notification de débogage de journal ajouté + logchanged: Le fournisseur de notification de débogage du journal a été modifié + logremoved: Fournisseur de notification de débogage du journal supprimé + oidc: + settings: + added: Paramètres OIDC ajoutés + changed: Paramètres OIDC modifiés + policy: + domain: + added: Politique de domaine ajoutée + changed: Politique de domaine modifiée + label: + activated: Politique d'étiquetage activée + added: Politique d'étiquetage ajoutée + assets: + removed: L'élément de la stratégie d'étiquette a été supprimé + changed: Politique d'étiquetage modifiée + font: + added: Police ajoutée à la stratégie d'étiquette + removed: Police supprimée de la stratégie relative aux étiquettes + icon: + added: Icône ajoutée à la politique d'étiquetage + removed: Icône supprimée des règles relatives aux étiquettes + dark: + added: Icône ajoutée à la politique d'étiquette sombre + removed: Icône supprimée de la politique relative aux étiquettes sombres + logo: + added: Logo ajouté à la politique d'étiquetage + removed: Logo supprimé de la politique relative aux étiquettes + dark: + added: Logo ajouté à la politique relative aux étiquettes sombres + removed: Logo supprimé de la politique relative aux étiquettes sombres + lockout: + added: Politique de verrouillage ajoutée + changed: La politique de verrouillage a été modifiée + login: + added: Politique de connexion ajoutée + changed: Politique de connexion modifiée + idpprovider: + added: Fournisseur d'identité ajouté à la politique de connexion + cascade: + removed: Cascade de fournisseurs d'identité supprimée de la stratégie de connexion + removed: Fournisseur d'identité supprimé de la stratégie de connexion + multifactor: + added: Multifactor ajouté à la politique de connexion + removed: Multifactor supprimé de la politique de connexion + secondfactor: + added: Deuxième facteur ajouté à la politique de connexion + removed: Deuxième facteur supprimé de la politique de connexion + password: + age: + added: Politique d'âge du mot de passe ajoutée + changed: La politique relative à l'âge du mot de passe a été modifiée + complexity: + added: Politique de complexité des mots de passe ajoutée + changed: Politique de complexité des mots de passe supprimée + privacy: + added: Politique de confidentialité ajoutée + changed: Politique de confidentialité modifiée + security: + set: Ensemble de règles de sécurité + removed: Instance removed + secret: + generator: + added: Générateur de secrets ajouté + changed: Le générateur de secrets a changé + removed: Générateur de secrets supprimé + sms: + configtwilio: + activated: Configuration SMS Twilio activée + added: Configuration SMS Twilio ajoutée + changed: La configuration des SMS Twilio a été modifiée + deactivated: Configuration SMS Twilio désactivée + removed: Configuration SMS Twilio supprimée + token: + changed: Jeton de configuration SMS Twilio modifié + smtp: + config: + added: Configuration SMTP ajoutée + changed: Configuration SMTP modifiée + activated: Configuration SMTP activée + deactivated: Configuration SMTP désactivée + password: + changed: Mot de passe de configuration SMTP modifié + removed: Configuration SMTP supprimée Application: OIDC: UnsupportedVersion: Votre version de l'OIDC n'est pas prise en charge diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index eb8f8fc111..1c17b4be1c 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: Configurazione SMTP non trovata AlreadyExists: La configurazione SMTP esiste già + AlreadyDeactivated: Configurazione SMTP già disattivata SenderAdressNotCustomDomain: L'indirizzo del mittente deve essere configurato come dominio personalizzato sull'istanza. Notification: NoDomain: Nessun dominio trovato per il messaggio @@ -218,7 +219,6 @@ Errors: EmptyString: I caratteri non numerici e alfabetici non validi sono stati sostituiti con spazi vuoti e il dominio risultante è una stringa vuota IDP: InvalidSearchQuery: Parametro di ricerca non valido - InvalidCharacter: Per un dominio sono ammessi solo caratteri alfanumerici, . e - ClientIDMissing: ClientID mancante TeamIDMissing: TeamID mancante KeyIDMissing: ID chiave mancante @@ -583,6 +583,15 @@ AggregateTypes: target: Bersaglio execution: Esecuzione user_schema: Schema utente + auth_request: Richiesta di autenticazione + device_auth: Autenticazione su dispositivo + idpintent: IDP Intent + limits: Limiti + milestone: Milestone + oidc_session: OIDC Session + restrictions: Restrizioni + system: Sistema + session: Sessione EventTypes: execution: @@ -608,6 +617,7 @@ EventTypes: username: reserved: Nome utente riservato released: Nome utente rilasciato + changed: Nome utente cambiato email: reserved: Indirizzo e-mail riservato released: Indirizzo e-mail rilasciato @@ -626,6 +636,7 @@ EventTypes: removed: Chiave rimossa secret: set: Secret set + updated: Hash segreto aggiornato removed: Secret rimosso check: succeeded: Controllo della Secret riuscito @@ -777,6 +788,7 @@ EventTypes: code: added: Codice del numero di telefono generato sent: Codice del numero di telefono inviato + removed: Codice del numero di telefono rimosso profile: changed: Profilo utente cambiato address: @@ -790,7 +802,9 @@ EventTypes: succeeded: Controllo OTP riuscito failed: Controllo OTP fallito init: - skipped: Inizializzazione saltata + skipped: Inizializzazione OTP saltata + init: + skipped: Inizializzazione saltata signed: out: L'utente è uscito grant: @@ -808,6 +822,12 @@ EventTypes: set: Set di metadati utente removed: Metadati utente rimossi removed.all: Tutti i metadati utente rimossi + domain: + claimed: Dominio rivendicato + claimed.sent: Notifica di rivendicazione del dominio inviata + pat: + added: Aggiunto token di accesso personale + removed: Token di accesso personale rimosso org: added: Organizzazione aggiunta changed: Organizzazione cambiata @@ -854,6 +874,10 @@ EventTypes: config: added: Aggiunta la configurazione IDP SAML changed: Configurazione IDP SAML modificata + jwt: + config: + added: Aggiunta la configurazione IDP JWT + changed: La configurazione dell'IDP JWT è stata modificata customtext: set: Testo personalizzato salvato removed: Testo personalizzato rimosso @@ -867,6 +891,8 @@ EventTypes: idpprovider: added: IDP aggiunto alle impostazioni di accesso removed: IDP rimosso dalle impostazioni di accesso + cascade: + removed: Cascata di provider di identità rimossa dalla policy di accesso secondfactor: added: Secondo fattore aggiunto alle impostazioni di accesso removed: Secondo fattore rimosso dalle impostazioni di accesso @@ -912,6 +938,14 @@ EventTypes: added: Informativa sulla privacy e termini e condizioni aggiunti changed: Informativa sulla privacy e termini e condizioni cambiati removed: Informativa sulla privacy e termini e condizioni rimossi + domain: + added: Aggiunta politica di dominio + changed: La politica del dominio è cambiata + removed: Politica del dominio rimossa + lockout: + added: Lockout policy added + changed: Lockout policy changed + removed: Lockout policy removed notification: added: Impostazione di notifica creata changed: Impostazione di notifica cambiata @@ -922,6 +956,20 @@ EventTypes: cascade: removed: Azioni a cascata rimosse removed: Azioni rimosse + cleared: Il flusso è stato eliminato + mail: + template: + added: Aggiunto modello di posta elettronica + changed: Il modello di posta elettronica è stato modificato + removed: Modello di posta elettronica rimosso + text: + added: Aggiunto il testo dell'e-mail + changed: Il testo dell'e-mail è stato modificato + removed: Testo dell'e-mail rimosso + metadata: + removed: Metadati rimossi + removed.all: Tutti i metadati rimossi + set: Insieme di metadati project: added: Progetto aggiunto changed: Progetto cambiato @@ -966,6 +1014,11 @@ EventTypes: key: added: Chiave di applicazione aggiunta removed: Chiave di applicazione rimossa + api: + secret: + check: + succeeded: Il controllo del segreto API è riuscito + failed: Il controllo del segreto API non è riuscito config: saml: added: Configurazione SAML aggiunta @@ -975,11 +1028,13 @@ EventTypes: changed: Configurazione OIDC modificata secret: changed: Segreto OIDC cambiato + updated: Hash segreto OIDC aggiornato api: added: Configurazione API aggiunta changed: Configurazione API modificata secret: changed: Segreto API cambiato + updated: Hash segreto API aggiornato policy: password: complexity: @@ -1021,7 +1076,11 @@ EventTypes: saml: config: added: Aggiunta la configurazione IDP SAML - changed: Configurazione IDP SAML modificata + changed: La configurazione dell'IDP SAML è stata modificata + jwt: + config: + added: Aggiunta la configurazione JWT al provider di identità + changed: Configurazione JWT dal provider di identità rimossa customtext: set: Il testo è stato impostato removed: Il testo è stato rimosso @@ -1070,6 +1129,9 @@ EventTypes: config: added: SMTP configuration added changed: SMTP configuration changed + activated: Configurazione SMTP attivata + deactivated: Configurazione SMTP disattivata + removed: Configurazione SMTP rimossa password: changed: SMTP configuration secret changed sms: @@ -1084,6 +1146,8 @@ EventTypes: deactivated: Provider SMS Twilio disattivato key_pair: added: Keypair aggiunto + certificate: + added: Certificato aggiunto action: added: Azione aggiunta changed: Azione cambiata @@ -1096,6 +1160,134 @@ EventTypes: deactivated: Schema utente disattivato reactivated: Schema utente riattivato deleted: Schema utente eliminato + instance: + added: Istanza aggiunta + changed: L'istanza è cambiata + customtext: + removed: Testo personalizzato rimosso + set: Set di testo personalizzato + template: + removed: Modello di testo personalizzato rimosso + default: + language: + set: Lingua predefinita impostata + org: + set: Insieme di organizzazioni predefinito + domain: + added: Dominio aggiunto + primary: + set: Insieme di domini primari + removed: Dominio rimosso + iam: + console: + set: Set di applicazioni per console ZITADEL + project: + set: Set progetto ZITADEL + mail: + template: + added: Aggiunto modello di posta elettronica + changed: Il modello di posta elettronica è stato modificato + text: + added: Aggiunto il testo dell'e-mail + changed: Il testo dell'e-mail è stato modificato + member: + added: Membro dell'istanza aggiunto + changed: Il membro dell'istanza è cambiato + removed: Membro dell'istanza rimosso + cascade: + removed: Cascata di membri dell'istanza rimossa + notification: + provider: + debug: + fileadded: Aggiunto provider di notifiche di debug dei file + filechanged: Il provider delle notifiche di debug dei file è stato modificato + fileremoved: Provider di notifiche di debug del file rimosso + logadded: Aggiunto provider di notifiche di debug del registro + logchanged: Il provider delle notifiche di debug del registro è stato modificato + logremoved: Provider di notifiche di debug del registro rimosso + oidc: + settings: + added: Aggiunte impostazioni OIDC + changed: Le impostazioni OIDC sono state modificate + policy: + domain: + added: Aggiunta politica di dominio + changed: Domain policy changed + label: + activated: Criterio etichetta attivato + added: Aggiunta la politica sull'etichetta + assets: + removed: Risorsa dalla norma sull'etichetta rimossa + changed: La politica sull'etichetta è cambiata + font: + added: Carattere aggiunto ai criteri di etichetta + removed: Carattere rimosso dai criteri di etichetta + icon: + added: Icona aggiunta al criterio dell'etichetta + removed: Icona rimossa dal criterio di etichetta + dark: + added: Icona aggiunta al criterio dell'etichetta oscura + removed: Icona rimossa dal criterio dell'etichetta oscura + logo: + added: Logo aggiunto alla politica sull'etichetta + removed: Logo rimosso dalla politica sull'etichetta + dark: + added: Logo aggiunto alla politica delle etichette scure + removed: Logo rimosso dalla politica delle etichette scure + lockout: + added: Aggiunta politica di blocco + changed: La politica di blocco è cambiata + login: + added: Criteri di accesso aggiunti + changed: La politica di accesso è cambiata + idpprovider: + added: Provider di identità aggiunto alla policy di accesso + cascade: + removed: Cascata di provider di identità rimossa dalla policy di accesso + removed: Provider di identità rimosso dalla policy di accesso + multifactor: + added: Multifattore aggiunto alla policy di accesso + removed: Multifattore rimosso dalla policy di accesso + secondfactor: + added: Secondo fattore aggiunto alla politica di accesso + removed: Secondo fattore rimosso dalla politica di accesso + password: + age: + added: Aggiunta politica sull'età della password + changed: La politica di validità della password è cambiata + complexity: + added: Aggiunta policy sulla complessità della password + changed: Criterio di complessità della password rimosso + privacy: + added: Aggiunta informativa sulla privacy + changed: L'informativa sulla privacy è cambiata + security: + set: Insieme di politiche di sicurezza + + removed: Istanza rimossa + secret: + generator: + added: Aggiunto generatore segreto + changed: Il generatore segreto è cambiato + removed: Generatore segreto rimosso + sms: + configtwilio: + activated: Configurazione SMS Twilio attivata + added: Aggiunta la configurazione SMS di Twilio + changed: La configurazione SMS di Twilio è stata modificata + deactivated: Configurazione SMS Twilio disattivata + removed: Configurazione SMS di Twilio rimossa + token: + changed: La configurazione del token di Twilio SMS è stata modificata + smtp: + config: + added: Aggiunta configurazione SMTP + changed: La configurazione SMTP è stata modificata + activated: Configurazione SMTP attivata + deactivated: Configurazione SMTP disattivata + password: + changed: La password della configurazione SMTP è cambiata + removed: Configurazione SMTP rimossa Application: OIDC: diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 1a292fac8a..dfad33b393 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: SMTP構成が見つかりません AlreadyExists: すでに存在するSMTP構成です + AlreadyDeactivated: SMTP設定はすでに無効化されています SenderAdressNotCustomDomain: 送信者アドレスは、インスタンスのカスタムドメインとして構成する必要があります。 Notification: NoDomain: メッセージのドメインが見つかりません @@ -571,6 +572,15 @@ AggregateTypes: target: 目標 execution: 実行 user_schema: ユーザースキーマ + auth_request: 認証リクエスト + device_auth: デバイス認証 + idpintent: IDPインテント + limits: 制限 + milestone: マイルストーン + oidc_session: OIDCセッション + restrictions: 制限 + system: システム + session: セッション EventTypes: execution: @@ -616,6 +626,7 @@ EventTypes: removed: キーの削除 secret: set: シークレットのセット + updated: 秘密のハッシュが更新されました removed: シークレットの削除 check: succeeded: シークレットチェックの成功 @@ -990,6 +1001,11 @@ EventTypes: check: succeeded: OIDCクライアントシークレットチェックの成功 failed: OIDCクライアントシークレットチェックの失敗 + api: + secret: + check: + succeeded: APIシークレットチェックに成功しました + failed: APIシークレットチェックに失敗しました key: added: アプリケーションキーの追加 removed: アプリケーションキーの削除 @@ -1002,11 +1018,13 @@ EventTypes: changed: OIDC構成の変更 secret: changed: OIDCシークレットの変更 + updated: OIDC シークレットハッシュが更新されました api: added: API構成の追加 changed: API構成の変更 secret: changed: APIのシークレットの変更 + updated: API シークレット ハッシュが更新されました policy: password: complexity: @@ -1101,6 +1119,9 @@ EventTypes: config: added: SMTP構成の追加 changed: SMTP構成の変更 + activated: SMTP設定が有効化されました + deactivated: SMTP設定が無効化されました + removed: SMTP設定が削除されました password: changed: SMTP構成シークレットの変更 sms: @@ -1246,6 +1267,8 @@ EventTypes: config: added: SMTP構成の追加 changed: SMTP構成の変更 + activated: SMTP設定が有効化されました + deactivated: SMTP設定が無効化されました password: changed: SMTP構成パスワードの変更 removed: SMTP構成の削除 diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index bc8fe56b3a..04de6bb3f4 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: SMTP конфигурацијата не е пронајдена AlreadyExists: SMTP конфигурацијата веќе постои + AlreadyDeactivated: SMTP конфигурацијата е веќе деактивирана SenderAdressNotCustomDomain: Адресата на испраќачот мора да биде конфигурирана како прилагоден домен на инстанцата. Notification: NoDomain: Не е пронајден домен за пораката @@ -581,6 +582,15 @@ AggregateTypes: target: Цел execution: Извршување user_schema: Корисничка шема + auth_request: Барање за автентикација + device_auth: Уред за автентикација + idpintent: Намера на IDP + limits: Ограничувања + milestone: Милениум + oidc_session: OIDC сесија + restrictions: Ограничувања + system: Систем + session: Сесија EventTypes: execution: @@ -626,6 +636,7 @@ EventTypes: removed: Клучот е отстранет secret: set: Тајната е додадена + updated: Тајниот хаш е ажуриран removed: Тајната е отстрането check: succeeded: Проверката на тајната е успешна @@ -1000,6 +1011,11 @@ EventTypes: check: succeeded: Проверката на OIDC клиентска тајна е успешна failed: Проверката на OIDC клиентска тајна е неуспешна + api: + secret: + check: + succeeded: Проверката на тајната на API беше успешна + failed: Проверката на тајната на API не успеа key: added: Додаден клуч за апликацијата removed: Отстранет клуч за апликацијата @@ -1012,11 +1028,13 @@ EventTypes: changed: Променета OIDC конфигурација secret: changed: Променета OIDC тајна + updated: Тајниот хаш на OIDC е ажуриран api: added: Додадена API конфигурација changed: Променета API конфигурација secret: changed: Променета API тајна + updated: Тајниот хаш на API е ажуриран policy: password: complexity: @@ -1111,6 +1129,9 @@ EventTypes: config: added: Додадена SMTP конфигурација changed: Променета SMTP конфигурација + activated: SMTP конфигурацијата е активирана + deactivated: SMTP конфигурацијата е деактивирана + removed: SMTP конфигурацијата е отстранета password: changed: Променена тајна на SMTP конфигурација sms: @@ -1255,6 +1276,8 @@ EventTypes: config: added: Додадена SMTP конфигурација changed: Променета SMTP конфигурација + activated: SMTP конфигурацијата е активирана + deactivated: SMTP конфигурацијата е деактивирана password: changed: Променета лозинка на SMTP конфигурацијата removed: Отстранета SMTP конфигурација diff --git a/internal/static/i18n/nl.yaml b/internal/static/i18n/nl.yaml index 5dac650788..39b624fe48 100644 --- a/internal/static/i18n/nl.yaml +++ b/internal/static/i18n/nl.yaml @@ -582,6 +582,15 @@ AggregateTypes: target: Doel execution: Executie user_schema: Gebruikersschema + auth_request: Auth Verzoek + device_auth: Apparaatverificatie + idpintent: IDP Intentie + limits: Limieten + milestone: Mijlpaal + oidc_session: OIDC Sessie + restrictions: Beperkingen + system: Systeem + session: Sessie EventTypes: execution: @@ -627,6 +636,7 @@ EventTypes: removed: Sleutel verwijderd secret: set: Geheim ingesteld + updated: Geheime hash bijgewerkt removed: Geheim verwijderd check: succeeded: Geheimcontrole geslaagd @@ -1001,6 +1011,11 @@ EventTypes: check: succeeded: OIDC Client Secret controle geslaagd failed: OIDC Client Secret controle mislukt + api: + secret: + check: + succeeded: API-geheimcontrole geslaagd + failed: API-geheimcontrole mislukt key: added: Applicatiesleutel toegevoegd removed: Applicatiesleutel verwijderd @@ -1013,11 +1028,13 @@ EventTypes: changed: OIDC Configuratie gewijzigd secret: changed: OIDC geheim gewijzigd + updated: OIDC geheime hash bijgewerkt api: added: API Configuratie toegevoegd changed: API Configuratie gewijzigd secret: changed: API geheim gewijzigd + updated: API-geheime hash bijgewerkt policy: password: complexity: diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index 40ebc31e98..63f469ed48 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: Konfiguracja SMTP nie znaleziona AlreadyExists: Konfiguracja SMTP już istnieje + AlreadyDeactivated: Konfiguracja SMTP jest już dezaktywowana SenderAdressNotCustomDomain: Adres nadawcy musi być skonfigurowany jako domena niestandardowa na instancji. Notification: NoDomain: Nie znaleziono domeny dla wiadomości @@ -582,6 +583,15 @@ AggregateTypes: target: Cel execution: Wykonanie user_schema: Schemat użytkownika + auth_request: Auth Request + device_auth: Uwierzytelnianie urządzenia + idpintent: Intencja IDP + limits: Limit + milestone: Kamień milowy + oidc_session: Sesja OIDC + restrictions: Ograniczenia + system: System + session: Sesja EventTypes: execution: @@ -627,6 +637,7 @@ EventTypes: removed: Usunięto klucz secret: set: Sekret ustawiony + updated: Zaktualizowano tajny skrót removed: Sekret usunięty check: succeeded: Sprawdzenie sekretu powiodło się @@ -1001,6 +1012,11 @@ EventTypes: check: succeeded: Sprawdzenie sekretu OIDC Klienta powiodło się failed: Sprawdzenie sekretu OIDC Klienta nie powiodło się + api: + secret: + check: + succeeded: Sprawdzenie tajnego interfejsu API powiodło się + failed: Sprawdzanie tajnego interfejsu API nie powiodło się key: added: Dodano klucz aplikacji removed: Usunięto klucz aplikacji @@ -1013,11 +1029,13 @@ EventTypes: changed: Zmieniono konfigurację OIDC secret: changed: Zmieniono sekret OIDC + updated: Zaktualizowano tajny skrót OIDC api: added: Dodano konfigurację API changed: Zmieniono konfigurację API secret: changed: Zmieniono sekret API + updated: Zaktualizowano tajny skrót API policy: password: complexity: @@ -1112,6 +1130,9 @@ EventTypes: config: added: Dodano konfigurację SMTP changed: Zmieniono konfigurację SMTP + activated: Konfiguracja SMTP została aktywowana + deactivated: Konfiguracja SMTP dezaktywowana + removed: Konfiguracja SMTP została usunięta password: changed: Zmieniono sekret konfiguracji SMTP sms: @@ -1257,6 +1278,8 @@ EventTypes: config: added: Konfiguracja SMTP dodana changed: Konfiguracja SMTP zmieniona + activated: Konfiguracja SMTP została aktywowana + deactivated: Konfiguracja SMTP dezaktywowana password: changed: Hasło konfiguracji SMTP zmienione removed: Konfiguracja SMTP usunięta diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index 03345901e2..011014c0e1 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: Configuração de SMTP não encontrada AlreadyExists: Configuração de SMTP já existe + AlreadyDeactivated: Configuração SMTP já desativada SenderAdressNotCustomDomain: O endereço do remetente deve ser configurado como um domínio personalizado na instância. Notification: NoDomain: Nenhum domínio encontrado para a mensagem @@ -576,6 +577,15 @@ AggregateTypes: target: Objetivo execution: Execução user_schema: Esquema do usuário + auth_request: Solicitação de autenticação + device_auth: Autenticação de dispositivo + idpintent: Intenção de IDP + limits: Limites + milestone: Marco + oidc_session: Sessão OIDC + restrictions: Restrições + system: Sistema + session: Sessão EventTypes: execution: @@ -621,6 +631,7 @@ EventTypes: removed: Chave removida secret: set: Segredo definido + updated: Hash secreto atualizado removed: Segredo removido check: succeeded: Verificação de segredo bem-sucedida @@ -995,6 +1006,11 @@ EventTypes: check: succeeded: Verificação de segredo do cliente OIDC bem-sucedida failed: Verificação de segredo do cliente OIDC falhou + api: + secret: + check: + succeeded: Verificação secreta da API bem-sucedida + failed: Falha na verificação do segredo da API key: added: Chave do aplicativo adicionada removed: Chave do aplicativo removida @@ -1007,11 +1023,13 @@ EventTypes: changed: Configuração OIDC alterada secret: changed: Segredo OIDC alterado + updated: Hash secreto do OIDC atualizado api: added: Configuração de API adicionada changed: Configuração de API alterada secret: changed: Segredo da API alterado + updated: Hash secreto da API atualizado policy: password: complexity: @@ -1106,6 +1124,9 @@ EventTypes: config: added: Configuração SMTP adicionada changed: Configuração SMTP alterada + activated: Configuração SMTP ativada + deactivated: Configuração SMTP desativada + removed: Configuração SMTP removida password: changed: Segredo da configuração SMTP alterado sms: @@ -1251,6 +1272,8 @@ EventTypes: config: added: Configuração SMTP adicionada changed: Configuração SMTP alterada + activated: Configuração SMTP ativada + deactivated: Configuração SMTP desativada password: changed: Senha da configuração SMTP alterada removed: Configuração SMTP removida diff --git a/internal/static/i18n/ru.yaml b/internal/static/i18n/ru.yaml index ece3c660c0..8b2c0d5646 100644 --- a/internal/static/i18n/ru.yaml +++ b/internal/static/i18n/ru.yaml @@ -56,7 +56,8 @@ Errors: SMTPConfig: NotFound: Конфигурация SMTP не найдена AlreadyExists: Конфигурация SMTP уже существует - SenderAdressNotCustomDomain: Адрес отправителя должен быть настроен как личный домен в экземпляре + AlreadyDeactivated: Конфигурация SMTP уже деактивирована + SenderAdressNotCustomDomain: Адрес отправителя должен быть настроен как личный домен на экземпляре. Notification: NoDomain: Домен не найден User: @@ -120,7 +121,7 @@ Errors: WrongType: Запрещено для данного типа пользователя NotAllowedToLink: У пользователя нет разрешения на связь с внешним провайдером входа в систему Username: - AlreadyExists: Имя пользователя занято + AlreadyExists: Имя пользователя занято Reserved: Имя пользователя уже занято Code: Empty: Код не заполнен @@ -571,6 +572,15 @@ AggregateTypes: target: мишень execution: Исполнение user_schema: Пользовательская схема + auth_request: Запрос на аутентификацию + device_auth: Аутентификация устройства + idpintent: Намерение IDP + limits: Ограничения + milestone: Веха + oidc_session: Сеанс OIDC + restrictions: Ограничения + system: Система + session: Сеанс EventTypes: execution: @@ -616,6 +626,7 @@ EventTypes: removed: Ключ удалён secret: set: Ключ установлен + updated: Секретный хеш обновлен. removed: Ключ удалён check: succeeded: Проверка ключа прошла успешно @@ -714,7 +725,7 @@ EventTypes: login: Проверка мультифактора U2F начата check: succeeded: Проверка мультифактора U2F прошла успешно - failed: Проверка мультифактора U2F U2F не удалась + failed: Проверка мультифактора U2F U2F не удалась signcount: changed: Контрольная сумма токена мультифактора U2F изменена init: @@ -876,7 +887,7 @@ EventTypes: added: Второй фактор добавлен в политику входа в систему removed: Второй фактор удалён из политики входа в систему multifactor: - added: Мультифактор добавлен в политику входа в систему + added: Мультифактор добавлен в политику входа в систему removed: Мультифактор удалён из политики входа в систему password: complexity: @@ -990,6 +1001,11 @@ EventTypes: check: succeeded: Проверка клиентского ключа OIDC прошла успешно failed: Проверки клиентского ключа OIDC не удалась + api: + secret: + check: + succeeded: Проверка секретности API прошла успешно + failed: Проверка секретности API не удалась key: added: Ключ приложения добавлен removed: Ключ приложения удалён @@ -1002,11 +1018,13 @@ EventTypes: changed: Конфигурация OIDC изменена secret: changed: Ключ OIDC изменён + updated: Секретный хеш OIDC обновлен. api: added: Конфигурация API добавлена changed: Конфигурация API изменена secret: changed: Ключ API изменён + updated: Секретный хэш API обновлен. policy: password: complexity: @@ -1101,6 +1119,9 @@ EventTypes: config: added: Конфигурация SMTP добавлена changed: Конфигурация SMTP изменена + activated: Конфигурация SMTP активирована + deactivated: Конфигурация SMTP деактивирована + removed: Конфигурация SMTP удалена. password: changed: Ключ конфигурации SMTP изменён sms: @@ -1246,6 +1267,8 @@ EventTypes: config: added: Конфигурация SMTP добавлена changed: Конфигурация SMTP изменена + activated: Конфигурация SMTP активирована + deactivated: Конфигурация SMTP деактивирована password: changed: Пароль конфигурации SMTP изменён removed: Конфигурация SMTP удалена diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index 497c697641..153b06a1b1 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -56,6 +56,7 @@ Errors: SMTPConfig: NotFound: 未找到 SMTP 配置 AlreadyExists: SMTP 配置已存在 + AlreadyDeactivated: SMTP 配置已停用 SenderAdressNotCustomDomain: 发件人地址必须在在实例的域名设置中验证。 Notification: NoDomain: 未找到对应的域名 @@ -582,6 +583,15 @@ AggregateTypes: target: 靶 execution: 执行 user_schema: 用户模式 + auth_request: 认证请求 + device_auth: 设备认证 + idpintent: IDP 意图 + limits: 限制 + milestone: 里程碑 + oidc_session: OIDC 会话 + restrictions: 限制 + system: 系统 + session: 会话 EventTypes: execution: @@ -607,6 +617,7 @@ EventTypes: username: reserved: 保留用户名 released: 用户名已发布 + changed: 用户名已更改 email: reserved: 电子邮件地址已保留 released: 电子邮件地址已发布 @@ -625,6 +636,7 @@ EventTypes: removed: 删除服务用户 Key secret: set: 秘密套装 + updated: 秘密哈希已更新 removed: 秘密删除 check: succeeded: 成功的秘密控制 @@ -661,6 +673,10 @@ EventTypes: check: succeeded: 密码检查成功 failed: 密码检查失败 + change: + sent: 密码更改已发送 + hash: + updated: 密码哈希已更新 externallogin: check: succeeded: 外部登录成功 @@ -764,10 +780,6 @@ EventTypes: check: succeeded: 密码检查成功 failed: 密码检查失败 - change: - sent: 密码更改已发送 - hash: - updated: 密码哈希已更新 phone: changed: 更改手机号码 verified: 验证手机号码 @@ -776,6 +788,7 @@ EventTypes: code: added: 生成的手机号码验证码 sent: 发送手机号码验证码 + removed: 电话号码已删除 profile: changed: 修改用户资料 address: @@ -788,8 +801,10 @@ EventTypes: check: succeeded: MFA OTP 验证成功 failed: MFA OTP 验证失败 + init: + skipped: 跳过 MFA 初始化 init: - skipped: 跳过 MFA 初始化 + skipped: 跳过多因素初始化 signed: out: 用户退出登录 grant: @@ -807,6 +822,12 @@ EventTypes: set: 用户元数据集 removed: 删除用户元数据 removed.all: 删除所有用户元数据 + domain: + claimed: 已认领域名 + claimed.sent: 已发送域声明通知 + pat: + added: 添加个人访问令牌 + removed: 个人访问令牌已删除 org: added: 添加组织 changed: 更改组织 @@ -853,6 +874,10 @@ EventTypes: config: added: 添加 SAML IDP 配置 changed: 更改 SAML IDP 配置 + jwt: + config: + added: 添加了 JWT IDP 配置 + changed: JWT IDP 配置已更改 customtext: set: 设置自定义文本 removed: 删除自定义文本 @@ -866,6 +891,8 @@ EventTypes: idpprovider: added: 添加 IDP 到登录策略 removed: 从登录策略删除 IDP + cascade: + removed: 从登录策略中删除了身份提供者级联 secondfactor: added: 添加两步认证到登录策略 removed: 删除两步认证到登录策略 @@ -909,6 +936,14 @@ EventTypes: removed: 从标签策略中删除的资产 privacy: added: 添加隐私政策和服务条款 + changed: 隐私政策和服务条款已更改 + removed: 隐私政策和 TOS 已删除 + domain: + added: 添加了域策略 + changed: 域策略已更改 + removed: 域策略已删除 + lockout: + added: 添加了锁定策略 changed: 更改隐私政策和服务条款 removed: 删除隐私政策和服务条款 notification: @@ -921,6 +956,20 @@ EventTypes: cascade: removed: 删除动作级联 removed: 删除动作 + cleared: 流量已清除 + mail: + template: + added: 添加了电子邮件模板 + changed: 电子邮件模板已更改 + removed: 电子邮件模板已删除 + text: + added: 添加了电子邮件文本 + changed: 电子邮件文本已更改 + removed: 电子邮件文本已删除 + metadata: + removed: 电子邮件文本已删除 + removed.all: 所有元数据已删除 + set: 元数据集 project: added: 添加项目 changed: 更改项目 @@ -962,6 +1011,11 @@ EventTypes: check: succeeded: 检查 OIDC Client Secret 成功 failed: 检查 OIDC Client Secret 失败 + api: + secret: + check: + succeeded: API密检成功 + failed: API 秘密检查失败 key: added: 添加应用 Key removed: 删除应用 Key @@ -974,11 +1028,13 @@ EventTypes: changed: 更改 OIDC 配置 secret: changed: 更改 OIDC Secret + updated: OIDC 秘密哈希已更新 api: added: 添加 API 配置 changed: 更改 API 配置 secret: changed: 更改 API Secret + updated: API 秘密哈希已更新 policy: password: complexity: @@ -1021,6 +1077,10 @@ EventTypes: config: added: 添加 SAML IDP 配置 changed: 更改 SAML IDP 配置 + jwt: + config: + added: 添加了身份提供者的 JWT 配置 + changed: 身份提供商的 JWT 配置已删除 customtext: set: 设置文本 removed: 删除文本 @@ -1069,6 +1129,9 @@ EventTypes: config: added: 添加 SMTP 配置 changed: 更改 SMTP 配置 + activated: SMTP 配置已激活 + deactivated: SMTP 配置已停用 + removed: SMTP 配置已删除 password: changed: 更改 SMTP 安全设置 sms: @@ -1083,6 +1146,8 @@ EventTypes: deactivated: 停用 Twilio SMS 提供者 key_pair: added: 添加密钥对 + certificate: + added: 证书已添加 action: added: 添加动作 changed: 更改动作 @@ -1095,6 +1160,134 @@ EventTypes: deactivated: 用户架构已停用 reactivated: 用户架构已重新激活 deleted: 用户架构已删除 + instance: + added: 实例已添加 + changed: 实例已更改 + customtext: + removed: 自定义文本已删除 + set: 自定义文本集 + template: + removed: 删除了自定义文本模板 + default: + language: + set: 默认语言设置 + org: + set: 默认组织集 + domain: + added: 已添加域名 + primary: + set: 主域集 + removed: 域名已删除 + iam: + console: + set: ZITADEL 控制台应用程序集 + project: + set: ZITADEL 项目集 + mail: + template: + added: 添加了电子邮件模板 + changed: 电子邮件模板已更改 + text: + added: 添加了电子邮件文本 + changed: 电子邮件文本已更改 + member: + added: 已添加实例成员 + changed: 实例成员发生变化 + removed: 实例成员已删除 + cascade: + removed: 实例成员级联已删除 + notification: + provider: + debug: + fileadded: 添加了文件调试通知提供程序 + filechanged: 文件调试通知提供程序已更改 + fileremoved: 删除文件调试通知提供程序 + logadded: 添加了日志调试通知提供程序 + logchanged: 日志调试通知提供程序已更改 + logremoved: 日志调试通知提供程序已删除 + oidc: + settings: + added: 添加了 OIDC 设置 + changed: OIDC 设置已更改 + policy: + domain: + added: 添加了域策略 + changed: 域策略已更改 + label: + activated: 标签政策已激活 + added: 添加了标签策略 + assets: + removed: 已删除标签政策中的资产 + changed: 标签政策已更改 + font: + added: 添加到标签政策的字体 + removed: 从标签政策中删除的字体 + icon: + added: 图标已添加到标签策略 + removed: 图标已从标签政策中删除 + dark: + added: 图标已添加到暗标签政策 + removed: 图标已从暗标签政策中删除 + logo: + added: 徽标已添加到标签政策中 + removed: 徽标已从标签政策中删除 + dark: + added: 徽标已添加到暗标签政策中 + removed: 徽标从暗标签政策中删除 + lockout: + added: 添加了锁定策略 + changed: 锁定政策已更改 + login: + added: 添加了登录策略 + changed: 登录政策已更改 + idpprovider: + added: 身份提供商已添加到登录策略中 + cascade: + removed: 身份提供者级联从登录策略中删除 + removed: 身份提供商已从登录策略中删除 + multifactor: + added: 登录策略中添加了多因素 + removed: 从登录策略中删除了多因素 + secondfactor: + added: 添加到登录策略的第二个因素 + removed: 从登录策略中删除了第二个因素 + password: + age: + added: 添加了密码年龄策略 + changed: 密码期限政策已更改 + complexity: + added: 添加了密码复杂性策略 + changed: 删除了密码复杂性策略 + privacy: + added: 添加了隐私政策 + changed: 隐私政策已更改 + security: + set: 安全策略集 + + removed: 实例已删除 + secret: + generator: + added: 添加了秘密生成器 + changed: 秘密生成器已更改 + removed: 秘密生成器已移除 + sms: + configtwilio: + activated: Twilio SMS 配置已激活 + added: 添加了 Twilio SMS 配置 + changed: Twilio SMS 配置已更改 + deactivated: Twilio SMS 配置已停用 + removed: Twilio SMS 配置已删除 + token: + changed: Twilio SMS 配置的令牌已更改 + smtp: + config: + added: 添加了 SMTP 配置 + changed: SMTP 配置已更改 + activated: SMTP 配置已激活 + deactivated: SMTP 配置已停用 + password: + changed: SMTP 配置密码已更改 + removed: SMTP 配置已删除 Application: OIDC: diff --git a/internal/static/mock/storage_mock.go b/internal/static/mock/storage_mock.go index f61a8dfebb..3ef02efa96 100644 --- a/internal/static/mock/storage_mock.go +++ b/internal/static/mock/storage_mock.go @@ -5,6 +5,7 @@ // // mockgen -source storage.go -destination ./mock/storage_mock.go -package mock // + // Package mock is a generated GoMock package. package mock diff --git a/internal/telemetry/http_handler.go b/internal/telemetry/http_handler.go index adb8bb0767..32e614b34c 100644 --- a/internal/telemetry/http_handler.go +++ b/internal/telemetry/http_handler.go @@ -4,9 +4,9 @@ import ( "net/http" "strings" - "github.com/zitadel/zitadel/internal/telemetry/metrics" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + + "github.com/zitadel/zitadel/internal/telemetry/metrics" ) func shouldNotIgnore(endpoints ...string) func(r *http.Request) bool { diff --git a/internal/test/filled_checker.go b/internal/test/filled_checker.go index f889f3635b..fb5902bb65 100644 --- a/internal/test/filled_checker.go +++ b/internal/test/filled_checker.go @@ -6,7 +6,7 @@ import ( "strings" ) -//testingT is a wrapper for testing.T +// testingT is a wrapper for testing.T // // this wrapper is needed for internal testing type testingT interface { diff --git a/internal/user/model/external_idp_view.go b/internal/user/model/external_idp_view.go index 6dc8f53dc7..4d2abed6fc 100644 --- a/internal/user/model/external_idp_view.go +++ b/internal/user/model/external_idp_view.go @@ -1,10 +1,10 @@ package model import ( + "time" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" - - "time" ) type ExternalIDPView struct { diff --git a/internal/user/model/refresh_token_view.go b/internal/user/model/refresh_token_view.go index 5f5f5c8997..4050fce8b4 100644 --- a/internal/user/model/refresh_token_view.go +++ b/internal/user/model/refresh_token_view.go @@ -1,10 +1,10 @@ package model import ( + "time" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" - - "time" ) type RefreshTokenView struct { diff --git a/internal/user/model/token_view.go b/internal/user/model/token_view.go index 9ee00c87e6..7e182e31bc 100644 --- a/internal/user/model/token_view.go +++ b/internal/user/model/token_view.go @@ -1,10 +1,10 @@ package model import ( + "time" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" - - "time" ) type TokenView struct { diff --git a/internal/user/model/user_membership_view.go b/internal/user/model/user_membership_view.go index 3c68ef2866..b6d0d18621 100644 --- a/internal/user/model/user_membership_view.go +++ b/internal/user/model/user_membership_view.go @@ -1,10 +1,10 @@ package model import ( + "time" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" - - "time" ) type UserMembershipView struct { diff --git a/internal/user/repository/eventsourcing/model/profile_test.go b/internal/user/repository/eventsourcing/model/profile_test.go index 9586594827..b9977e83b5 100644 --- a/internal/user/repository/eventsourcing/model/profile_test.go +++ b/internal/user/repository/eventsourcing/model/profile_test.go @@ -3,8 +3,9 @@ package model import ( "testing" - user_model "github.com/zitadel/zitadel/internal/user/model" "golang.org/x/text/language" + + user_model "github.com/zitadel/zitadel/internal/user/model" ) func TestProfileChanges(t *testing.T) { diff --git a/internal/user/repository/view/user_session_by_id.sql b/internal/user/repository/view/user_session_by_id.sql index 0f9d8c04e9..1a9f3c250e 100644 --- a/internal/user/repository/view/user_session_by_id.sql +++ b/internal/user/repository/view/user_session_by_id.sql @@ -19,8 +19,8 @@ SELECT s.creation_date, s.sequence, s.instance_id FROM auth.user_sessions s - LEFT JOIN projections.users11 u ON s.user_id = u.id AND s.instance_id = u.instance_id - LEFT JOIN projections.users11_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id + LEFT JOIN projections.users12 u ON s.user_id = u.id AND s.instance_id = u.instance_id + LEFT JOIN projections.users12_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id LEFT JOIN projections.login_names3 l ON s.user_id = l.user_id AND s.instance_id = l.instance_id AND l.is_primary = true WHERE (s.user_agent_id = $1) AND (s.user_id = $2) diff --git a/internal/user/repository/view/user_sessions_by_user_agent.sql b/internal/user/repository/view/user_sessions_by_user_agent.sql index 1adb11f44c..ac72f27e3c 100644 --- a/internal/user/repository/view/user_sessions_by_user_agent.sql +++ b/internal/user/repository/view/user_sessions_by_user_agent.sql @@ -19,8 +19,8 @@ SELECT s.creation_date, s.sequence, s.instance_id FROM auth.user_sessions s - LEFT JOIN projections.users11 u ON s.user_id = u.id AND s.instance_id = u.instance_id - LEFT JOIN projections.users11_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id + LEFT JOIN projections.users12 u ON s.user_id = u.id AND s.instance_id = u.instance_id + LEFT JOIN projections.users12_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id LEFT JOIN projections.login_names3 l ON s.user_id = l.user_id AND s.instance_id = l.instance_id AND l.is_primary = true WHERE (s.user_agent_id = $1) AND (s.instance_id = $2) diff --git a/internal/view/repository/db_mock_test.go b/internal/view/repository/db_mock_test.go index 10e0d002f4..b78d68d4da 100644 --- a/internal/view/repository/db_mock_test.go +++ b/internal/view/repository/db_mock_test.go @@ -8,6 +8,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/jinzhu/gorm" + db_mock "github.com/zitadel/zitadel/internal/database/mock" "github.com/zitadel/zitadel/internal/domain" ) diff --git a/internal/view/repository/query.go b/internal/view/repository/query.go index b3be1f0163..fc2b6d8ac3 100644 --- a/internal/view/repository/query.go +++ b/internal/view/repository/query.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/jinzhu/gorm" - "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/database" diff --git a/internal/view/repository/query_test.go b/internal/view/repository/query_test.go index 49cd961b1a..10ec8c9422 100644 --- a/internal/view/repository/query_test.go +++ b/internal/view/repository/query_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/jinzhu/gorm" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" ) diff --git a/internal/zerrors/zerror_test.go b/internal/zerrors/zerror_test.go index dcd0d8bcf0..3a11a8e78e 100644 --- a/internal/zerrors/zerror_test.go +++ b/internal/zerrors/zerror_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/zerrors" ) diff --git a/proto/zitadel/execution/v3alpha/execution_service.proto b/proto/zitadel/action/v3alpha/action_service.proto similarity index 95% rename from proto/zitadel/execution/v3alpha/execution_service.proto rename to proto/zitadel/action/v3alpha/action_service.proto index 7c044a817d..4c37276326 100644 --- a/proto/zitadel/execution/v3alpha/execution_service.proto +++ b/proto/zitadel/action/v3alpha/action_service.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package zitadel.execution.v3alpha; +package zitadel.action.v3alpha; import "google/api/annotations.proto"; import "google/api/field_behavior.proto"; @@ -8,17 +8,17 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; -import "zitadel/execution/v3alpha/target.proto"; -import "zitadel/execution/v3alpha/execution.proto"; -import "zitadel/execution/v3alpha/query.proto"; +import "zitadel/action/v3alpha/target.proto"; +import "zitadel/action/v3alpha/execution.proto"; +import "zitadel/action/v3alpha/query.proto"; import "zitadel/object/v2beta/object.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; -option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution"; +option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action"; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { info: { - title: "Execution Service"; + title: "Action Service"; version: "3.0-preview"; description: "This API is intended to manage custom executions (previously known as actions) in a ZITADEL instance. This project is in preview state. It can AND will continue breaking until the services provide the same functionality as the current actions."; contact:{ @@ -104,7 +104,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { } }; -service ExecutionService { +service ActionService { // Create a target // @@ -516,22 +516,22 @@ message ListTargetsRequest { // list limitations and ordering. zitadel.object.v2beta.ListQuery query = 1; // the field the result is sorted. - zitadel.execution.v3alpha.TargetFieldName sorting_column = 2 [ + zitadel.action.v3alpha.TargetFieldName sorting_column = 2 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"FIELD_NAME_SCHEMA_TYPE\"" } ]; // Define the criteria to query for. - repeated zitadel.execution.v3alpha.TargetSearchQuery queries = 3; + repeated zitadel.action.v3alpha.TargetSearchQuery queries = 3; } message ListTargetsResponse { // Details provides information about the returned result including total amount found. zitadel.object.v2beta.ListDetails details = 1; // States by which field the results are sorted. - zitadel.execution.v3alpha.TargetFieldName sorting_column = 2; + zitadel.action.v3alpha.TargetFieldName sorting_column = 2; // The result contains the user schemas, which matched the queries. - repeated zitadel.execution.v3alpha.Target result = 3; + repeated zitadel.action.v3alpha.Target result = 3; } message GetTargetByIDRequest { @@ -548,7 +548,7 @@ message GetTargetByIDRequest { } message GetTargetByIDResponse { - zitadel.execution.v3alpha.Target target = 1; + zitadel.action.v3alpha.Target target = 1; } message SetExecutionRequest { @@ -579,14 +579,14 @@ message ListExecutionsRequest { // list limitations and ordering. zitadel.object.v2beta.ListQuery query = 1; // Define the criteria to query for. - repeated zitadel.execution.v3alpha.SearchQuery queries = 2; + repeated zitadel.action.v3alpha.SearchQuery queries = 2; } message ListExecutionsResponse { // Details provides information about the returned result including total amount found. zitadel.object.v2beta.ListDetails details = 1; // The result contains the executions, which matched the queries. - repeated zitadel.execution.v3alpha.Execution result = 2; + repeated zitadel.action.v3alpha.Execution result = 2; } message ListExecutionFunctionsRequest{} diff --git a/proto/zitadel/execution/v3alpha/execution.proto b/proto/zitadel/action/v3alpha/execution.proto similarity index 97% rename from proto/zitadel/execution/v3alpha/execution.proto rename to proto/zitadel/action/v3alpha/execution.proto index 14d05446bd..c8b85f50a0 100644 --- a/proto/zitadel/execution/v3alpha/execution.proto +++ b/proto/zitadel/action/v3alpha/execution.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package zitadel.execution.v3alpha; +package zitadel.action.v3alpha; import "google/api/annotations.proto"; import "google/api/field_behavior.proto"; @@ -11,7 +11,7 @@ import "validate/validate.proto"; import "zitadel/object/v2beta/object.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; -option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution"; +option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action"; message Execution { string execution_id = 1 [ diff --git a/proto/zitadel/execution/v3alpha/query.proto b/proto/zitadel/action/v3alpha/query.proto similarity index 94% rename from proto/zitadel/execution/v3alpha/query.proto rename to proto/zitadel/action/v3alpha/query.proto index c13bd12cf8..26093305bc 100644 --- a/proto/zitadel/execution/v3alpha/query.proto +++ b/proto/zitadel/action/v3alpha/query.proto @@ -1,14 +1,14 @@ syntax = "proto3"; -package zitadel.execution.v3alpha; +package zitadel.action.v3alpha; -option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution"; +option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action"; import "google/api/field_behavior.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; import "zitadel/object/v2beta/object.proto"; -import "zitadel/execution/v3alpha/execution.proto"; +import "zitadel/action/v3alpha/execution.proto"; message SearchQuery { oneof query { diff --git a/proto/zitadel/execution/v3alpha/target.proto b/proto/zitadel/action/v3alpha/target.proto similarity index 94% rename from proto/zitadel/execution/v3alpha/target.proto rename to proto/zitadel/action/v3alpha/target.proto index 4320253e37..a034c58ace 100644 --- a/proto/zitadel/execution/v3alpha/target.proto +++ b/proto/zitadel/action/v3alpha/target.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package zitadel.execution.v3alpha; +package zitadel.action.v3alpha; import "google/api/annotations.proto"; import "google/api/field_behavior.proto"; @@ -11,7 +11,7 @@ import "validate/validate.proto"; import "zitadel/object/v2beta/object.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; -option go_package = "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha;execution"; +option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action"; message SetRESTWebhook { string url = 1 [ diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 3e42109619..5f5639060a 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -381,8 +381,24 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Get SMTP Configuration"; - description: "Returns the SMTP configuration from the system. This is used to send E-Mails to the users." + summary: "Get active SMTP Configuration"; + description: "Returns the active SMTP configuration from the system. This is used to send E-Mails to the users." + }; + } + + rpc GetSMTPConfigById(GetSMTPConfigByIdRequest) returns (GetSMTPConfigByIdResponse) { + option (google.api.http) = { + get: "/smtp/{id}"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP"; + summary: "Get SMTP provider configuration by its id"; + description: "Get a specific SMTP provider configuration by its ID."; }; } @@ -405,7 +421,7 @@ service AdminService { rpc UpdateSMTPConfig(UpdateSMTPConfigRequest) returns (UpdateSMTPConfigResponse) { option (google.api.http) = { - put: "/smtp"; + put: "/smtp/{id}"; body: "*" }; @@ -422,7 +438,7 @@ service AdminService { rpc UpdateSMTPConfigPassword(UpdateSMTPConfigPasswordRequest) returns (UpdateSMTPConfigPasswordResponse) { option (google.api.http) = { - put: "/smtp/password"; + put: "/smtp/{id}/password"; body: "*" }; @@ -437,9 +453,43 @@ service AdminService { }; } + rpc ActivateSMTPConfig(ActivateSMTPConfigRequest) returns (ActivateSMTPConfigResponse) { + option (google.api.http) = { + post: "/smtp/{id}/_activate"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP Provider"; + summary: "Activate SMTP Provider"; + description: "Activate an SMTP provider." + }; + } + + rpc DeactivateSMTPConfig(DeactivateSMTPConfigRequest) returns (DeactivateSMTPConfigResponse) { + option (google.api.http) = { + post: "/smtp/{id}/_deactivate"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP Provider"; + summary: "Deactivate SMTP Provider"; + description: "Deactivate an SMTP provider. After deactivating the provider, the users will not be able to receive SMTP notifications from that provider anymore." + }; + } + rpc RemoveSMTPConfig(RemoveSMTPConfigRequest) returns (RemoveSMTPConfigResponse) { option (google.api.http) = { - delete: "/smtp"; + delete: "/smtp/{id}"; }; option (zitadel.v1.auth_option) = { @@ -453,6 +503,23 @@ service AdminService { }; } + rpc ListSMTPConfigs(ListSMTPConfigsRequest) returns (ListSMTPConfigsResponse) { + option (google.api.http) = { + post: "/smtp/_search" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP Configs"; + summary: "List SMTP Configs"; + description: "Returns a list of SMTP configurations." + }; + } + rpc ListSMSProviders(ListSMSProvidersRequest) returns (ListSMSProvidersResponse) { option (google.api.http) = { post: "/sms/_search" @@ -4007,6 +4074,23 @@ message GetSMTPConfigResponse { zitadel.settings.v1.SMTPConfig smtp_config = 1; } +message GetSMTPConfigByIdRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; +} + +message GetSMTPConfigByIdResponse { + zitadel.settings.v1.SMTPConfig smtp_config = 1; +} + +message ListSMTPConfigsRequest { + zitadel.v1.ListQuery query = 1; +} + +message ListSMTPConfigsResponse { + zitadel.v1.ListDetails details = 1; + repeated zitadel.settings.v1.SMTPConfig result = 2; +} + message AddSMTPConfigRequest { string sender_address = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -4051,7 +4135,15 @@ message AddSMTPConfigRequest { (validate.rules).string = {min_len: 0, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"replyto@m.zitadel.cloud\""; - min_length: 1; + min_length: 0; + max_length: 200; + } + ]; + string description = 8 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"provider description\""; + min_length: 0; max_length: 200; } ]; @@ -4059,6 +4151,7 @@ message AddSMTPConfigRequest { message AddSMTPConfigResponse { zitadel.v1.ObjectDetails details = 1; + string id = 2; } message UpdateSMTPConfigRequest { @@ -4104,6 +4197,20 @@ message UpdateSMTPConfigRequest { max_length: 200; } ]; + string password = 7 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"this-is-my-password\""; + } + ]; + string description = 8 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"provider description\""; + min_length: 1; + max_length: 200; + } + ]; + string id = 9 [(validate.rules).string = {min_len: 1, max_len: 100}]; } message UpdateSMTPConfigResponse { @@ -4116,14 +4223,32 @@ message UpdateSMTPConfigPasswordRequest { example: "\"this-is-my-updated-password\""; } ]; + string id = 2 [(validate.rules).string = {min_len: 1, max_len: 100}]; } message UpdateSMTPConfigPasswordResponse { zitadel.v1.ObjectDetails details = 1; } -//this is an empty request -message RemoveSMTPConfigRequest {} +message ActivateSMTPConfigRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; +} + +message ActivateSMTPConfigResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message DeactivateSMTPConfigRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; +} + +message DeactivateSMTPConfigResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message RemoveSMTPConfigRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; +} message RemoveSMTPConfigResponse { zitadel.v1.ObjectDetails details = 1; @@ -6650,6 +6775,12 @@ message UpdateLockoutPolicyRequest { example: "\"10\"" } ]; + uint32 max_otp_attempts = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } message UpdateLockoutPolicyResponse { @@ -7488,6 +7619,7 @@ message SetCustomLoginTextsRequest { zitadel.text.v1.PasswordlessRegistrationScreenText passwordless_registration_text = 33; zitadel.text.v1.PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34; zitadel.text.v1.ExternalRegistrationUserOverviewScreenText external_registration_user_overview_text = 35; + zitadel.text.v1.LinkingUserPromptScreenText linking_user_prompt_text = 36; } message SetCustomLoginTextsResponse { diff --git a/proto/zitadel/feature/v2beta/instance.proto b/proto/zitadel/feature/v2beta/instance.proto index f2df465c6a..3cfc2c4506 100644 --- a/proto/zitadel/feature/v2beta/instance.proto +++ b/proto/zitadel/feature/v2beta/instance.proto @@ -43,6 +43,12 @@ message SetInstanceFeaturesRequest{ description: "Enable the experimental `urn:ietf:params:oauth:grant-type:token-exchange` grant type for the OIDC token endpoint. Token exchange can be used to request tokens with a lesser scope or impersonate other users. See the security policy to allow impersonation on an instance."; } ]; + optional bool actions = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "true"; + description: "Actions allow to manage data executions and targets. If the flag is enabled, you'll be able to use the new API and its features. Note that it is still in an early stage."; + } + ]; } message SetInstanceFeaturesResponse { @@ -100,4 +106,11 @@ message GetInstanceFeaturesResponse { description: "Enable the experimental `urn:ietf:params:oauth:grant-type:token-exchange` grant type for the OIDC token endpoint. Token exchange can be used to request tokens with a lesser scope or impersonate other users. See the security policy to allow impersonation on an instance."; } ]; + + FeatureFlag actions = 7 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "true"; + description: "Actions v2 allow to manage data executions and targets. If the flag is enabled, you'll be able to use the new API and its features. Note that it is still in an early stage."; + } + ]; } diff --git a/proto/zitadel/feature/v2beta/system.proto b/proto/zitadel/feature/v2beta/system.proto index 0091bcbd33..8f98eb0625 100644 --- a/proto/zitadel/feature/v2beta/system.proto +++ b/proto/zitadel/feature/v2beta/system.proto @@ -45,7 +45,14 @@ message SetSystemFeaturesRequest{ description: "Enable the experimental `urn:ietf:params:oauth:grant-type:token-exchange` grant type for the OIDC token endpoint. Token exchange can be used to request tokens with a lesser scope or impersonate other users. See the security policy to allow impersonation on an instance."; } ]; - + + + optional bool actions = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "true"; + description: "Actions allow to manage data executions and targets. If the flag is enabled, you'll be able to use the new API and its features. Note that it is still in an early stage."; + } + ]; } message SetSystemFeaturesResponse { @@ -96,4 +103,11 @@ message GetSystemFeaturesResponse { description: "Enable the experimental `urn:ietf:params:oauth:grant-type:token-exchange` grant type for the OIDC token endpoint. Token exchange can be used to request tokens with a lesser scope or impersonate other users. See the security policy to allow impersonation on an instance."; } ]; + + FeatureFlag actions = 7 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "true"; + description: "Actions v2 allow to manage data executions and targets. If the flag is enabled, you'll be able to use the new API and its features. Note that it is still in an early stage."; + } + ]; } diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto index 6171471075..1beab90a5f 100644 --- a/proto/zitadel/idp.proto +++ b/proto/zitadel/idp.proto @@ -515,6 +515,21 @@ message Options { description: "Enable if a the ZITADEL account fields should be updated automatically on each login."; } ]; + AutoLinkingOption auto_linking = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Enable if users should get prompted to link an existing ZITADEL user to an external account if the selected attribute matches."; + } + ]; +} + +enum AutoLinkingOption { + // AUTO_LINKING_OPTION_UNSPECIFIED disables the auto linking prompt. + AUTO_LINKING_OPTION_UNSPECIFIED = 0; + // AUTO_LINKING_OPTION_USERNAME will use the username of the external user to check for a corresponding ZITADEL user. + AUTO_LINKING_OPTION_USERNAME = 1; + // AUTO_LINKING_OPTION_EMAIL will use the email of the external user to check for a corresponding ZITADEL user with the same verified email + // Note that in case multiple users match, no prompt will be shown. + AUTO_LINKING_OPTION_EMAIL = 2; } message LDAPAttributes { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 284b070d5d..0b1a3b6a10 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -10412,6 +10412,12 @@ message AddCustomLockoutPolicyRequest { description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger." } ]; + uint32 max_otp_attempts = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } message AddCustomLockoutPolicyResponse { @@ -10424,6 +10430,12 @@ message UpdateCustomLockoutPolicyRequest { description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger." } ]; + uint32 max_otp_attempts = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } message UpdateCustomLockoutPolicyResponse { @@ -10936,6 +10948,7 @@ message SetCustomLoginTextsRequest { zitadel.text.v1.PasswordlessRegistrationScreenText passwordless_registration_text = 33; zitadel.text.v1.PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34; zitadel.text.v1.ExternalRegistrationUserOverviewScreenText external_registration_user_overview_text = 35; + zitadel.text.v1.LinkingUserPromptScreenText linking_user_prompt_text = 36; } message SetCustomLoginTextsResponse { diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto index 8fcb976d44..75d8472103 100644 --- a/proto/zitadel/policy.proto +++ b/proto/zitadel/policy.proto @@ -337,6 +337,12 @@ message LockoutPolicy { example: "\"10\"" } ]; + uint64 max_otp_attempts = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; bool is_default = 4 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "defines if the organization's admin changed the policy" diff --git a/proto/zitadel/settings.proto b/proto/zitadel/settings.proto index ffb2dd9da8..18b343df08 100644 --- a/proto/zitadel/settings.proto +++ b/proto/zitadel/settings.proto @@ -40,6 +40,12 @@ message SecretGeneratorTypeQuery { SecretGeneratorType generator_type = 1; } +enum SMTPConfigState { + SMTP_CONFIG_STATE_UNSPECIFIED = 0; + SMTP_CONFIG_ACTIVE = 1; + SMTP_CONFIG_INACTIVE = 2; +} + enum SecretGeneratorType { SECRET_GENERATOR_TYPE_UNSPECIFIED = 0; SECRET_GENERATOR_TYPE_INIT_CODE = 1; @@ -80,6 +86,13 @@ message SMTPConfig { example: "\"replyto@m.zitadel.cloud\""; } ]; + SMTPConfigState state = 8; + string description = 9 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"Mailjet\""; + } + ]; + string id = 10; } message SMSProvider { diff --git a/proto/zitadel/settings/v2beta/lockout_settings.proto b/proto/zitadel/settings/v2beta/lockout_settings.proto index 10786c9282..1ff10c65f0 100644 --- a/proto/zitadel/settings/v2beta/lockout_settings.proto +++ b/proto/zitadel/settings/v2beta/lockout_settings.proto @@ -20,4 +20,10 @@ message LockoutSettings { description: "resource_owner_type returns if the settings is managed on the organization or on the instance"; } ]; + uint64 max_otp_attempts = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } diff --git a/proto/zitadel/text.proto b/proto/zitadel/text.proto index 602f7f9caf..b6632a1b42 100644 --- a/proto/zitadel/text.proto +++ b/proto/zitadel/text.proto @@ -92,6 +92,7 @@ message LoginCustomText { PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34; ExternalRegistrationUserOverviewScreenText external_registration_user_overview_text = 35; bool is_default = 36; + LinkingUserPromptScreenText linking_user_prompt_text = 37; } message SelectAccountScreenText { @@ -357,6 +358,13 @@ message RegistrationOrgScreenText { string save_button_text = 19 [(validate.rules).string = {max_len: 200}]; } +message LinkingUserPromptScreenText { + string title = 1 [(validate.rules).string = {max_len: 200}]; + string description = 2 [(validate.rules).string = {max_len: 500}]; + string link_button_text = 3 [(validate.rules).string = {max_len: 100}]; + string other_button_text = 4 [(validate.rules).string = {max_len: 100}]; +} + message LinkingUserDoneScreenText { string title = 1 [(validate.rules).string = {max_len: 200}]; string description = 2 [(validate.rules).string = {max_len: 500}]; diff --git a/release-channels.yaml b/release-channels.yaml index aba4c56cd4..0830924e47 100644 --- a/release-channels.yaml +++ b/release-channels.yaml @@ -1 +1 @@ -stable: "v2.42.16" +stable: "v2.44.7"