feat: invite user link (#8578)

# Which Problems Are Solved

As an administrator I want to be able to invite users to my application
with the API V2, some user data I will already prefil, the user should
add the authentication method themself (password, passkey, sso).

# How the Problems Are Solved

- A user can now be created with a email explicitly set to false.
- If a user has no verified email and no authentication method, an
`InviteCode` can be created through the User V2 API.
  - the code can be returned or sent through email
- additionally `URLTemplate` and an `ApplicatioName` can provided for
the email
- The code can be resent and verified through the User V2 API
- The V1 login allows users to verify and resend the code and set a
password (analog user initialization)
- The message text for the user invitation can be customized

# Additional Changes

- `verifyUserPasskeyCode` directly uses `crypto.VerifyCode` (instead of
`verifyEncryptedCode`)
- `verifyEncryptedCode` is removed (unnecessarily queried for the code
generator)

# Additional Context

- closes #8310
- TODO: login V2 will have to implement invite flow:
https://github.com/zitadel/typescript/issues/166
This commit is contained in:
Livio Spring
2024-09-11 12:53:55 +02:00
committed by GitHub
parent 02c78a19c6
commit a07b2f4677
114 changed files with 3898 additions and 293 deletions

View File

@@ -41,14 +41,6 @@ func newEncryptedCodeWithDefaultConfig(ctx context.Context, filter preparation.F
}, nil
}
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.Alg())
}
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 {

View File

@@ -123,78 +123,6 @@ func Test_newCryptoCode(t *testing.T) {
}
}
func Test_verifyCryptoCode(t *testing.T) {
es := eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
))
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.EncryptionAlgorithm
expiry time.Duration
crypted *crypto.CryptoValue
plain string
}
tests := []struct {
name string
eventsore *eventstore.Eventstore
args args
wantErr bool
}{
{
name: "filter config error",
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry,
crypted: code.Crypted,
plain: code.Plain,
},
wantErr: true,
},
{
name: "success",
eventsore: eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry,
crypted: code.Crypted,
plain: code.Plain,
},
},
{
name: "wrong plain",
eventsore: eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry,
crypted: code.Crypted,
plain: "wrong",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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
}
require.NoError(t, err)
})
}
}
func Test_cryptoCodeGenerator(t *testing.T) {
type args struct {
typ domain.SecretGeneratorType

View File

@@ -12,6 +12,9 @@ type Email struct {
Address domain.EmailAddress
Verified bool
// NoEmailVerification is used Verified field is false
NoEmailVerification bool
// ReturnCode is used if the Verified field is false
ReturnCode bool

View File

@@ -145,6 +145,7 @@ type SecretGenerators struct {
DomainVerification *crypto.GeneratorConfig
OTPSMS *crypto.GeneratorConfig
OTPEmail *crypto.GeneratorConfig
InviteCode *crypto.GeneratorConfig
}
type ZitadelConfig struct {

View File

@@ -388,6 +388,10 @@ func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.Filte
return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
}
func (c *Commands) newUserInviteCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newEncryptedCodeWithDefault(ctx, filter, domain.SecretGeneratorTypeInviteCode, alg, c.defaultSecretGenerators.InviteCode)
}
func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) {
user := NewUserWriteModel(userID, resourceOwner)
events, err := filter(ctx, user.Query())

View File

@@ -284,6 +284,9 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
}
return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry, human.AuthRequestID)), nil
}
if human.Email.NoEmailVerification {
return cmds, nil
}
if !human.Email.Verified {
emailCode, err := c.newEmailCode(ctx, filter, codeAlg)
if err != nil {

View File

@@ -626,6 +626,67 @@ func TestCommandSide_AddHuman(t *testing.T) {
wantEmailCode: "emailCode",
},
},
{
name: "add human (with password and unverified email), ok (no email code)",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "", AllowedLanguage),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("emailCode", time.Hour),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &AddHuman{
Username: "username",
Password: "password",
FirstName: "firstname",
LastName: "lastname",
Email: Email{
Address: "email@test.ch",
Verified: false,
NoEmailVerification: true,
},
PreferredLanguage: AllowedLanguage,
},
secretGenerator: GetMockSecretGenerator(t),
allowInitMail: false,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
wantID: "user1",
},
},
{
name: "add human email verified, ok",
fields: fields{

View File

@@ -1,6 +1,8 @@
package command
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user"
@@ -122,6 +124,10 @@ func UserAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregat
return eventstore.AggregateFromWriteModel(wm, user.AggregateType, user.AggregateVersion)
}
func UserAggregateFromWriteModelCtx(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
return eventstore.AggregateFromWriteModelCtx(ctx, wm, user.AggregateType, user.AggregateVersion)
}
func isUserStateExists(state domain.UserState) bool {
return !hasUserState(state, domain.UserStateDeleted, domain.UserStateUnspecified)
}

View File

@@ -0,0 +1,193 @@
package command
import (
"context"
"strings"
"github.com/zitadel/logging"
"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/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
type CreateUserInvite struct {
UserID string
URLTemplate string
ReturnCode bool
ApplicationName string
}
func (c *Commands) CreateInviteCode(ctx context.Context, invite *CreateUserInvite) (details *domain.ObjectDetails, returnCode *string, err error) {
invite.UserID = strings.TrimSpace(invite.UserID)
if invite.UserID == "" {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4jio3", "Errors.User.UserIDMissing")
}
wm, err := c.userInviteCodeWriteModel(ctx, invite.UserID, "")
if err != nil {
return nil, nil, err
}
if err := c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil {
return nil, nil, err
}
if !wm.UserState.Exists() {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wgvn4", "Errors.User.NotFound")
}
if !wm.CreationAllowed() {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-EF34g", "Errors.User.AlreadyInitialised")
}
code, err := c.newUserInviteCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint
if err != nil {
return nil, nil, err
}
err = c.pushAppendAndReduce(ctx, wm, user.NewHumanInviteCodeAddedEvent(
ctx,
UserAggregateFromWriteModelCtx(ctx, &wm.WriteModel),
code.Crypted,
code.Expiry,
invite.URLTemplate,
invite.ReturnCode,
invite.ApplicationName,
"",
))
if err != nil {
return nil, nil, err
}
if invite.ReturnCode {
returnCode = &code.Plain
}
return writeModelToObjectDetails(&wm.WriteModel), returnCode, nil
}
// ResendInviteCode resends the invite mail with a new code and an optional authRequestID.
// It will reuse the applicationName from the previous code.
func (c *Commands) ResendInviteCode(ctx context.Context, userID, resourceOwner, authRequestID string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
}
existingCode, err := c.userInviteCodeWriteModel(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingCode.ResourceOwner, userID); err != nil {
return nil, err
}
}
if !existingCode.UserState.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-H3b2a", "Errors.User.NotFound")
}
if !existingCode.CreationAllowed() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Gg42s", "Errors.User.AlreadyInitialised")
}
if existingCode.InviteCode == nil || existingCode.CodeReturned {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound")
}
code, err := c.newUserInviteCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint
if err != nil {
return nil, err
}
if authRequestID == "" {
authRequestID = existingCode.AuthRequestID
}
err = c.pushAppendAndReduce(ctx, existingCode,
user.NewHumanInviteCodeAddedEvent(
ctx,
UserAggregateFromWriteModelCtx(ctx, &existingCode.WriteModel),
code.Crypted,
code.Expiry,
existingCode.URLTemplate,
false,
existingCode.ApplicationName,
authRequestID,
))
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingCode.WriteModel), nil
}
func (c *Commands) InviteCodeSent(ctx context.Context, userID, orgID string) (err error) {
if userID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Sgf31", "Errors.User.UserIDMissing")
}
existingCode, err := c.userInviteCodeWriteModel(ctx, userID, orgID)
if err != nil {
return err
}
if !existingCode.UserState.Exists() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-HN34a", "Errors.User.NotFound")
}
if existingCode.InviteCode == nil || existingCode.CodeReturned {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound")
}
userAgg := UserAggregateFromWriteModelCtx(ctx, &existingCode.WriteModel)
_, err = c.eventstore.Push(ctx, user.NewHumanInviteCodeSentEvent(ctx, userAgg))
return err
}
func (c *Commands) VerifyInviteCode(ctx context.Context, userID, code string) (details *domain.ObjectDetails, err error) {
return c.VerifyInviteCodeSetPassword(ctx, userID, code, "", "")
}
func (c *Commands) VerifyInviteCodeSetPassword(ctx context.Context, userID, code, password, userAgentID string) (details *domain.ObjectDetails, err error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Gk3f2", "Errors.User.UserIDMissing")
}
wm, err := c.userInviteCodeWriteModel(ctx, userID, "")
if err != nil {
return nil, err
}
if !wm.UserState.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-F5g2h", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModelCtx(ctx, &wm.WriteModel)
err = crypto.VerifyCode(wm.InviteCodeCreationDate, wm.InviteCodeExpiry, wm.InviteCode, code, c.userEncryption)
if err != nil {
_, err = c.eventstore.Push(ctx, user.NewHumanInviteCheckFailedEvent(ctx, userAgg))
logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInviteCheckFailedEvent push failed")
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Wgn4q", "Errors.User.Code.Invalid")
}
commands := []eventstore.Command{
user.NewHumanInviteCheckSucceededEvent(ctx, userAgg),
user.NewHumanEmailVerifiedEvent(ctx, userAgg),
}
if password != "" {
passwordCommand, err := c.setPasswordCommand(
ctx,
userAgg,
wm.UserState,
password,
"",
userAgentID,
false,
nil,
)
if err != nil {
return nil, err
}
commands = append(commands, passwordCommand)
}
err = c.pushAppendAndReduce(ctx, wm, commands...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) userInviteCodeWriteModel(ctx context.Context, userID, orgID string) (writeModel *UserV2InviteWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = newUserV2InviteWriteModel(userID, orgID)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@@ -0,0 +1,141 @@
package command
import (
"time"
"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"
)
type UserV2InviteWriteModel struct {
eventstore.WriteModel
InviteCode *crypto.CryptoValue
InviteCodeCreationDate time.Time
InviteCodeExpiry time.Duration
InviteCheckFailureCount uint8
ApplicationName string
AuthRequestID string
URLTemplate string
CodeReturned bool
EmailVerified bool
AuthMethodSet bool
UserState domain.UserState
}
func (wm *UserV2InviteWriteModel) CreationAllowed() bool {
return !wm.EmailVerified && !wm.AuthMethodSet
}
func newUserV2InviteWriteModel(userID, orgID string) *UserV2InviteWriteModel {
return &UserV2InviteWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: orgID,
},
}
}
func (wm *UserV2InviteWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanAddedEvent:
wm.UserState = domain.UserStateActive
wm.AuthMethodSet = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""
wm.EmptyInviteCode()
wm.ApplicationName = ""
wm.AuthRequestID = ""
case *user.HumanRegisteredEvent:
wm.UserState = domain.UserStateActive
wm.AuthMethodSet = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""
wm.EmptyInviteCode()
wm.ApplicationName = ""
wm.AuthRequestID = ""
case *user.HumanInviteCodeAddedEvent:
wm.SetInviteCode(e.Code, e.Expiry, e.CreationDate())
wm.URLTemplate = e.URLTemplate
wm.CodeReturned = e.CodeReturned
wm.ApplicationName = e.ApplicationName
wm.AuthRequestID = e.AuthRequestID
case *user.HumanInviteCheckSucceededEvent:
wm.EmptyInviteCode()
case *user.HumanInviteCheckFailedEvent:
wm.InviteCheckFailureCount++
if wm.InviteCheckFailureCount >= 3 { //TODO: config?
wm.UserState = domain.UserStateDeleted
}
case *user.HumanEmailVerifiedEvent:
wm.EmailVerified = true
wm.EmptyInviteCode()
case *user.UserLockedEvent:
wm.UserState = domain.UserStateLocked
case *user.UserUnlockedEvent:
wm.UserState = domain.UserStateActive
case *user.UserDeactivatedEvent:
wm.UserState = domain.UserStateInactive
case *user.UserReactivatedEvent:
wm.UserState = domain.UserStateActive
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
case *user.HumanPasswordChangedEvent:
wm.AuthMethodSet = true
case *user.UserIDPLinkAddedEvent:
wm.AuthMethodSet = true
case *user.HumanPasswordlessVerifiedEvent:
wm.AuthMethodSet = true
}
}
return wm.WriteModel.Reduce()
}
func (wm *UserV2InviteWriteModel) SetInviteCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
wm.InviteCode = code
wm.InviteCodeExpiry = expiry
wm.InviteCodeCreationDate = creationDate
wm.InviteCheckFailureCount = 0
}
func (wm *UserV2InviteWriteModel) EmptyInviteCode() {
wm.InviteCode = nil
wm.InviteCodeExpiry = 0
wm.InviteCodeCreationDate = time.Time{}
wm.InviteCheckFailureCount = 0
}
func (wm *UserV2InviteWriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
user.UserV1AddedType,
user.HumanAddedType,
user.UserV1RegisteredType,
user.HumanRegisteredType,
user.HumanInviteCodeAddedType,
user.HumanInviteCheckSucceededType,
user.HumanInviteCheckFailedType,
user.UserV1EmailVerifiedType,
user.HumanEmailVerifiedType,
user.UserLockedType,
user.UserUnlockedType,
user.UserDeactivatedType,
user.UserReactivatedType,
user.UserRemovedType,
user.HumanPasswordChangedType,
user.UserV1PasswordChangedType,
user.UserIDPLinkAddedType,
user.HumanPasswordlessTokenVerifiedType,
).Builder()
if wm.ResourceOwner != "" {
query.ResourceOwner(wm.ResourceOwner)
}
return query
}
func (wm *UserV2InviteWriteModel) Aggregate() *user.Aggregate {
return user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
}

