diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index ef289463e0..b8c7486f1f 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -49,7 +49,7 @@ jobs:
turbo-lint-unit:
if: ${{ github.event_name == 'pull_request' }}
name: turbo-lint-unit
- runs-on: depot-ubuntu-22.04-8
+ runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/.github/workflows/login-container.yml b/.github/workflows/login-container.yml
index 6910bca074..5137213cc4 100644
--- a/.github/workflows/login-container.yml
+++ b/.github/workflows/login-container.yml
@@ -30,7 +30,7 @@ env:
jobs:
login-container:
name: Build Login Container
- runs-on: depot-ubuntu-22.04-8
+ runs-on: ubuntu-latest
permissions:
packages: write
steps:
diff --git a/apps/login/README.md b/apps/login/README.md
deleted file mode 100644
index cdde703f48..0000000000
--- a/apps/login/README.md
+++ /dev/null
@@ -1,263 +0,0 @@
-# ZITADEL TypeScript with Turborepo
-
-This repository contains all TypeScript and JavaScript packages and applications you need to create your own ZITADEL
-Login UI.
-
-
-
-[](https://www.npmjs.com/package/@zitadel/proto)
-[](https://www.npmjs.com/package/@zitadel/client)
-
-**⚠️ This repo and packages are in beta state and subject to change ⚠️**
-
-The scope of functionality of this repo and packages is under active development.
-
-The `@zitadel/client` package is using [@connectrpc/connect](https://github.com/connectrpc/connect-es#readme).
-
-You can read the [contribution guide](/CONTRIBUTING.md) on how to contribute.
-Questions can be raised in our [Discord channel](https://discord.gg/erh5Brh7jE) or as
-a [GitHub issue](https://github.com/zitadel/typescript/issues).
-
-## Developing Your Own ZITADEL Login UI
-
-We think the easiest path of getting up and running, is the following:
-
-1. Fork and clone this repository
-1. Rename the file .github/dependabot.example.yml to .github/dependabot.yml so you don't miss version and security updates.
-1. [Run the ZITADEL Cloud login UI locally](#run-login-ui)
-1. Make changes to the code and see the effects live on your local machine
-1. Study the rest of this README.md and get familiar and comfortable with how everything works.
-1. Decide on a way of how you want to build and run your login UI.
- You can reuse ZITADEL Clouds way.
- But if you need more freedom, you can also import the packages you need into your self built application.
-
-## Included Apps And Packages
-
-- `login`: The login UI used by ZITADEL Cloud, powered by Next.js
-- `@zitadel/client`: shared client utilities for node and browser environments
-- `@zitadel/proto`: Protocol Buffers (proto) definitions used by ZITADEL projects
-
-Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
-
-### Login
-
-The login is currently in a work in progress state.
-The goal is to implement a login UI, using the session API of ZITADEL, which also implements the OIDC Standard and is
-ready to use for everyone.
-
-In the first phase we want to have a MVP login ready with the OIDC Standard and a basic feature set. In a second step
-the features will be extended.
-
-This list should show the current implementation state, and also what is missing.
-You can already use the current state, and extend it with your needs.
-
-#### Features list
-
-- [x] Local User Registration (with Password)
-- [x] User Registration and Login with external Provider
- - [x] Google
- - [x] GitHub
- - [x] GitHub Enterprise
- - [x] GitLab
- - [x] GitLab Enterprise
- - [x] Azure
- - [x] Apple
- - [x] Generic OIDC
- - [x] Generic OAuth
- - [x] Generic JWT
- - [x] LDAP
- - [x] SAML SP
-- Multifactor Registration an Login
- - [x] Passkeys
- - [x] TOTP
- - [x] OTP: Email Code
- - [x] OTP: SMS Code
-- [x] Password Change/Reset
-- [x] Domain Discovery
-- [x] Branding
-- OIDC Standard
-
- - [x] Authorization Code Flow with PKCE
- - [x] AuthRequest `hintUserId`
- - [x] AuthRequest `loginHint`
- - [x] AuthRequest `prompt`
- - [x] Login
- - [x] Select Account
- - [ ] Consent
- - [x] Create
- - Scopes
- - [x] `openid email profile address``
- - [x] `offline access`
- - [x] `urn:zitadel:iam:org:idp:id:{idp_id}`
- - [x] `urn:zitadel:iam:org:project:id:zitadel:aud`
- - [x] `urn:zitadel:iam:org:id:{orgid}`
- - [x] `urn:zitadel:iam:org:domain:primary:{domain}`
- - [ ] AuthRequest UI locales
-
- #### Flow diagram
-
- This diagram shows the available pages and flows.
-
- > Note that back navigation or retries are not displayed.
-
-```mermaid
- flowchart TD
- A[Start] --> register
- A[Start] --> accounts
- A[Start] --> loginname
- loginname -- signInWithIDP --> idp-success
- loginname -- signInWithIDP --> idp-failure
- idp-success --> B[signedin]
- loginname --> password
- loginname -- hasPasskey --> passkey
- loginname -- allowRegister --> register
- passkey-add --passwordAllowed --> password
- passkey -- hasPassword --> password
- passkey --> B[signedin]
- password -- hasMFA --> mfa
- password -- allowPasskeys --> passkey-add
- password -- reset --> password-set
- email -- reset --> password-set
- password-set --> B[signedin]
- password-change --> B[signedin]
- password -- userstate=initial --> password-change
-
- mfa --> otp
- otp --> B[signedin]
- mfa--> u2f
- u2f -->B[signedin]
- register -- password/passkey --> B[signedin]
- password --> B[signedin]
- password-- forceMFA -->mfaset
- mfaset --> u2fset
- mfaset --> otpset
- u2fset --> B[signedin]
- otpset --> B[signedin]
- accounts--> loginname
- password -- not verified yet -->verify
- register-- withpassword -->verify
- passkey-- notVerified --> verify
- verify --> B[signedin]
-```
-
-You can find a more detailed documentation of the different pages [here](./apps/login/readme.md).
-
-#### Custom translations
-
-The new login uses the [SettingsApi](https://zitadel.com/docs/apis/resources/settings_service_v2/settings-service-get-hosted-login-translation) to load custom translations.
-Translations can be overriden at both the instance and organization levels.
-To find the keys more easily, you can inspect the HTML and search for a `data-i18n-key` attribute, or look at the defaults in `/apps/login/locales/[locale].ts`.
-
-
-## Tooling
-
-- [TypeScript](https://www.typescriptlang.org/) for static type checking
-- [ESLint](https://eslint.org/) for code linting
-- [Prettier](https://prettier.io) for code formatting
-
-## Useful Commands
-
-- `make login-quality` - Check the quality of your code against a production build without installing any dependencies besides Docker
-- `pnpm generate` - Build proto stubs for the client package
-- `pnpm dev` - Develop all packages and the login app
-- `pnpm build` - Build all packages and the login app
-- `pnpm clean` - Clean up all `node_modules` and `dist` folders (runs each package's clean script)
-
-Learn more about developing the login UI in the [contribution guide](/CONTRIBUTING.md).
-
-## Versioning And Publishing Packages
-
-Package publishing has been configured using [Changesets](https://github.com/changesets/changesets).
-Here is their [documentation](https://github.com/changesets/changesets#documentation) for more information about the
-workflow.
-
-The [GitHub Action](https://github.com/changesets/action) needs an `NPM_TOKEN` and `GITHUB_TOKEN` in the repository
-settings. The [Changesets bot](https://github.com/apps/changeset-bot) should also be installed on the GitHub repository.
-
-Read the [changesets documentation](https://github.com/changesets/changesets/blob/main/docs/automating-changesets.md)
-for more information about this automation
-
-### Run Login UI
-
-To run the application make sure to install the dependencies with
-
-```sh
-pnpm install
-```
-
-then generate the GRPC stubs with
-
-```sh
-pnpm generate
-```
-
-To run the application against a local ZITADEL instance, run the following command:
-
-```sh
-pnpm run-zitadel
-```
-
-This sets up ZITADEL using docker compose and writes the configuration to the file `apps/login/.env.local`.
-
-
-Alternatively, use another environment
-You can develop against any ZITADEL instance in which you have sufficient rights to execute the following steps.
-Just create or overwrite the file `apps/login/.env.local` yourself.
-Add your instances base URL to the file at the key `ZITADEL_API_URL`.
-Go to your instance and create a service user for the login application.
-The login application creates users on your primary organization and reads policy data.
-For the sake of simplicity, just make the service user an instance member with the role `IAM_OWNER`.
-Create a PAT and copy it to the file `apps/login/.env.local` using the key `ZITADEL_SERVICE_USER_TOKEN`.
-
-The file should look similar to this:
-
-```
-ZITADEL_API_URL=https://zitadel-tlx3du.us1.zitadel.cloud
-ZITADEL_SERVICE_USER_TOKEN=1S6w48thfWFI2klgfwkCnhXJLf9FQ457E-_3H74ePQxfO3Af0Tm4V5Xi-ji7urIl_xbn-Rk
-```
-
-
-
-Start the login application in dev mode:
-
-```sh
-pnpm dev
-```
-
-Open the login application with your favorite browser at `localhost:3000`.
-Change the source code and see the changes live in your browser.
-
-Make sure the application still behaves as expected by running all tests
-
-```sh
-pnpm test
-```
-
-To satisfy your unique workflow requirements, check out the package.json in the root directory for more detailed scripts.
-
-### Run Login UI Acceptance tests
-
-To run the acceptance tests you need a running ZITADEL environment and a component which receives HTTP requests for the emails and sms's.
-This component should also be able to return the content of these notifications, as the codes and links are used in the login flows.
-There is a basic implementation in Golang available under [the sink package](./acceptance/sink).
-
-To setup ZITADEL with the additional Sink container for handling the notifications:
-
-```sh
-pnpm run-sink
-```
-
-Then you can start the acceptance tests with:
-
-```sh
-pnpm test:acceptance
-```
-
-### Deploy to Vercel
-
-To deploy your own version on Vercel, navigate to your instance and create a service user.
-Then create a personal access token (PAT), copy and set it as ZITADEL_SERVICE_USER_TOKEN, then navigate to your instance
-settings and make sure it gets IAM_OWNER permissions.
-Finally set your instance url as ZITADEL_API_URL. Make sure to set it without trailing slash.
-
-[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fzitadel%2Ftypescript&env=ZITADEL_API_URL,ZITADEL_SERVICE_USER_TOKEN&root-directory=apps/login&envDescription=Setup%20a%20service%20account%20with%20IAM_LOGIN_CLIENT%20membership%20on%20your%20instance%20and%20provide%20its%20personal%20access%20token.&project-name=zitadel-login&repository-name=zitadel-login)
diff --git a/internal/command/user_v2_invite_model.go b/internal/command/user_v2_invite_model.go
index 6b2ab62e0d..91726498f8 100644
--- a/internal/command/user_v2_invite_model.go
+++ b/internal/command/user_v2_invite_model.go
@@ -65,8 +65,11 @@ func (wm *UserV2InviteWriteModel) Reduce() error {
wm.EmptyInviteCode()
case *user.HumanInviteCheckFailedEvent:
wm.InviteCheckFailureCount++
- if wm.InviteCheckFailureCount >= 3 { //TODO: config?
- wm.UserState = domain.UserStateDeleted
+ if wm.InviteCheckFailureCount >= 3 || crypto.IsCodeExpired(wm.InviteCodeCreationDate, wm.InviteCodeExpiry) { //TODO: make failure count comparison with wm.InviteCheckFailureCount configurable?
+ // invalidate the invite code after attempting to verify an expired code, or a wrong code three or more times
+ // so that a new invite code can be created for this user
+ wm.EmptyInviteCode()
+ wm.CodeReturned = false
}
case *user.HumanEmailVerifiedEvent:
wm.EmailVerified = true
diff --git a/internal/command/user_v2_invite_test.go b/internal/command/user_v2_invite_test.go
index 53ad1bd944..49a2e78249 100644
--- a/internal/command/user_v2_invite_test.go
+++ b/internal/command/user_v2_invite_test.go
@@ -21,6 +21,7 @@ import (
)
func TestCommands_CreateInviteCode(t *testing.T) {
+ t.Parallel()
type fields struct {
checkPermission domain.PermissionCheck
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
@@ -323,9 +324,348 @@ func TestCommands_CreateInviteCode(t *testing.T) {
returnCode: nil,
},
},
+ {
+ "create ok after three verification failures",
+ fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ "username", "firstName",
+ "lastName",
+ "nickName",
+ "displayName",
+ language.Afrikaans,
+ domain.GenderUnspecified,
+ "email",
+ false,
+ ),
+ ),
+ // first invite code generated and returned
+ eventFromEventPusherWithCreationDateNow(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code1"),
+ },
+ time.Hour,
+ "",
+ true,
+ "",
+ "",
+ ),
+ ),
+ // simulate three failed verification attempts
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ ),
+ expectPush(
+ eventFromEventPusher(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code2"),
+ },
+ time.Hour,
+ "",
+ false,
+ "",
+ "",
+ ),
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code2", time.Hour),
+ defaultSecretGenerators: &SecretGenerators{},
+ },
+ args{
+ ctx: context.Background(),
+ invite: &CreateUserInvite{
+ UserID: "userID",
+ },
+ },
+ want{
+ details: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "userID",
+ },
+ returnCode: nil,
+ },
+ },
+ {
+ "return ok after three verification failures",
+ fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ "username", "firstName",
+ "lastName",
+ "nickName",
+ "displayName",
+ language.Afrikaans,
+ domain.GenderUnspecified,
+ "email",
+ false,
+ ),
+ ),
+ // first invite code generated and returned
+ eventFromEventPusherWithCreationDateNow(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code1"),
+ },
+ time.Hour,
+ "",
+ true,
+ "",
+ "",
+ ),
+ ),
+ // simulate three failed verification attempts
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ ),
+ expectPush(
+ eventFromEventPusher(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code2"),
+ },
+ time.Hour,
+ "",
+ true,
+ "",
+ "",
+ ),
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code2", time.Hour),
+ defaultSecretGenerators: &SecretGenerators{},
+ },
+ args{
+ ctx: context.Background(),
+ invite: &CreateUserInvite{
+ UserID: "userID",
+ ReturnCode: true,
+ },
+ },
+ want{
+ details: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "userID",
+ },
+ returnCode: gu.Ptr("code2"),
+ },
+ },
+ {
+ "create ok after verification fails due to invite code expiration",
+ fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ "username", "firstName",
+ "lastName",
+ "nickName",
+ "displayName",
+ language.Afrikaans,
+ domain.GenderUnspecified,
+ "email",
+ false,
+ ),
+ ),
+ // first invite code generated and returned
+ eventFromEventPusherWithCreationDateNow(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code1"),
+ },
+ -5*time.Minute, // expired code
+ "",
+ true,
+ "",
+ "",
+ ),
+ ),
+ // simulate a failed verification attempt due to expiry
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ ),
+ expectPush(
+ eventFromEventPusher(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code2"),
+ },
+ time.Hour,
+ "",
+ false,
+ "",
+ "",
+ ),
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code2", time.Hour),
+ defaultSecretGenerators: &SecretGenerators{},
+ },
+ args{
+ ctx: context.Background(),
+ invite: &CreateUserInvite{
+ UserID: "userID",
+ },
+ },
+ want{
+ details: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "userID",
+ },
+ returnCode: nil,
+ },
+ },
+ {
+ "return ok after verification fails due to invite code expiration",
+ fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ "username", "firstName",
+ "lastName",
+ "nickName",
+ "displayName",
+ language.Afrikaans,
+ domain.GenderUnspecified,
+ "email",
+ false,
+ ),
+ ),
+ // first invite code generated and returned
+ eventFromEventPusherWithCreationDateNow(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code1"),
+ },
+ -5*time.Minute, // expired code
+ "",
+ true,
+ "",
+ "",
+ ),
+ ),
+ // simulate a failed verification attempt due to expiry
+ eventFromEventPusher(
+ user.NewHumanInviteCheckFailedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ ),
+ ),
+ ),
+ expectPush(
+ eventFromEventPusher(
+ user.NewHumanInviteCodeAddedEvent(context.Background(),
+ &user.NewAggregate("userID", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("code2"),
+ },
+ time.Hour,
+ "",
+ true,
+ "",
+ "",
+ ),
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code2", time.Hour),
+ defaultSecretGenerators: &SecretGenerators{},
+ },
+ args{
+ ctx: context.Background(),
+ invite: &CreateUserInvite{
+ UserID: "userID",
+ ReturnCode: true,
+ },
+ },
+ want{
+ details: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "userID",
+ },
+ returnCode: gu.Ptr("code2"),
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
c := &Commands{
checkPermission: tt.fields.checkPermission,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
@@ -342,6 +682,7 @@ func TestCommands_CreateInviteCode(t *testing.T) {
}
func TestCommands_ResendInviteCode(t *testing.T) {
+ t.Parallel()
type fields struct {
checkPermission domain.PermissionCheck
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
@@ -713,6 +1054,7 @@ func TestCommands_ResendInviteCode(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
c := &Commands{
checkPermission: tt.fields.checkPermission,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
@@ -727,6 +1069,7 @@ func TestCommands_ResendInviteCode(t *testing.T) {
}
func TestCommands_InviteCodeSent(t *testing.T) {
+ t.Parallel()
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
}
@@ -845,6 +1188,7 @@ func TestCommands_InviteCodeSent(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
@@ -855,6 +1199,7 @@ func TestCommands_InviteCodeSent(t *testing.T) {
}
func TestCommands_VerifyInviteCode(t *testing.T) {
+ t.Parallel()
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
@@ -940,6 +1285,7 @@ func TestCommands_VerifyInviteCode(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
c := &Commands{
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,
@@ -952,6 +1298,7 @@ func TestCommands_VerifyInviteCode(t *testing.T) {
}
func TestCommands_VerifyInviteCodeSetPassword(t *testing.T) {
+ t.Parallel()
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
@@ -1268,6 +1615,7 @@ func TestCommands_VerifyInviteCodeSetPassword(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
c := &Commands{
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,