File diff suppressed because it is too large Load Diff

View File

@@ -48,7 +48,7 @@ func (c *Commands) verifyUserPasskeyCode(ctx context.Context, userID, resourceOw
if err != nil {
return nil, err
}
err = verifyEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) //nolint:staticcheck
err = crypto.VerifyCode(wm.ChangeDate, wm.Expiration, wm.CryptoCode, code, alg)
if err != nil || wm.State != domain.PasswordlessInitCodeStateActive {
c.verifyUserPasskeyCodeFailed(ctx, wm)
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid")

View File

@@ -143,7 +143,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
require.NoError(t, err)
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
@@ -163,7 +163,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
{
name: "code verification error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -174,7 +174,6 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectPush(
user.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, "123"),
),
@@ -192,7 +191,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
{
name: "code verification ok, get human passwordless error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -203,7 +202,6 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectFilterError(io.ErrClosedPipe),
),
},
@@ -220,7 +218,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
webauthnConfig: webauthnConfig,
}
@@ -242,7 +240,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
userID string
@@ -260,7 +258,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
{
name: "filter error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
},
@@ -274,7 +272,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
{
name: "code verification error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -285,7 +283,6 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectPush(
user.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, "123"),
),
@@ -302,7 +299,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
{
name: "success",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -313,7 +310,6 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
),
},
args: args{
@@ -328,7 +324,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := c.verifyUserPasskeyCode(ctx, tt.args.userID, tt.args.resourceOwner, tt.args.codeID, tt.args.code, alg)
require.ErrorIs(t, err, tt.wantErr)