feat: Add Twilio Verification Service (#8678)

# Which Problems Are Solved
Twilio supports a robust, multi-channel verification service that
notably supports multi-region SMS sender numbers required for our use
case. Currently, Zitadel does much of the work of the Twilio Verify (eg.
localization, code generation, messaging) but doesn't support the pool
of sender numbers that Twilio Verify does.

# How the Problems Are Solved
To support this API, we need to be able to store the Twilio Service ID
and send that in a verification request where appropriate: phone number
verification and SMS 2FA code paths.

This PR does the following: 
- Adds the ability to use Twilio Verify of standard messaging through
Twilio
- Adds support for international numbers and more reliable verification
messages sent from multiple numbers
- Adds a new Twilio configuration option to support Twilio Verify in the
admin console
- Sends verification SMS messages through Twilio Verify
- Implements Twilio Verification Checks for codes generated through the
same

# Additional Changes

# Additional Context
- base was implemented by @zhirschtritt in
https://github.com/zitadel/zitadel/pull/8268 ❤️
- closes https://github.com/zitadel/zitadel/issues/8581

---------

Co-authored-by: Zachary Hirschtritt <[email protected]>
Co-authored-by: Joey Biscoglia <[email protected]>
This commit is contained in:
Livio Spring
2024-09-26 09:14:33 +02:00
committed by GitHub
co-authored by Zachary Hirschtritt Joey Biscoglia
parent 4eaa3163b6
commit 14e2aba1bc
89 changed files with 3888 additions and 782 deletions
+3
View File
@@ -24,6 +24,7 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/static"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
webauthn_helper "github.com/zitadel/zitadel/internal/webauthn"
@@ -64,6 +65,7 @@ type Commands struct {
defaultAccessTokenLifetime time.Duration
defaultRefreshTokenLifetime time.Duration
defaultRefreshTokenIdleLifetime time.Duration
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
multifactors domain.MultifactorConfigs
webauthnConfig *webauthn_helper.Config
@@ -179,6 +181,7 @@ func StartCommands(
if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil {
repo.newHashedSecret = newHashedSecretWithDefault(secretHasher, defaultSecretGenerators.ClientSecret)
}
repo.phoneCodeVerifier = repo.phoneCodeVerifierFromConfig
return repo, nil
}
+23
View File
@@ -13,6 +13,8 @@ type encrypedCodeFunc func(ctx context.Context, filter preparation.FilterToQuery
type encryptedCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error)
type encryptedCodeGeneratorWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, string, error)
var emptyConfig = &crypto.GeneratorConfig{}
type EncryptedCode struct {
@@ -21,6 +23,27 @@ type EncryptedCode struct {
Expiry time.Duration
}
func (e *EncryptedCode) CryptedCode() *crypto.CryptoValue {
if e == nil {
return nil
}
return e.Crypted
}
func (e *EncryptedCode) PlainCode() string {
if e == nil {
return ""
}
return e.Plain
}
func (e *EncryptedCode) CodeExpiry() time.Duration {
if e == nil {
return 0
}
return e.Expiry
}
func newEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return newEncryptedCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig)
}
+21
View File
@@ -49,6 +49,27 @@ func mockEncryptedCodeWithDefault(code string, exp time.Duration) encryptedCodeW
}
}
func mockEncryptedCodeGeneratorWithDefault(code string, exp time.Duration) encryptedCodeGeneratorWithDefaultFunc {
return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, _ *crypto.GeneratorConfig) (*EncryptedCode, string, error) {
return &EncryptedCode{
Crypted: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte(code),
},
Plain: code,
Expiry: exp,
}, "", nil
}
}
func mockEncryptedCodeGeneratorWithDefaultExternal(id string) encryptedCodeGeneratorWithDefaultFunc {
return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, _ *crypto.GeneratorConfig) (*EncryptedCode, string, error) {
return nil, id, nil
}
}
func mockHashedSecret(secret string) hashedSecretFunc {
return func(_ context.Context, _ preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) {
return secret, secret, nil
+12 -2
View File
@@ -16,6 +16,16 @@ type Phone struct {
ReturnCode bool
}
func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg)
// newPhoneCode generates a new code to be sent out to via SMS or
// returns the ID of the external code provider (e.g. when using Twilio verification API)
func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, secretGeneratorType domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, string, error) {
externalID, err := c.activeSMSProvider(ctx)
if err != nil {
return nil, "", err
}
if externalID != "" {
return nil, externalID, nil
}
code, err := c.newEncryptedCodeWithDefault(ctx, filter, secretGeneratorType, alg, defaultConfig)
return code, "", err
}
+13 -9
View File
@@ -16,6 +16,7 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -31,13 +32,15 @@ type SessionCommands struct {
eventstore *eventstore.Eventstore
eventCommands []eventstore.Command
hasher *crypto.Hasher
intentAlg crypto.EncryptionAlgorithm
totpAlg crypto.EncryptionAlgorithm
otpAlg crypto.EncryptionAlgorithm
createCode encryptedCodeWithDefaultFunc
createToken func(sessionID string) (id string, token string, err error)
now func() time.Time
hasher *crypto.Hasher
intentAlg crypto.EncryptionAlgorithm
totpAlg crypto.EncryptionAlgorithm
otpAlg crypto.EncryptionAlgorithm
createCode encryptedCodeWithDefaultFunc
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
createToken func(sessionID string) (id string, token string, err error)
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
now func() time.Time
}
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
@@ -50,6 +53,7 @@ func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWri
totpAlg: c.multifactors.OTP.CryptoMFA,
otpAlg: c.userEncryption,
createCode: c.newEncryptedCodeWithDefault,
createPhoneCode: c.newPhoneCode,
createToken: c.sessionTokenCreator,
now: time.Now,
}
@@ -188,8 +192,8 @@ func (s *SessionCommands) TOTPChecked(ctx context.Context, checkedAt time.Time)
s.eventCommands = append(s.eventCommands, session.NewTOTPCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
}
func (s *SessionCommands) OTPSMSChallenged(ctx context.Context, code *crypto.CryptoValue, expiry time.Duration, returnCode bool) {
s.eventCommands = append(s.eventCommands, session.NewOTPSMSChallengedEvent(ctx, s.sessionWriteModel.aggregate, code, expiry, returnCode))
func (s *SessionCommands) OTPSMSChallenged(ctx context.Context, code *crypto.CryptoValue, expiry time.Duration, returnCode bool, generatorID string) {
s.eventCommands = append(s.eventCommands, session.NewOTPSMSChallengedEvent(ctx, s.sessionWriteModel.aggregate, code, expiry, returnCode, generatorID))
}
func (s *SessionCommands) OTPSMSChecked(ctx context.Context, checkedAt time.Time) {
+14 -3
View File
@@ -20,9 +20,11 @@ type WebAuthNChallengeModel struct {
}
type OTPCode struct {
Code *crypto.CryptoValue
Expiry time.Duration
CreationDate time.Time
Code *crypto.CryptoValue
Expiry time.Duration
CreationDate time.Time
GeneratorID string
VerificationID string
}
func (p *WebAuthNChallengeModel) WebAuthNLogin(human *domain.Human, credentialAssertionData []byte) *domain.WebAuthNLogin {
@@ -92,6 +94,8 @@ func (wm *SessionWriteModel) Reduce() error {
wm.reduceTOTPChecked(e)
case *session.OTPSMSChallengedEvent:
wm.reduceOTPSMSChallenged(e)
case *session.OTPSMSSentEvent:
wm.reduceOTPSMSSent(e)
case *session.OTPSMSCheckedEvent:
wm.reduceOTPSMSChecked(e)
case *session.OTPEmailChallengedEvent:
@@ -123,6 +127,7 @@ func (wm *SessionWriteModel) Query() *eventstore.SearchQueryBuilder {
session.WebAuthNCheckedType,
session.TOTPCheckedType,
session.OTPSMSChallengedType,
session.OTPSMSSentType,
session.OTPSMSCheckedType,
session.OTPEmailChallengedType,
session.OTPEmailCheckedType,
@@ -183,9 +188,15 @@ func (wm *SessionWriteModel) reduceOTPSMSChallenged(e *session.OTPSMSChallengedE
Code: e.Code,
Expiry: e.Expiry,
CreationDate: e.CreationDate(),
GeneratorID: e.GeneratorID,
}
}
func (wm *SessionWriteModel) reduceOTPSMSSent(e *session.OTPSMSSentEvent) {
wm.OTPSMSCodeChallenge.GeneratorID = e.GeneratorInfo.GetID()
wm.OTPSMSCodeChallenge.VerificationID = e.GeneratorInfo.GetVerificationID()
}
func (wm *SessionWriteModel) reduceOTPSMSChecked(e *session.OTPSMSCheckedEvent) {
wm.OTPSMSCodeChallenge = nil
wm.OTPSMSCheckedAt = e.CheckedAt
+32 -7
View File
@@ -8,6 +8,7 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -33,19 +34,19 @@ func (c *Commands) createOTPSMSChallenge(returnCode bool, dst *string) SessionCo
if !writeModel.OTPAdded() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-BJ2g3", "Errors.User.MFA.OTP.NotReady")
}
code, err := cmd.createCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPSMS, cmd.otpAlg, c.defaultSecretGenerators.OTPSMS)
code, generatorID, err := cmd.createPhoneCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPSMS, cmd.otpAlg, c.defaultSecretGenerators.OTPSMS) //nolint:staticcheck
if err != nil {
return nil, err
}
if returnCode {
*dst = code.Plain
}
cmd.OTPSMSChallenged(ctx, code.Crypted, code.Expiry, returnCode)
cmd.OTPSMSChallenged(ctx, code.CryptedCode(), code.CodeExpiry(), returnCode, generatorID)
return nil, nil
}
}
func (c *Commands) OTPSMSSent(ctx context.Context, sessionID, resourceOwner string) error {
func (c *Commands) OTPSMSSent(ctx context.Context, sessionID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) error {
sessionWriteModel := NewSessionWriteModel(sessionID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
if err != nil {
@@ -55,7 +56,7 @@ func (c *Commands) OTPSMSSent(ctx context.Context, sessionID, resourceOwner stri
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-G3t31", "Errors.User.Code.NotFound")
}
return c.pushAppendAndReduce(ctx, sessionWriteModel,
session.NewOTPSMSSentEvent(ctx, &session.NewAggregate(sessionID, sessionWriteModel.ResourceOwner).Aggregate),
session.NewOTPSMSSentEvent(ctx, &session.NewAggregate(sessionID, sessionWriteModel.ResourceOwner).Aggregate, generatorInfo),
)
}
@@ -86,7 +87,7 @@ func (c *Commands) createOTPEmailChallenge(returnCode bool, urlTmpl string, dst
if !writeModel.OTPAdded() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-JKLJ3", "Errors.User.MFA.OTP.NotReady")
}
code, err := cmd.createCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPEmail, cmd.otpAlg, c.defaultSecretGenerators.OTPEmail)
code, err := cmd.createCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPEmail, cmd.otpAlg, c.defaultSecretGenerators.OTPEmail) //nolint:staticcheck
if err != nil {
return nil, err
}
@@ -130,7 +131,19 @@ func CheckOTPSMS(code string) SessionCommand {
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPSMSCheckFailedEvent(ctx, aggregate, nil)
}
commands, err := checkOTP(ctx, cmd.sessionWriteModel.UserID, code, "", nil, writeModel, cmd.eventstore.FilterToQueryReducer, cmd.otpAlg, succeededEvent, failedEvent)
commands, err := checkOTP(
ctx,
cmd.sessionWriteModel.UserID,
code,
"",
nil,
writeModel,
cmd.eventstore.FilterToQueryReducer,
cmd.otpAlg,
cmd.getCodeVerifier,
succeededEvent,
failedEvent,
)
if err != nil {
return commands, err
}
@@ -158,7 +171,19 @@ func CheckOTPEmail(code string) SessionCommand {
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPEmailCheckFailedEvent(ctx, aggregate, nil)
}
commands, err := checkOTP(ctx, cmd.sessionWriteModel.UserID, code, "", nil, writeModel, cmd.eventstore.FilterToQueryReducer, cmd.otpAlg, succeededEvent, failedEvent)
commands, err := checkOTP(
ctx,
cmd.sessionWriteModel.UserID,
code,
"",
nil,
writeModel,
cmd.eventstore.FilterToQueryReducer,
cmd.otpAlg,
cmd.getCodeVerifier,
succeededEvent,
failedEvent,
)
if err != nil {
return commands, err
}
+73 -12
View File
@@ -11,6 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/repository/user"
@@ -19,9 +20,9 @@ import (
func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
type fields struct {
userID string
eventstore func(*testing.T) *eventstore.Eventstore
createCode encryptedCodeWithDefaultFunc
userID string
eventstore func(*testing.T) *eventstore.Eventstore
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
}
type res struct {
err error
@@ -66,7 +67,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
),
),
),
createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
createPhoneCode: mockEncryptedCodeGeneratorWithDefault("1234567", 5*time.Minute),
},
res: res{
returnCode: "1234567",
@@ -80,6 +81,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
},
5*time.Minute,
true,
"",
),
},
},
@@ -107,7 +109,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
sessionCommands: []SessionCommand{cmd},
sessionWriteModel: sessionModel,
eventstore: tt.fields.eventstore(t),
createCode: tt.fields.createCode,
createPhoneCode: tt.fields.createPhoneCode,
now: time.Now,
}
@@ -122,9 +124,9 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
type fields struct {
userID string
eventstore func(*testing.T) *eventstore.Eventstore
createCode encryptedCodeWithDefaultFunc
userID string
eventstore func(*testing.T) *eventstore.Eventstore
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
}
type res struct {
err error
@@ -168,7 +170,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
),
),
),
createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
createPhoneCode: mockEncryptedCodeGeneratorWithDefault("1234567", 5*time.Minute),
},
res: res{
commands: []eventstore.Command{
@@ -181,6 +183,31 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
},
5*time.Minute,
false,
"",
),
},
},
},
{
name: "generate code externally",
fields: fields{
userID: "userID",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanOTPSMSAddedEvent(context.Background(), &user.NewAggregate("userID", "org").Aggregate),
),
),
),
createPhoneCode: mockEncryptedCodeGeneratorWithDefaultExternal("generatorID"),
},
res: res{
commands: []eventstore.Command{
session.NewOTPSMSChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
nil,
0,
false,
"generatorID",
),
},
},
@@ -208,7 +235,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
sessionCommands: []SessionCommand{cmd},
sessionWriteModel: sessionModel,
eventstore: tt.fields.eventstore(t),
createCode: tt.fields.createCode,
createPhoneCode: tt.fields.createPhoneCode,
now: time.Now,
}
@@ -228,6 +255,7 @@ func TestCommands_OTPSMSSent(t *testing.T) {
ctx context.Context
sessionID string
resourceOwner string
generatorInfo *senders.CodeGeneratorInfo
}
tests := []struct {
name string
@@ -246,6 +274,7 @@ func TestCommands_OTPSMSSent(t *testing.T) {
ctx: context.Background(),
sessionID: "sessionID",
resourceOwner: "instanceID",
generatorInfo: &senders.CodeGeneratorInfo{},
},
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-G3t31", "Errors.User.Code.NotFound"),
},
@@ -264,11 +293,12 @@ func TestCommands_OTPSMSSent(t *testing.T) {
},
5*time.Minute,
false,
"",
),
),
),
expectPush(
session.NewOTPSMSSentEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate),
session.NewOTPSMSSentEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate, &senders.CodeGeneratorInfo{}),
),
),
},
@@ -276,6 +306,37 @@ func TestCommands_OTPSMSSent(t *testing.T) {
ctx: context.Background(),
sessionID: "sessionID",
resourceOwner: "instanceID",
generatorInfo: &senders.CodeGeneratorInfo{},
},
wantErr: nil,
},
{
name: "challenged and sent (externally)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
session.NewOTPSMSChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
nil,
0,
false,
"",
),
),
),
expectPush(
session.NewOTPSMSSentEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate, &senders.CodeGeneratorInfo{ID: "generatorID", VerificationID: "verificationID"}),
),
),
},
args: args{
ctx: context.Background(),
sessionID: "sessionID",
resourceOwner: "instanceID",
generatorInfo: &senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
},
wantErr: nil,
},
@@ -285,7 +346,7 @@ func TestCommands_OTPSMSSent(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
err := c.OTPSMSSent(tt.args.ctx, tt.args.sessionID, tt.args.resourceOwner)
err := c.OTPSMSSent(tt.args.ctx, tt.args.sessionID, tt.args.resourceOwner, tt.args.generatorInfo)
assert.ErrorIs(t, err, tt.wantErr)
})
}
+24 -9
View File
@@ -14,10 +14,11 @@ type AddTwilioConfig struct {
ResourceOwner string
ID string
Description string
SID string
Token string
SenderNumber string
Description string
SID string
Token string
SenderNumber string
VerifyServiceSID string
}
func (c *Commands) AddSMSConfigTwilio(ctx context.Context, config *AddTwilioConfig) (err error) {
@@ -52,6 +53,7 @@ func (c *Commands) AddSMSConfigTwilio(ctx context.Context, config *AddTwilioConf
config.SID,
config.SenderNumber,
token,
config.VerifyServiceSID,
),
)
if err != nil {
@@ -66,10 +68,11 @@ type ChangeTwilioConfig struct {
ResourceOwner string
ID string
Description *string
SID *string
Token *string
SenderNumber *string
Description *string
SID *string
Token *string
SenderNumber *string
VerifyServiceSID *string
}
func (c *Commands) ChangeSMSConfigTwilio(ctx context.Context, config *ChangeTwilioConfig) (err error) {
@@ -92,7 +95,9 @@ func (c *Commands) ChangeSMSConfigTwilio(ctx context.Context, config *ChangeTwil
config.ID,
config.Description,
config.SID,
config.SenderNumber)
config.SenderNumber,
config.VerifyServiceSID,
)
if err != nil {
return err
}
@@ -330,3 +335,13 @@ func (c *Commands) getSMSConfig(ctx context.Context, instanceID, id string) (_ *
}
return writeModel, nil
}
// getActiveSMSConfig returns the last activated SMS configuration
func (c *Commands) getActiveSMSConfig(ctx context.Context, instanceID string) (_ *IAMSMSConfigWriteModel, err error) {
writeModel := NewIAMSMSLastActivatedConfigWriteModel(instanceID)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return c.getSMSConfig(ctx, instanceID, writeModel.activeID)
}
+59 -7
View File
@@ -20,9 +20,10 @@ type IAMSMSConfigWriteModel struct {
}
type TwilioConfig struct {
SID string
Token *crypto.CryptoValue
SenderNumber string
SID string
Token *crypto.CryptoValue
SenderNumber string
VerifyServiceSID string
}
type HTTPConfig struct {
@@ -48,9 +49,10 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
continue
}
wm.Twilio = &TwilioConfig{
SID: e.SID,
Token: e.Token,
SenderNumber: e.SenderNumber,
SID: e.SID,
Token: e.Token,
SenderNumber: e.SenderNumber,
VerifyServiceSID: e.VerifyServiceSID,
}
wm.Description = e.Description
wm.State = domain.SMSConfigStateInactive
@@ -67,6 +69,9 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
if e.SenderNumber != nil {
wm.Twilio.SenderNumber = *e.SenderNumber
}
if e.VerifyServiceSID != nil {
wm.Twilio.VerifyServiceSID = *e.VerifyServiceSID
}
case *instance.SMSConfigTwilioTokenChangedEvent:
if wm.ID != e.ID {
continue
@@ -131,6 +136,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
}
return wm.WriteModel.Reduce()
}
func (wm *IAMSMSConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
@@ -152,7 +158,7 @@ func (wm *IAMSMSConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
Builder()
}
func (wm *IAMSMSConfigWriteModel) NewTwilioChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id string, description, sid, senderNumber *string) (*instance.SMSConfigTwilioChangedEvent, bool, error) {
func (wm *IAMSMSConfigWriteModel) NewTwilioChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id string, description, sid, senderNumber, verifyServiceSID *string) (*instance.SMSConfigTwilioChangedEvent, bool, error) {
changes := make([]instance.SMSConfigTwilioChanges, 0)
var err error
@@ -169,6 +175,9 @@ func (wm *IAMSMSConfigWriteModel) NewTwilioChangedEvent(ctx context.Context, agg
if senderNumber != nil && wm.Twilio.SenderNumber != *senderNumber {
changes = append(changes, instance.ChangeSMSConfigTwilioSenderNumber(*senderNumber))
}
if verifyServiceSID != nil && wm.Twilio.VerifyServiceSID != *verifyServiceSID {
changes = append(changes, instance.ChangeSMSConfigTwilioVerifyServiceSID(*verifyServiceSID))
}
if len(changes) == 0 {
return nil, false, nil
@@ -204,3 +213,46 @@ func (wm *IAMSMSConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggre
}
return changeEvent, true, nil
}
type IAMSMSLastActivatedConfigWriteModel struct {
eventstore.WriteModel
activeID string
}
func NewIAMSMSLastActivatedConfigWriteModel(instanceID string) *IAMSMSLastActivatedConfigWriteModel {
return &IAMSMSLastActivatedConfigWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: instanceID,
ResourceOwner: instanceID,
InstanceID: instanceID,
},
}
}
func (wm *IAMSMSLastActivatedConfigWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *instance.SMSConfigActivatedEvent:
wm.activeID = e.ID
case *instance.SMSConfigTwilioActivatedEvent:
wm.activeID = e.ID
}
}
return wm.WriteModel.Reduce()
}
func (wm *IAMSMSLastActivatedConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
OrderDesc().
Limit(1).
AddQuery().
AggregateTypes(instance.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
instance.SMSConfigActivatedEventType,
instance.SMSConfigTwilioActivatedEventType,
).
Builder()
}
+30 -17
View File
@@ -72,6 +72,7 @@ func TestCommandSide_AddSMSConfigTwilio(t *testing.T) {
KeyID: "id",
Crypted: []byte("token"),
},
"",
),
),
),
@@ -81,11 +82,12 @@ func TestCommandSide_AddSMSConfigTwilio(t *testing.T) {
args: args{
ctx: context.Background(),
sms: &AddTwilioConfig{
ResourceOwner: "INSTANCE",
Description: "description",
SID: "sid",
Token: "token",
SenderNumber: "senderName",
ResourceOwner: "INSTANCE",
Description: "description",
SID: "sid",
Token: "token",
SenderNumber: "senderName",
VerifyServiceSID: "",
},
},
res: res{
@@ -206,6 +208,7 @@ func TestCommandSide_ChangeSMSConfigTwilio(t *testing.T) {
KeyID: "id",
Crypted: []byte("token"),
},
"",
),
),
),
@@ -214,11 +217,12 @@ func TestCommandSide_ChangeSMSConfigTwilio(t *testing.T) {
args: args{
ctx: context.Background(),
sms: &ChangeTwilioConfig{
ResourceOwner: "INSTANCE",
ID: "providerid",
SID: gu.Ptr("sid"),
Token: gu.Ptr("token"),
SenderNumber: gu.Ptr("senderName"),
ResourceOwner: "INSTANCE",
ID: "providerid",
SID: gu.Ptr("sid"),
Token: gu.Ptr("token"),
SenderNumber: gu.Ptr("senderName"),
VerifyServiceSID: gu.Ptr(""),
},
},
res: res{
@@ -246,6 +250,7 @@ func TestCommandSide_ChangeSMSConfigTwilio(t *testing.T) {
KeyID: "id",
Crypted: []byte("token"),
},
"verifyServiceSid",
),
),
),
@@ -256,6 +261,7 @@ func TestCommandSide_ChangeSMSConfigTwilio(t *testing.T) {
"sid2",
"senderName2",
"description2",
"verifyServiceSid2",
),
),
),
@@ -263,12 +269,13 @@ func TestCommandSide_ChangeSMSConfigTwilio(t *testing.T) {
args: args{
ctx: context.Background(),
sms: &ChangeTwilioConfig{
ResourceOwner: "INSTANCE",
ID: "providerid",
Description: gu.Ptr("description2"),
SID: gu.Ptr("sid2"),
Token: gu.Ptr("token2"),
SenderNumber: gu.Ptr("senderName2"),
ResourceOwner: "INSTANCE",
ID: "providerid",
Description: gu.Ptr("description2"),
SID: gu.Ptr("sid2"),
Token: gu.Ptr("token2"),
SenderNumber: gu.Ptr("senderName2"),
VerifyServiceSID: gu.Ptr("verifyServiceSid2"),
},
},
res: res{
@@ -626,6 +633,7 @@ func TestCommandSide_ActivateSMSConfig(t *testing.T) {
"sid",
"sender-name",
&crypto.CryptoValue{},
"",
),
),
eventFromEventPusher(
@@ -663,6 +671,7 @@ func TestCommandSide_ActivateSMSConfig(t *testing.T) {
"sid",
"sender-name",
&crypto.CryptoValue{},
"",
),
),
),
@@ -820,6 +829,7 @@ func TestCommandSide_DeactivateSMSConfig(t *testing.T) {
"sid",
"sender-name",
&crypto.CryptoValue{},
"",
),
),
eventFromEventPusher(
@@ -864,6 +874,7 @@ func TestCommandSide_DeactivateSMSConfig(t *testing.T) {
"sid",
"sender-name",
&crypto.CryptoValue{},
"",
),
),
eventFromEventPusher(
@@ -1036,6 +1047,7 @@ func TestCommandSide_RemoveSMSConfig(t *testing.T) {
"sid",
"sender-name",
&crypto.CryptoValue{},
"",
),
),
),
@@ -1114,11 +1126,12 @@ func TestCommandSide_RemoveSMSConfig(t *testing.T) {
}
}
func newSMSConfigTwilioChangedEvent(ctx context.Context, id, sid, senderName, description string) *instance.SMSConfigTwilioChangedEvent {
func newSMSConfigTwilioChangedEvent(ctx context.Context, id, sid, senderName, description, verifyServiceSid string) *instance.SMSConfigTwilioChangedEvent {
changes := []instance.SMSConfigTwilioChanges{
instance.ChangeSMSConfigTwilioSID(sid),
instance.ChangeSMSConfigTwilioSenderNumber(senderName),
instance.ChangeSMSConfigTwilioDescription(description),
instance.ChangeSMSConfigTwilioVerifyServiceSID(verifyServiceSid),
}
event, _ := instance.NewSMSConfigTwilioChangedEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate,
+38 -4
View File
@@ -3,6 +3,7 @@ package command
import (
"context"
"strings"
"time"
"golang.org/x/text/language"
@@ -11,6 +12,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -315,14 +317,14 @@ func (c *Commands) addHumanCommandPhone(ctx context.Context, filter preparation.
if human.Phone.Verified {
return append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &a.Aggregate)), nil
}
phoneCode, err := c.newPhoneCode(ctx, filter, codeAlg)
phoneCode, generatorID, err := c.newPhoneCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, codeAlg, c.defaultSecretGenerators.PhoneVerificationCode)
if err != nil {
return nil, err
}
if human.Phone.ReturnCode {
human.PhoneCode = &phoneCode.Plain
}
return append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &a.Aggregate, phoneCode.Crypted, phoneCode.Expiry, human.Phone.ReturnCode)), nil
return append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &a.Aggregate, phoneCode.CryptedCode(), phoneCode.CodeExpiry(), human.Phone.ReturnCode, generatorID)), nil
}
// Deprecated: use commands.NewUserHumanWriteModel, to remove deprecated eventstore.Filter
@@ -561,11 +563,11 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
}
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(phoneCodeGenerator)
phoneCode, generatorID, err := c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
if err != nil {
return nil, nil, err
}
events = append(events, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.Code, phoneCode.Expiry))
events = append(events, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.CryptedCode(), phoneCode.CodeExpiry(), generatorID))
} else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
events = append(events, user.NewHumanPhoneVerifiedEvent(ctx, userAgg))
}
@@ -733,3 +735,35 @@ func AddHumanFromDomain(user *domain.Human, metadataList []*domain.Metadata, aut
}
return human
}
func verifyCode(
ctx context.Context,
codeCreationDate time.Time,
codeExpiry time.Duration,
encryptedCode *crypto.CryptoValue,
codeProviderID string,
codeVerificationID string,
code string,
codeAlg crypto.EncryptionAlgorithm,
getCodeVerifier func(ctx context.Context, id string) (_ senders.CodeGenerator, err error),
) (err error) {
if codeProviderID == "" {
if encryptedCode == nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
}
_, spanCrypto := tracing.NewNamedSpan(ctx, "crypto.VerifyCode")
defer func() {
spanCrypto.EndWithError(err)
}()
return crypto.VerifyCode(codeCreationDate, codeExpiry, encryptedCode, code, codeAlg)
}
if getCodeVerifier == nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-M0g95", "Errors.User.Code.NotConfigured")
}
verifier, err := getCodeVerifier(ctx, codeProviderID)
if err != nil {
return err
}
return verifier.VerifyCode(codeVerificationID, code)
}
+32 -16
View File
@@ -9,9 +9,11 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
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"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -335,8 +337,8 @@ func (c *Commands) HumanSendOTPSMS(ctx context.Context, userID, resourceOwner st
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
}
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPSMSCodeAddedEvent(ctx, aggregate, code, expiry, info)
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo, generatorID string) eventstore.Command {
return user.NewHumanOTPSMSCodeAddedEvent(ctx, aggregate, code, expiry, info, generatorID)
}
return c.sendHumanOTP(
ctx,
@@ -347,15 +349,16 @@ func (c *Commands) HumanSendOTPSMS(ctx context.Context, userID, resourceOwner st
domain.SecretGeneratorTypeOTPSMS,
c.defaultSecretGenerators.OTPSMS,
codeAddedEvent,
c.newPhoneCode,
)
}
func (c *Commands) HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
func (c *Commands) HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) (err error) {
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
}
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
return user.NewHumanOTPSMSCodeSentEvent(ctx, aggregate)
return user.NewHumanOTPSMSCodeSentEvent(ctx, aggregate, generatorInfo)
}
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
}
@@ -379,6 +382,7 @@ func (c *Commands) HumanCheckOTPSMS(ctx context.Context, userID, code, resourceO
writeModel,
c.eventstore.FilterToQueryReducer,
c.userEncryption,
c.phoneCodeVerifier,
succeededEvent,
failedEvent,
)
@@ -467,9 +471,13 @@ func (c *Commands) HumanSendOTPEmail(ctx context.Context, userID, resourceOwner
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
}
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo, _ string) eventstore.Command {
return user.NewHumanOTPEmailCodeAddedEvent(ctx, aggregate, code, expiry, info)
}
generateCode := func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, string, error) {
code, err := c.newEncryptedCodeWithDefault(ctx, filter, typ, alg, defaultConfig)
return code, "", err
}
return c.sendHumanOTP(
ctx,
userID,
@@ -479,6 +487,7 @@ func (c *Commands) HumanSendOTPEmail(ctx context.Context, userID, resourceOwner
domain.SecretGeneratorTypeOTPEmail,
c.defaultSecretGenerators.OTPEmail,
codeAddedEvent,
generateCode,
)
}
@@ -511,6 +520,7 @@ func (c *Commands) HumanCheckOTPEmail(ctx context.Context, userID, code, resourc
writeModel,
c.eventstore.FilterToQueryReducer,
c.userEncryption,
nil, // email currently always uses local code checks
succeededEvent,
failedEvent,
)
@@ -529,7 +539,8 @@ func (c *Commands) sendHumanOTP(
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
secretGeneratorType domain.SecretGeneratorType,
defaultSecretGenerator *crypto.GeneratorConfig,
codeAddedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command,
codeAddedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo, generatorID string) eventstore.Command,
generateCode func(ctx context.Context, filter preparation.FilterToQueryReducer, secretGeneratorType domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, string, error),
) (err error) {
if userID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-S3SF1", "Errors.User.UserIDMissing")
@@ -541,17 +552,12 @@ func (c *Commands) sendHumanOTP(
if !existingOTP.OTPAdded() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady")
}
config, err := cryptoGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) //nolint:staticcheck
if err != nil {
return err
}
gen := crypto.NewEncryptionGenerator(*config, c.userEncryption)
value, _, err := crypto.NewCode(gen)
code, generatorID, err := generateCode(ctx, c.eventstore.Filter, secretGeneratorType, c.userEncryption, defaultSecretGenerator) //nolint:staticcheck
if err != nil {
return err
}
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
_, err = c.eventstore.Push(ctx, codeAddedEvent(ctx, userAgg, value, gen.Expiry(), authRequestDomainToAuthRequestInfo(authRequest)))
_, err = c.eventstore.Push(ctx, codeAddedEvent(ctx, userAgg, code.CryptedCode(), code.CodeExpiry(), authRequestDomainToAuthRequestInfo(authRequest), generatorID))
return err
}
@@ -583,6 +589,7 @@ func checkOTP(
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error),
queryReducer func(ctx context.Context, r eventstore.QueryReducer) error,
alg crypto.EncryptionAlgorithm,
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error),
checkSucceededEvent, checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
) ([]eventstore.Command, error) {
if userID == "" {
@@ -598,12 +605,21 @@ func checkOTP(
if !existingOTP.OTPAdded() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-d2r52", "Errors.User.MFA.OTP.NotReady")
}
if existingOTP.Code() == nil {
if existingOTP.Code() == nil && existingOTP.GeneratorID() == "" {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
}
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
verifyErr := crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, alg)
verifyErr := verifyCode(
ctx,
existingOTP.CodeCreationDate(),
existingOTP.CodeExpiry(),
existingOTP.Code(),
existingOTP.GeneratorID(),
existingOTP.ProviderVerificationID(),
code,
alg,
getCodeVerifier,
)
// recheck for additional events (failed OTP checks or locks)
recheckErr := queryReducer(ctx, existingOTP)
if recheckErr != nil {
+35
View File
@@ -90,6 +90,8 @@ type OTPCodeWriteModel interface {
Code() *crypto.CryptoValue
CheckFailedCount() uint64
UserLocked() bool
GeneratorID() string
ProviderVerificationID() string
eventstore.QueryReducer
}
@@ -192,6 +194,20 @@ func (wm *HumanOTPSMSCodeWriteModel) UserLocked() bool {
return wm.userLocked
}
func (wm *HumanOTPSMSCodeWriteModel) GeneratorID() string {
if wm.otpCode == nil {
return ""
}
return wm.otpCode.GeneratorID
}
func (wm *HumanOTPSMSCodeWriteModel) ProviderVerificationID() string {
if wm.otpCode == nil {
return ""
}
return wm.otpCode.VerificationID
}
func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCodeWriteModel {
return &HumanOTPSMSCodeWriteModel{
HumanOTPSMSWriteModel: NewHumanOTPSMSWriteModel(userID, resourceOwner),
@@ -206,7 +222,11 @@ func (wm *HumanOTPSMSCodeWriteModel) Reduce() error {
Code: e.Code,
CreationDate: e.CreationDate(),
Expiry: e.Expiry,
GeneratorID: e.GeneratorID,
}
case *user.HumanOTPSMSCodeSentEvent:
wm.otpCode.GeneratorID = e.GeneratorInfo.GetID()
wm.otpCode.VerificationID = e.GeneratorInfo.GetVerificationID()
case *user.HumanOTPSMSCheckSucceededEvent:
wm.checkFailedCount = 0
case *user.HumanOTPSMSCheckFailedEvent:
@@ -228,6 +248,7 @@ func (wm *HumanOTPSMSCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
AggregateIDs(wm.AggregateID).
EventTypes(
user.HumanOTPSMSCodeAddedType,
user.HumanOTPSMSCodeSentType,
user.HumanOTPSMSCheckSucceededType,
user.HumanOTPSMSCheckFailedType,
user.UserLockedType,
@@ -344,6 +365,20 @@ func (wm *HumanOTPEmailCodeWriteModel) UserLocked() bool {
return wm.userLocked
}
func (wm *HumanOTPEmailCodeWriteModel) GeneratorID() string {
if wm.otpCode == nil {
return ""
}
return wm.otpCode.GeneratorID
}
func (wm *HumanOTPEmailCodeWriteModel) ProviderVerificationID() string {
if wm.otpCode == nil {
return ""
}
return wm.otpCode.VerificationID
}
func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmailCodeWriteModel {
return &HumanOTPEmailCodeWriteModel{
HumanOTPEmailWriteModel: NewHumanOTPEmailWriteModel(userID, resourceOwner),
+306 -112
View File
@@ -18,6 +18,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/notification/senders"
"github.com/zitadel/zitadel/internal/notification/senders/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user"
@@ -1439,9 +1441,9 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
},
}
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
defaultSecretGenerators *SecretGenerators
eventstore func(*testing.T) *eventstore.Eventstore
defaultSecretGenerators *SecretGenerators
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
}
type (
args struct {
@@ -1506,16 +1508,33 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
domain.SecretGeneratorTypeOTPSMS,
8,
time.Hour,
true,
true,
true,
true,
)),
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanOTPSMSCodeAddedEvent(ctx,
@@ -1528,11 +1547,77 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
},
time.Hour,
nil,
"",
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
defaultSecretGenerators: defaultGenerators,
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
},
args: args{
ctx: ctx,
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "successful add (external code)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanOTPSMSAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSID",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanOTPSMSCodeAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
nil,
"id",
),
),
),
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
},
args: args{
ctx: ctx,
@@ -1556,7 +1641,36 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
),
),
),
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanOTPSMSCodeAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
@@ -1568,11 +1682,12 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
},
time.Hour,
nil,
"",
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
defaultSecretGenerators: defaultGenerators,
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
},
args: args{
ctx: ctx,
@@ -1598,16 +1713,33 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
domain.SecretGeneratorTypeOTPSMS,
8,
time.Hour,
true,
true,
true,
true,
)),
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanOTPSMSCodeAddedEvent(ctx,
@@ -1628,11 +1760,12 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
RemoteIP: net.IP{192, 0, 2, 1},
},
},
"",
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
defaultSecretGenerators: defaultGenerators,
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
},
args: args{
ctx: ctx,
@@ -1658,9 +1791,9 @@ func TestCommandSide_HumanSendOTPSMS(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
eventstore: tt.fields.eventstore(t),
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
}
err := r.HumanSendOTPSMS(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.authRequest)
assert.ErrorIs(t, err, tt.res.err)
@@ -1678,6 +1811,7 @@ func TestCommandSide_HumanOTPSMSCodeSent(t *testing.T) {
ctx context.Context
userID string
resourceOwner string
generatorInfo *senders.CodeGeneratorInfo
}
)
type res struct {
@@ -1734,6 +1868,7 @@ func TestCommandSide_HumanOTPSMSCodeSent(t *testing.T) {
expectPush(
user.NewHumanOTPSMSCodeSentEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{},
),
),
),
@@ -1742,6 +1877,44 @@ func TestCommandSide_HumanOTPSMSCodeSent(t *testing.T) {
ctx: ctx,
userID: "user1",
resourceOwner: "org1",
generatorInfo: &senders.CodeGeneratorInfo{},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "successful add (external code)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanOTPSMSAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
),
),
),
expectPush(
user.NewHumanOTPSMSCodeSentEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
),
),
),
},
args: args{
ctx: ctx,
userID: "user1",
resourceOwner: "org1",
generatorInfo: &senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
},
res: res{
want: &domain.ObjectDetails{
@@ -1755,7 +1928,7 @@ func TestCommandSide_HumanOTPSMSCodeSent(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
}
err := r.HumanOTPSMSCodeSent(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
err := r.HumanOTPSMSCodeSent(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.generatorInfo)
assert.ErrorIs(t, err, tt.res.err)
})
}
@@ -1764,8 +1937,9 @@ func TestCommandSide_HumanOTPSMSCodeSent(t *testing.T) {
func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
ctx := authz.NewMockContext("inst1", "org1", "user1")
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
}
type (
args struct {
@@ -1885,6 +2059,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
RemoteIP: net.IP{192, 0, 2, 1},
},
},
"",
),
),
),
@@ -1962,6 +2137,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
RemoteIP: net.IP{192, 0, 2, 1},
},
},
"",
),
),
),
@@ -2042,6 +2218,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
RemoteIP: net.IP{192, 0, 2, 1},
},
},
"",
),
),
),
@@ -2113,6 +2290,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
RemoteIP: net.IP{192, 0, 2, 1},
},
},
"",
),
),
),
@@ -2143,12 +2321,94 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"),
},
},
{
name: "code ok (external)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanOTPSMSAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanOTPSMSCodeAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
&user.AuthRequestInfo{
ID: "authRequestID",
UserAgentID: "userAgentID",
BrowserInfo: &user.BrowserInfo{
UserAgent: "user-agent",
AcceptLanguage: "en",
RemoteIP: net.IP{192, 0, 2, 1},
},
},
"id",
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanOTPSMSCodeSentEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "id",
VerificationID: "verificationID",
},
),
),
),
expectFilter(), // recheck
expectPush(
user.NewHumanOTPSMSCheckSucceededEvent(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},
},
},
),
),
),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
phoneCodeVerifier: func(ctx context.Context, id string) (senders.CodeGenerator, error) {
sender := mock.NewMockCodeGenerator(gomock.NewController(t))
sender.EXPECT().VerifyCode("verificationID", "code").Return(nil)
return sender, nil
},
},
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{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
}
err := r.HumanCheckOTPSMS(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.authRequest)
assert.ErrorIs(t, err, tt.res.err)
@@ -2594,9 +2854,9 @@ func TestCommandSide_HumanSendOTPEmail(t *testing.T) {
},
}
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
defaultSecretGenerators *SecretGenerators
eventstore func(*testing.T) *eventstore.Eventstore
defaultSecretGenerators *SecretGenerators
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
}
type (
args struct {
@@ -2659,19 +2919,6 @@ func TestCommandSide_HumanSendOTPEmail(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
domain.SecretGeneratorTypeOTPEmail,
8,
time.Hour,
true,
true,
true,
true,
)),
),
expectPush(
user.NewHumanOTPEmailCodeAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
@@ -2686,48 +2933,8 @@ func TestCommandSide_HumanSendOTPEmail(t *testing.T) {
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: ctx,
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "successful add (without secret config)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanOTPEmailAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
),
),
),
expectFilter(),
expectPush(
user.NewHumanOTPEmailCodeAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
time.Hour,
nil,
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: ctx,
@@ -2751,19 +2958,6 @@ func TestCommandSide_HumanSendOTPEmail(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
domain.SecretGeneratorTypeOTPEmail,
8,
time.Hour,
true,
true,
true,
true,
)),
),
expectPush(
user.NewHumanOTPEmailCodeAddedEvent(ctx,
&user.NewAggregate("user1", "org1").Aggregate,
@@ -2786,8 +2980,8 @@ func TestCommandSide_HumanSendOTPEmail(t *testing.T) {
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: ctx,
@@ -2813,9 +3007,9 @@ func TestCommandSide_HumanSendOTPEmail(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
eventstore: tt.fields.eventstore(t),
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
}
err := r.HumanSendOTPEmail(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.authRequest)
assert.ErrorIs(t, err, tt.res.err)
+32 -13
View File
@@ -11,6 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -67,7 +68,14 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID,
"",
userAgentID,
changeRequired,
c.setPasswordWithVerifyCode(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code),
c.setPasswordWithVerifyCode(
wm.CodeCreationDate,
wm.CodeExpiry,
wm.Code,
wm.GeneratorID,
wm.VerificationID,
code,
),
)
}
@@ -111,17 +119,22 @@ func (c *Commands) setPasswordWithVerifyCode(
passwordCodeCreationDate time.Time,
passwordCodeExpiry time.Duration,
passwordCode *crypto.CryptoValue,
passwordCodeProviderID string,
passwordCodeVerificationID string,
code string,
) setPasswordVerification {
return func(ctx context.Context) (_ string, err error) {
if passwordCode == nil {
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
}
_, spanCrypto := tracing.NewNamedSpan(ctx, "crypto.VerifyCode")
defer func() {
spanCrypto.EndWithError(err)
}()
return "", crypto.VerifyCode(passwordCodeCreationDate, passwordCodeExpiry, passwordCode, code, c.userEncryption)
return "", verifyCode(
ctx,
passwordCodeCreationDate,
passwordCodeExpiry,
passwordCode,
passwordCodeProviderID,
passwordCodeVerificationID,
code,
c.userEncryption,
c.phoneCodeVerifier, // password code can only be custom generated by SMS
)
}
}
@@ -253,11 +266,17 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.NotInitialised")
}
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)
passwordCode, err := c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck
var passwordCode *EncryptedCode
var generatorID string
if notifyType == domain.NotificationTypeSms {
passwordCode, generatorID, err = c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption, c.defaultSecretGenerators.PasswordVerificationCode) //nolint:staticcheck
} else {
passwordCode, err = c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck
}
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPasswordCodeAddedEvent(ctx, userAgg, passwordCode.Crypted, passwordCode.Expiry, notifyType, authRequestID))
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPasswordCodeAddedEvent(ctx, userAgg, passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notifyType, authRequestID, generatorID))
if err != nil {
return nil, err
}
@@ -269,7 +288,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
}
// PasswordCodeSent notification send with code to change password
func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (err error) {
func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string, generatorInfo *senders.CodeGeneratorInfo) (err error) {
if userID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-meEfe", "Errors.User.UserIDMissing")
}
@@ -282,7 +301,7 @@ func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
_, err = c.eventstore.Push(ctx, user.NewHumanPasswordCodeSentEvent(ctx, userAgg))
_, err = c.eventstore.Push(ctx, user.NewHumanPasswordCodeSentEvent(ctx, userAgg, generatorInfo))
return err
}
@@ -19,6 +19,8 @@ type HumanPasswordWriteModel struct {
CodeCreationDate time.Time
CodeExpiry time.Duration
PasswordCheckFailedCount uint64
GeneratorID string
VerificationID string
UserState domain.UserState
}
@@ -56,6 +58,10 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry
wm.GeneratorID = e.GeneratorID
case *user.HumanPasswordCodeSentEvent:
wm.GeneratorID = e.GeneratorInfo.GetID()
wm.VerificationID = e.GeneratorInfo.GetVerificationID()
case *user.HumanEmailVerifiedEvent:
if wm.UserState == domain.UserStateInitial {
wm.UserState = domain.UserStateActive
@@ -91,6 +97,7 @@ func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder {
user.HumanInitializedCheckSucceededType,
user.HumanPasswordChangedType,
user.HumanPasswordCodeAddedType,
user.HumanPasswordCodeSentType,
user.HumanEmailVerifiedType,
user.HumanPasswordCheckFailedType,
user.HumanPasswordCheckSucceededType,
@@ -104,6 +111,7 @@ func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder {
user.UserV1InitializedCheckSucceededType,
user.UserV1PasswordChangedType,
user.UserV1PasswordCodeAddedType,
user.UserV1PasswordCodeSentType,
user.UserV1EmailVerifiedType,
user.UserV1PasswordCheckFailedType,
user.UserV1PasswordCheckSucceededType,
+148 -1
View File
@@ -15,6 +15,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/notification/senders"
"github.com/zitadel/zitadel/internal/notification/senders/mock"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -265,6 +267,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
userPasswordHasher *crypto.Hasher
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
}
type args struct {
ctx context.Context
@@ -393,6 +396,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
time.Hour*1,
domain.NotificationTypeEmail,
"",
"",
),
),
),
@@ -446,6 +450,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
time.Hour*1,
domain.NotificationTypeEmail,
"",
"",
),
),
),
@@ -522,6 +527,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
time.Hour*1,
domain.NotificationTypeEmail,
"",
"",
),
),
),
@@ -599,6 +605,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
time.Hour*1,
domain.NotificationTypeEmail,
"",
"",
),
),
),
@@ -641,6 +648,92 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
},
},
},
{
name: "set password (external code), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
domain.NotificationTypeSms,
"",
"id",
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "id",
VerificationID: "verificationID",
},
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
false,
"",
),
),
),
userPasswordHasher: mockPasswordHasher("x"),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
phoneCodeVerifier: func(ctx context.Context, id string) (senders.CodeGenerator, error) {
sender := mock.NewMockCodeGenerator(gomock.NewController(t))
sender.EXPECT().VerifyCode("verificationID", "a").Return(nil)
return sender, nil
},
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
password: "password",
code: "a",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -648,6 +741,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.userEncryption,
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
}
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.userAgentID, tt.args.changeRequired)
if tt.res.err == nil {
@@ -1248,6 +1342,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
time.Hour*1,
domain.NotificationTypeEmail,
"",
"",
),
),
),
@@ -1304,6 +1399,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
time.Hour*1,
domain.NotificationTypeEmail,
"authRequestID",
"",
),
),
),
@@ -1350,6 +1446,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
ctx context.Context
userID string
resourceOwner string
generatorInfo *senders.CodeGeneratorInfo
}
type res struct {
err func(error) bool
@@ -1418,6 +1515,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
expectPush(
user.NewHumanPasswordCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{},
),
),
),
@@ -1426,6 +1524,55 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
generatorInfo: &senders.CodeGeneratorInfo{},
},
res: res{},
},
{
name: "code sent (external code), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
),
expectPush(
user.NewHumanPasswordCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
generatorInfo: &senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
},
res: res{},
},
@@ -1435,7 +1582,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
}
err := r.PasswordCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
err := r.PasswordCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.generatorInfo)
if tt.res.err == nil {
assert.NoError(t, err)
}
+59 -13
View File
@@ -5,9 +5,12 @@ import (
"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/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -40,11 +43,11 @@ func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone, re
if phone.IsPhoneVerified {
events = append(events, user.NewHumanPhoneVerifiedEvent(ctx, userAgg))
} else {
phoneCode, err := domain.NewPhoneCode(phoneCodeGenerator)
phoneCode, generatorID, err := c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
if err != nil {
return nil, err
}
events = append(events, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.Code, phoneCode.Expiry))
events = append(events, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.CryptedCode(), phoneCode.CodeExpiry(), generatorID))
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
@@ -74,12 +77,22 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
if !existingCode.UserState.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Rsj8c", "Errors.User.NotFound")
}
if !existingCode.State.Exists() || existingCode.Code == nil {
if !existingCode.State.Exists() || (existingCode.Code == nil && existingCode.GeneratorID == "") {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Rsj8c", "Errors.User.Code.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator.Alg())
err = verifyCode(
ctx,
existingCode.CodeCreationDate,
existingCode.CodeExpiry,
existingCode.Code,
existingCode.GeneratorID,
existingCode.VerificationID,
code,
phoneCodeGenerator.Alg(),
c.phoneCodeVerifier,
)
if err == nil {
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg))
if err != nil {
@@ -92,10 +105,47 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
return writeModelToObjectDetails(&existingCode.WriteModel), nil
}
_, err = c.eventstore.Push(ctx, user.NewHumanPhoneVerificationFailedEvent(ctx, userAgg))
logging.LogWithFields("COMMAND-5M9ds", "userID", userAgg.ID).OnError(err).Error("NewHumanPhoneVerificationFailedEvent push failed")
logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanPhoneVerificationFailedEvent push failed")
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid")
}
func (c *Commands) phoneCodeVerifierFromConfig(ctx context.Context, id string) (senders.CodeGenerator, error) {
config, err := c.getSMSConfig(ctx, authz.GetInstance(ctx).InstanceID(), id)
if err != nil {
return nil, err
}
if config.State != domain.SMSConfigStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-M0odsf", "Errors.SMSConfig.NotFound")
}
if config.Twilio != nil {
if config.Twilio.VerifyServiceSID == "" {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sgb4h", "Errors.SMSConfig.NotExternalVerification")
}
token, err := crypto.DecryptString(config.Twilio.Token, c.smsEncryption)
if err != nil {
return nil, err
}
return &twilio.Config{
SID: config.Twilio.SID,
Token: token,
SenderNumber: config.Twilio.SenderNumber,
VerifyServiceSID: config.Twilio.VerifyServiceSID,
}, nil
}
return nil, nil
}
func (c *Commands) activeSMSProvider(ctx context.Context) (string, error) {
config, err := c.getActiveSMSConfig(ctx, authz.GetInstance(ctx).InstanceID())
if err != nil {
return "", err
}
if config.State == domain.SMSConfigStateActive && config.Twilio != nil && config.Twilio.VerifyServiceSID != "" {
return config.ID, nil
}
return "", err
}
func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
@@ -115,23 +165,19 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID,
if existingPhone.IsPhoneVerified {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified")
}
config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck
if err != nil {
return nil, err
}
phoneCode, err := domain.NewPhoneCode(crypto.NewEncryptionGenerator(*config, c.userEncryption))
phoneCode, generatorID, err := c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
if err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
if err = c.pushAppendAndReduce(ctx, existingPhone, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.Code, phoneCode.Expiry)); err != nil {
if err = c.pushAppendAndReduce(ctx, existingPhone, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.CryptedCode(), phoneCode.CodeExpiry(), generatorID)); err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPhone.WriteModel), nil
}
func (c *Commands) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) {
func (c *Commands) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string, generatorInfo *senders.CodeGeneratorInfo) (err error) {
if userID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3m9Fs", "Errors.User.UserIDMissing")
}
@@ -148,7 +194,7 @@ func (c *Commands) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, us
}
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
_, err = c.eventstore.Push(ctx, user.NewHumanPhoneCodeSentEvent(ctx, userAgg))
_, err = c.eventstore.Push(ctx, user.NewHumanPhoneCodeSentEvent(ctx, userAgg, generatorInfo))
return err
}
@@ -19,6 +19,8 @@ type HumanPhoneWriteModel struct {
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
GeneratorID string
VerificationID string
State domain.PhoneState
UserState domain.UserState
@@ -64,6 +66,10 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry
wm.GeneratorID = e.GeneratorID
case *user.HumanPhoneCodeSentEvent:
wm.GeneratorID = e.GeneratorInfo.GetID()
wm.VerificationID = e.GeneratorInfo.GetVerificationID()
case *user.HumanPhoneRemovedEvent:
wm.State = domain.PhoneStateRemoved
wm.IsPhoneVerified = false
@@ -90,6 +96,7 @@ func (wm *HumanPhoneWriteModel) Query() *eventstore.SearchQueryBuilder {
user.HumanPhoneChangedType,
user.HumanPhoneVerifiedType,
user.HumanPhoneCodeAddedType,
user.HumanPhoneCodeSentType,
user.HumanPhoneRemovedType,
user.UserRemovedType,
user.UserV1AddedType,
+422 -77
View File
@@ -13,14 +13,29 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/senders/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommandSide_ChangeHumanPhone(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
defaultSecretGenerators *SecretGenerators
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
}
type args struct {
ctx context.Context
@@ -41,9 +56,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "invalid phone, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -61,8 +74,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -83,8 +95,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "phone not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -126,8 +137,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "verified phone changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -186,8 +196,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "phone changed to verified, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -242,8 +251,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "phone changed to verified, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -298,8 +306,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
{
name: "phone changed with code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -316,6 +323,36 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -330,9 +367,100 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
userEncryption: crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)),
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
},
args: args{
ctx: context.Background(),
email: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "+41711234567",
},
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
PhoneNumber: "+41711234567",
},
},
},
{
name: "phone changed with code (external), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSID",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+41711234567",
),
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
"id",
),
),
),
userEncryption: crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -359,7 +487,10 @@ func TestCommandSide_ChangeHumanPhone(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),
userEncryption: tt.fields.userEncryption,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
}
got, err := r.ChangeHumanPhone(tt.args.ctx, tt.args.email, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil {
@@ -377,7 +508,8 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
func TestCommandSide_VerifyHumanPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
}
type args struct {
ctx context.Context
@@ -399,9 +531,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -415,9 +545,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
{
name: "code missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -431,8 +559,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -449,8 +576,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
{
name: "code not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -482,8 +608,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -515,6 +640,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@@ -539,8 +665,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
{
name: "valid code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -572,6 +697,7 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@@ -595,11 +721,80 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
},
},
},
{
name: "valid code (external), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
"id",
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPhoneCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "id",
VerificationID: "verificationID",
},
),
),
),
expectPush(
user.NewHumanPhoneVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
),
phoneCodeVerifier: func(ctx context.Context, id string) (senders.CodeGenerator, error) {
sender := mock.NewMockCodeGenerator(gomock.NewController(t))
sender.EXPECT().VerifyCode("verificationID", "a")
return sender, nil
},
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "a",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
}
got, err := r.VerifyHumanPhone(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil {
@@ -616,9 +811,21 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
}
func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
defaultSecretGenerators *SecretGenerators
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
}
type args struct {
ctx context.Context
@@ -638,9 +845,7 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -653,8 +858,7 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -670,8 +874,7 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
{
name: "phone already verified, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -713,8 +916,7 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
{
name: "new code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -739,16 +941,33 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
domain.SecretGeneratorTypeVerifyPhoneCode,
8,
time.Hour,
true,
true,
true,
true,
)),
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
@@ -760,10 +979,92 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
Crypted: []byte("12345678"),
},
time.Hour*1,
"",
),
),
),
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
defaultSecretGenerators: defaultGenerators,
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code (external), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSID",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
"id",
),
),
),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -780,8 +1081,10 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userEncryption: tt.fields.userEncryption,
eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
}
got, err := r.CreateHumanPhoneVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
if tt.res.err == nil {
@@ -799,12 +1102,13 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
func TestCommandSide_PhoneVerificationCodeSent(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
generatorInfo *senders.CodeGeneratorInfo
}
type res struct {
err func(error) bool
@@ -818,9 +1122,7 @@ func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -833,8 +1135,7 @@ func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -850,8 +1151,7 @@ func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -877,6 +1177,7 @@ func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
expectPush(
user.NewHumanPhoneCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{},
),
),
),
@@ -885,6 +1186,55 @@ func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
generatorInfo: &senders.CodeGeneratorInfo{},
},
res: res{},
},
{
name: "code sent (external), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
),
expectPush(
user.NewHumanPhoneCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
generatorInfo: &senders.CodeGeneratorInfo{
ID: "generatorID",
VerificationID: "verificationID",
},
},
res: res{},
},
@@ -892,9 +1242,9 @@ func TestCommandSide_PhoneVerificationCodeSent(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),
}
err := r.HumanPhoneVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
err := r.HumanPhoneVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.generatorInfo)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -907,7 +1257,7 @@ func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
func TestCommandSide_RemoveHumanPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -927,9 +1277,7 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -942,8 +1290,7 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -959,8 +1306,7 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) {
{
name: "phone not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -991,8 +1337,7 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) {
{
name: "remove phone, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1037,7 +1382,7 @@ func TestCommandSide_RemoveHumanPhone(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),
}
got, err := r.RemoveHumanPhone(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
if tt.res.err == nil {
+154 -28
View File
@@ -19,18 +19,31 @@ import (
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/idp"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommandSide_AddHuman(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
codeAlg crypto.EncryptionAlgorithm
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
codeAlg crypto.EncryptionAlgorithm
newCode encrypedCodeFunc
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
}
type args struct {
ctx context.Context
@@ -660,7 +673,6 @@ func TestCommandSide_AddHuman(t *testing.T) {
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(),
@@ -1042,6 +1054,36 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "+41711234567", AllowedLanguage),
user.NewHumanEmailVerifiedEvent(
@@ -1057,13 +1099,15 @@ func TestCommandSide_AddHuman(t *testing.T) {
Crypted: []byte("phonecode"),
},
time.Hour*1,
"",
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("phonecode", time.Hour),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phonecode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -1183,6 +1227,36 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "+41711234567", AllowedLanguage),
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -1198,13 +1272,15 @@ func TestCommandSide_AddHuman(t *testing.T) {
},
1*time.Hour,
true,
"",
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("phoneCode", time.Hour),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -1307,11 +1383,13 @@ func TestCommandSide_AddHuman(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.codeAlg,
idGenerator: tt.fields.idGenerator,
newEncryptedCode: tt.fields.newCode,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.codeAlg,
idGenerator: tt.fields.idGenerator,
newEncryptedCode: tt.fields.newCode,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
}
err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail)
if tt.res.err == nil {
@@ -1332,10 +1410,22 @@ func TestCommandSide_AddHuman(t *testing.T) {
}
func TestCommandSide_ImportHuman(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
}
type args struct {
ctx context.Context
@@ -1889,6 +1979,36 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "+41711234567", AllowedLanguage),
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -1910,11 +2030,15 @@ func TestCommandSide_ImportHuman(t *testing.T) {
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1),
time.Hour*1,
"",
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: context.Background(),
@@ -2866,9 +2990,11 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
f, a := tt.given(t)
r := &Commands{
eventstore: f.eventstore(t),
idGenerator: f.idGenerator,
userPasswordHasher: f.userPasswordHasher,
eventstore: f.eventstore(t),
idGenerator: f.idGenerator,
userPasswordHasher: f.userPasswordHasher,
newEncryptedCodeWithDefault: f.newEncryptedCodeWithDefault,
defaultSecretGenerators: f.defaultSecretGenerators,
}
gotHuman, gotCode, err := r.ImportHuman(a.ctx, a.orgID, a.human, a.passwordless, a.links, a.secretGenerator, a.secretGenerator, a.secretGenerator, a.secretGenerator)
if tt.res.err == nil {
+10 -3
View File
@@ -352,11 +352,11 @@ func (c *Commands) changeUserPhone(ctx context.Context, cmds []eventstore.Comman
if phone.Verified {
return append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
} else {
cryptoCode, err := c.newPhoneCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
cryptoCode, generatorID, err := c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
if err != nil {
return cmds, code, err
}
cmds = append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, phone.ReturnCode))
cmds = append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.CryptedCode(), cryptoCode.CodeExpiry(), phone.ReturnCode, generatorID))
if phone.ReturnCode {
code = &cryptoCode.Plain
}
@@ -389,7 +389,14 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com
verification := c.setPasswordWithPermission(wm.AggregateID, wm.ResourceOwner)
// otherwise check the password code...
if password.PasswordCode != "" {
verification = c.setPasswordWithVerifyCode(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, password.PasswordCode)
verification = c.setPasswordWithVerifyCode(
wm.PasswordCodeCreationDate,
wm.PasswordCodeExpiry,
wm.PasswordCode,
wm.PasswordCodeGeneratorID,
wm.PasswordCodeVerificationID,
password.PasswordCode,
)
}
// ...or old password
if password.OldPassword != "" {
+370 -32
View File
@@ -18,18 +18,31 @@ import (
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/idp"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommandSide_AddUserHuman(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
newCode encrypedCodeFunc
checkPermission domain.PermissionCheck
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
newCode encrypedCodeFunc
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
checkPermission domain.PermissionCheck
defaultSecretGenerators *SecretGenerators
}
type args struct {
ctx context.Context
@@ -1027,6 +1040,36 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "+41711234567", language.English),
user.NewHumanEmailVerifiedEvent(
@@ -1042,13 +1085,120 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("phonecode"),
},
time.Hour*1,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newCode: mockEncryptedCode("phonecode", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phonecode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &AddHuman{
Username: "username",
FirstName: "firstname",
LastName: "lastname",
Password: "password",
Email: Email{
Address: "[email protected]",
Verified: true,
},
Phone: Phone{
Number: "+41711234567",
},
PreferredLanguage: language.English,
},
secretGenerator: GetMockSecretGenerator(t),
allowInitMail: true,
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
wantID: "user1",
},
},
{
name: "add human (with phone), ok (external)",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
true,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifiyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "+41711234567", language.English),
user.NewHumanEmailVerifiedEvent(
context.Background(),
&userAgg.Aggregate,
),
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
nil,
0,
"id",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phonecode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -1171,6 +1321,36 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "+41711234567", language.English),
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -1186,13 +1366,15 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
},
1*time.Hour,
true,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newCode: mockEncryptedCode("phoneCode", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -1547,11 +1729,13 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
idGenerator: tt.fields.idGenerator,
newEncryptedCode: tt.fields.newCode,
checkPermission: tt.fields.checkPermission,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
idGenerator: tt.fields.idGenerator,
newEncryptedCode: tt.fields.newCode,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
checkPermission: tt.fields.checkPermission,
multifactors: domain.MultifactorConfigs{
OTP: domain.OTPConfig{
Issuer: "zitadel.com",
@@ -1578,11 +1762,23 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
}
func TestCommandSide_ChangeUserHuman(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher
newCode encrypedCodeFunc
checkPermission domain.PermissionCheck
eventstore func(t *testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher
newCode encrypedCodeFunc
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
checkPermission domain.PermissionCheck
defaultSecretGenerators *SecretGenerators
}
type args struct {
ctx context.Context
@@ -2107,6 +2303,36 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
newAddHumanEvent("$plain$x$password", true, true, "", language.English),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&userAgg.Aggregate,
@@ -2122,11 +2348,13 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
},
time.Hour,
false,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneCode", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -2145,7 +2373,83 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
ResourceOwner: "org1",
},
},
}, {
},
{
name: "change human phone, ok (external)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
newAddHumanEvent("$plain$x$password", true, true, "", language.English),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&userAgg.Aggregate,
"+41791234567",
),
user.NewHumanPhoneCodeAddedEventV2(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &ChangeHuman{
Phone: &Phone{
Number: "+41791234567",
},
},
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
res: res{
want: &domain.ObjectDetails{
Sequence: 0,
EventDate: time.Time{},
ResourceOwner: "org1",
},
},
},
{
name: "change human phone verified, not allowed",
fields: fields{
eventstore: expectEventstore(
@@ -2255,6 +2559,36 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
newAddHumanEvent("$plain$x$password", true, true, "", language.English),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&userAgg.Aggregate,
@@ -2270,11 +2604,13 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
},
time.Hour,
true,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneCode", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
ctx: context.Background(),
@@ -2929,11 +3265,13 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
newEncryptedCode: tt.fields.newCode,
checkPermission: tt.fields.checkPermission,
userEncryption: tt.args.codeAlg,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
newEncryptedCode: tt.fields.newCode,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
checkPermission: tt.fields.checkPermission,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
userEncryption: tt.args.codeAlg,
}
err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg)
if tt.res.err == nil {
+22 -12
View File
@@ -42,13 +42,15 @@ type UserV2WriteModel struct {
InitCodeExpiry time.Duration
InitCheckFailedCount uint64
PasswordWriteModel bool
PasswordEncodedHash string
PasswordChangeRequired bool
PasswordCode *crypto.CryptoValue
PasswordCodeCreationDate time.Time
PasswordCodeExpiry time.Duration
PasswordCheckFailedCount uint64
PasswordWriteModel bool
PasswordEncodedHash string
PasswordChangeRequired bool
PasswordCode *crypto.CryptoValue
PasswordCodeCreationDate time.Time
PasswordCodeExpiry time.Duration
PasswordCheckFailedCount uint64
PasswordCodeGeneratorID string
PasswordCodeVerificationID string
EmailWriteModel bool
Email domain.EmailAddress
@@ -270,7 +272,9 @@ func (wm *UserV2WriteModel) Reduce() error {
wm.PasswordChangeRequired = e.ChangeRequired
wm.EmptyPasswordCode()
case *user.HumanPasswordCodeAddedEvent:
wm.SetPasswordCode(e.Code, e.Expiry, e.CreationDate())
wm.SetPasswordCode(e)
case *user.HumanPasswordCodeSentEvent:
wm.SetPasswordCodeSent(e)
case *user.UserIDPLinkAddedEvent:
wm.AddIDPLink(e.IDPConfigID, e.DisplayName, e.ExternalUserID)
case *user.UserIDPLinkRemovedEvent:
@@ -337,10 +341,16 @@ func (wm *UserV2WriteModel) EmptyPasswordCode() {
wm.PasswordCodeExpiry = 0
wm.PasswordCodeCreationDate = time.Time{}
}
func (wm *UserV2WriteModel) SetPasswordCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
wm.PasswordCode = code
wm.PasswordCodeExpiry = expiry
wm.PasswordCodeCreationDate = creationDate
func (wm *UserV2WriteModel) SetPasswordCode(e *user.HumanPasswordCodeAddedEvent) {
wm.PasswordCode = e.Code
wm.PasswordCodeExpiry = e.Expiry
wm.PasswordCodeCreationDate = e.CreationDate()
wm.PasswordCodeGeneratorID = e.GeneratorID
}
func (wm *UserV2WriteModel) SetPasswordCodeSent(e *user.HumanPasswordCodeSentEvent) {
wm.PasswordCodeGeneratorID = e.GeneratorInfo.GetID()
wm.PasswordCodeVerificationID = e.GeneratorInfo.GetVerificationID()
}
func (wm *UserV2WriteModel) Query() *eventstore.SearchQueryBuilder {
+63
View File
@@ -1021,6 +1021,7 @@ func TestCommandSide_userHumanWriteModel_phone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
@@ -1064,6 +1065,65 @@ func TestCommandSide_userHumanWriteModel_phone(t *testing.T) {
},
},
},
{
name: "user added with phone code external",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
newRegisterHumanEvent("username", "$plain$x$password", true, true, "", language.English),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&userAgg.Aggregate,
"+41791234567",
),
),
eventFromEventPusher(
user.NewHumanPhoneCodeAddedEventV2(context.Background(),
&userAgg.Aggregate,
nil,
0,
false,
"id",
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
},
res: res{
want: &UserV2WriteModel{
HumanWriteModel: true,
PhoneWriteModel: true,
StateWriteModel: true,
WriteModel: eventstore.WriteModel{
AggregateID: "user1",
Events: []eventstore.Event{},
ProcessedSequence: 0,
ResourceOwner: "org1",
},
UserName: "username",
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: language.English,
PasswordEncodedHash: "$plain$x$password",
PasswordChangeRequired: true,
Email: "[email protected]",
IsEmailVerified: false,
Phone: "+41791234567",
IsPhoneVerified: false,
PhoneCode: nil,
PhoneCodeCreationDate: time.Time{},
PhoneCodeExpiry: 0,
UserState: domain.UserStateActive,
},
},
},
{
name: "user added with phone code verified",
fields: fields{
@@ -1089,6 +1149,7 @@ func TestCommandSide_userHumanWriteModel_phone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
eventFromEventPusher(
@@ -1155,6 +1216,7 @@ func TestCommandSide_userHumanWriteModel_phone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
eventFromEventPusher(
@@ -1229,6 +1291,7 @@ func TestCommandSide_userHumanWriteModel_phone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
eventFromEventPusher(
+30 -13
View File
@@ -6,9 +6,11 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"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/notification/senders"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -88,7 +90,7 @@ func (c *Commands) changeUserPhoneWithGenerator(ctx context.Context, userID, pho
if err = cmd.Change(ctx, domain.PhoneNumber(phone)); err != nil {
return nil, err
}
if err = cmd.AddGeneratedCode(ctx, gen, returnCode); err != nil {
if err = cmd.AddGeneratedCode(ctx, returnCode); err != nil {
return nil, err
}
return cmd.Push(ctx)
@@ -107,10 +109,10 @@ func (c *Commands) resendUserPhoneCodeWithGenerator(ctx context.Context, userID
return nil, err
}
}
if cmd.model.Code == nil {
if cmd.model.Code == nil && cmd.model.GeneratorID == "" {
return nil, zerrors.ThrowPreconditionFailed(err, "PHONE-5xrra88eq8", "Errors.User.Code.Empty")
}
if err = cmd.AddGeneratedCode(ctx, gen, returnCode); err != nil {
if err = cmd.AddGeneratedCode(ctx, returnCode); err != nil {
return nil, err
}
return cmd.Push(ctx)
@@ -166,10 +168,12 @@ func (c *Commands) removeUserPhone(ctx context.Context, userID string) (*domain.
// UserPhoneEvents allows step-by-step additions of events,
// operating on the Human Phone Model.
type UserPhoneEvents struct {
eventstore *eventstore.Eventstore
aggregate *eventstore.Aggregate
events []eventstore.Command
model *HumanPhoneWriteModel
eventstore *eventstore.Eventstore
aggregate *eventstore.Aggregate
events []eventstore.Command
model *HumanPhoneWriteModel
generateCode func(ctx context.Context, filter preparation.FilterToQueryReducer) (*EncryptedCode, string, error)
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
plainCode *string
}
@@ -196,6 +200,10 @@ func (c *Commands) NewUserPhoneEvents(ctx context.Context, userID string) (*User
eventstore: c.eventstore,
aggregate: UserAggregateFromWriteModel(&model.WriteModel),
model: model,
generateCode: func(ctx context.Context, filter preparation.FilterToQueryReducer) (*EncryptedCode, string, error) {
return c.newPhoneCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode)
},
getCodeVerifier: c.phoneCodeVerifier,
}, nil
}
@@ -229,15 +237,14 @@ func (c *UserPhoneEvents) SetVerified(ctx context.Context) {
// AddGeneratedCode generates a new encrypted code and sets it to the phone number.
// When returnCode a plain text of the code will be returned from Push.
func (c *UserPhoneEvents) AddGeneratedCode(ctx context.Context, gen crypto.Generator, returnCode bool) error {
value, plain, err := crypto.NewCode(gen)
func (c *UserPhoneEvents) AddGeneratedCode(ctx context.Context, returnCode bool) error {
code, generatorID, err := c.generateCode(ctx, c.eventstore.Filter) //nolint:staticcheck
if err != nil {
return err
}
c.events = append(c.events, user.NewHumanPhoneCodeAddedEventV2(ctx, c.aggregate, value, gen.Expiry(), returnCode))
c.events = append(c.events, user.NewHumanPhoneCodeAddedEventV2(ctx, c.aggregate, code.CryptedCode(), code.CodeExpiry(), returnCode, generatorID))
if returnCode {
c.plainCode = &plain
c.plainCode = &code.Plain
}
return nil
}
@@ -247,7 +254,17 @@ 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.Alg())
err := verifyCode(
ctx,
c.model.CodeCreationDate,
c.model.CodeExpiry,
c.model.Code,
c.model.GeneratorID,
c.model.VerificationID,
code,
gen.Alg(),
c.getCodeVerifier,
)
if err == nil {
c.events = append(c.events, user.NewHumanPhoneVerifiedEvent(ctx, c.aggregate))
return nil
+300 -67
View File
@@ -22,7 +22,7 @@ import (
func TestCommands_ChangeUserPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
@@ -38,8 +38,7 @@ func TestCommands_ChangeUserPhone(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -81,8 +80,7 @@ func TestCommands_ChangeUserPhone(t *testing.T) {
{
name: "missing phone",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -124,8 +122,7 @@ func TestCommands_ChangeUserPhone(t *testing.T) {
{
name: "not changed",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -168,7 +165,7 @@ func TestCommands_ChangeUserPhone(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),
checkPermission: tt.fields.checkPermission,
}
_, err := c.ChangeUserPhone(context.Background(), tt.args.userID, tt.args.phone, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))
@@ -180,7 +177,7 @@ func TestCommands_ChangeUserPhone(t *testing.T) {
func TestCommands_ChangeUserPhoneReturnCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
@@ -196,8 +193,7 @@ func TestCommands_ChangeUserPhoneReturnCode(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -239,8 +235,7 @@ func TestCommands_ChangeUserPhoneReturnCode(t *testing.T) {
{
name: "missing phone",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -283,7 +278,7 @@ func TestCommands_ChangeUserPhoneReturnCode(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),
checkPermission: tt.fields.checkPermission,
}
_, err := c.ChangeUserPhoneReturnCode(context.Background(), tt.args.userID, tt.args.phone, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))
@@ -295,7 +290,7 @@ func TestCommands_ChangeUserPhoneReturnCode(t *testing.T) {
func TestCommands_ResendUserPhoneCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
@@ -310,8 +305,7 @@ func TestCommands_ResendUserPhoneCode(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -352,8 +346,7 @@ func TestCommands_ResendUserPhoneCode(t *testing.T) {
{
name: "no code",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -395,7 +388,7 @@ func TestCommands_ResendUserPhoneCode(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),
checkPermission: tt.fields.checkPermission,
}
_, err := c.ResendUserPhoneCode(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))
@@ -407,7 +400,7 @@ func TestCommands_ResendUserPhoneCode(t *testing.T) {
func TestCommands_ResendUserPhoneCodeReturnCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
@@ -422,8 +415,7 @@ func TestCommands_ResendUserPhoneCodeReturnCode(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -464,8 +456,7 @@ func TestCommands_ResendUserPhoneCodeReturnCode(t *testing.T) {
{
name: "no code",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
@@ -507,7 +498,7 @@ func TestCommands_ResendUserPhoneCodeReturnCode(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),
checkPermission: tt.fields.checkPermission,
}
_, err := c.ResendUserPhoneCodeReturnCode(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))
@@ -519,7 +510,7 @@ func TestCommands_ResendUserPhoneCodeReturnCode(t *testing.T) {
func TestCommands_ChangeUserPhoneVerified(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
@@ -536,7 +527,7 @@ func TestCommands_ChangeUserPhoneVerified(t *testing.T) {
{
name: "missing userID",
fields: fields{
eventstore: eventstoreExpect(t),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckNotAllowed(),
},
args: args{
@@ -548,8 +539,7 @@ func TestCommands_ChangeUserPhoneVerified(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -582,8 +572,7 @@ func TestCommands_ChangeUserPhoneVerified(t *testing.T) {
{
name: "missing phone",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -616,8 +605,7 @@ func TestCommands_ChangeUserPhoneVerified(t *testing.T) {
{
name: "phone changed",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -667,7 +655,7 @@ func TestCommands_ChangeUserPhoneVerified(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),
checkPermission: tt.fields.checkPermission,
}
got, err := c.ChangeUserPhoneVerified(context.Background(), tt.args.userID, tt.args.phone)
@@ -678,9 +666,22 @@ func TestCommands_ChangeUserPhoneVerified(t *testing.T) {
}
func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore *eventstore.Eventstore
checkPermission domain.PermissionCheck
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
userEncryption crypto.EncryptionAlgorithm
defaultSecretGenerators *SecretGenerators
}
type args struct {
userID string
@@ -697,7 +698,7 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
{
name: "missing user",
fields: fields{
eventstore: eventstoreExpect(t),
eventstore: expectEventstore(),
},
args: args{
userID: "",
@@ -709,8 +710,7 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -744,8 +744,7 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
{
name: "missing phone",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -779,8 +778,7 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
{
name: "not changed",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -814,8 +812,7 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
{
name: "phone changed",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -836,6 +833,36 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
}(),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -851,10 +878,13 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
userID: "user1",
@@ -873,8 +903,7 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
{
name: "phone changed, return code",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -895,6 +924,36 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
}(),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -910,10 +969,13 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
userID: "user1",
@@ -934,8 +996,11 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
checkPermission: tt.fields.checkPermission,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
userEncryption: tt.fields.userEncryption,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
}
got, err := c.changeUserPhoneWithGenerator(context.Background(), tt.args.userID, tt.args.phone, GetMockSecretGenerator(t), tt.args.returnCode)
require.ErrorIs(t, err, tt.wantErr)
@@ -945,9 +1010,21 @@ func TestCommands_changeUserPhoneWithGenerator(t *testing.T) {
}
func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore *eventstore.Eventstore
checkPermission domain.PermissionCheck
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
}
type args struct {
userID string
@@ -963,7 +1040,7 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
{
name: "missing user",
fields: fields{
eventstore: eventstoreExpect(t),
eventstore: expectEventstore(),
},
args: args{
userID: "",
@@ -974,8 +1051,7 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
{
name: "missing permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -1008,8 +1084,7 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
{
name: "no code",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -1042,8 +1117,7 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
{
name: "resend",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -1074,6 +1148,37 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
@@ -1088,10 +1193,103 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
userID: "user1",
returnCode: false,
},
want: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
PhoneNumber: "+41791234567",
IsPhoneVerified: false,
},
},
{
name: "resend (external)",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
event := user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"[email protected]",
true,
)
event.AddPhoneData("+41791234567")
return event
}(),
),
eventFromEventPusher(
user.NewHumanPhoneCodeAddedEventV2(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
true,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
user.NewHumanPhoneCodeAddedEventV2(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
userID: "user1",
@@ -1109,8 +1307,7 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
{
name: "return code",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
func() eventstore.Command {
@@ -1141,6 +1338,37 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
@@ -1155,10 +1383,13 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("a", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args: args{
userID: "user1",
@@ -1178,8 +1409,10 @@ func TestCommands_resendUserPhoneCodeWithGenerator(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
checkPermission: tt.fields.checkPermission,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
}
got, err := c.resendUserPhoneCodeWithGenerator(context.Background(), tt.args.userID, GetMockSecretGenerator(t), tt.args.returnCode)
require.ErrorIs(t, err, tt.wantErr)
+6
View File
@@ -114,6 +114,9 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser)
func(ctx context.Context) (*EncryptedCode, error) {
return c.newEmailCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
},
func(ctx context.Context) (*EncryptedCode, string, error) {
return c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
},
)
if err != nil {
return nil, err
@@ -220,6 +223,9 @@ func (c *Commands) ChangeSchemaUser(ctx context.Context, user *ChangeSchemaUser)
func(ctx context.Context) (*EncryptedCode, error) {
return c.newEmailCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
},
func(ctx context.Context) (*EncryptedCode, string, error) {
return c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
},
)
if err != nil {
return nil, err
+16 -13
View File
@@ -222,7 +222,8 @@ func (wm *UserV3WriteModel) NewCreated(
data json.RawMessage,
email *Email,
phone *Phone,
code func(context.Context) (*EncryptedCode, error),
emailCode func(context.Context) (*EncryptedCode, error),
phoneCode func(context.Context) (*EncryptedCode, string, error),
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
return nil, "", "", err
@@ -239,7 +240,7 @@ func (wm *UserV3WriteModel) NewCreated(
if email != nil {
emailEvents, plainCodeEmail, err := wm.NewEmailCreate(ctx,
email,
code,
emailCode,
)
if err != nil {
return nil, "", "", err
@@ -253,7 +254,7 @@ func (wm *UserV3WriteModel) NewCreated(
if phone != nil {
phoneEvents, plainCodePhone, err := wm.NewPhoneCreate(ctx,
phone,
code,
phoneCode,
)
if err != nil {
return nil, "", "", err
@@ -310,7 +311,8 @@ func (wm *UserV3WriteModel) NewUpdate(
user *SchemaUser,
email *Email,
phone *Phone,
code func(context.Context) (*EncryptedCode, error),
emailCode func(context.Context) (*EncryptedCode, error),
phoneCode func(context.Context) (*EncryptedCode, string, error),
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
return nil, "", "", err
@@ -334,7 +336,7 @@ func (wm *UserV3WriteModel) NewUpdate(
if email != nil {
emailEvents, plainCodeEmail, err := wm.NewEmailUpdate(ctx,
email,
code,
emailCode,
)
if err != nil {
return nil, "", "", err
@@ -348,7 +350,7 @@ func (wm *UserV3WriteModel) NewUpdate(
if phone != nil {
phoneEvents, plainCodePhone, err := wm.NewPhoneCreate(ctx,
phone,
code,
phoneCode,
)
if err != nil {
return nil, "", "", err
@@ -564,7 +566,7 @@ func (wm *UserV3WriteModel) newEmailCodeAddedEvent(
func (wm *UserV3WriteModel) NewPhoneCreate(
ctx context.Context,
phone *Phone,
code func(context.Context) (*EncryptedCode, error),
code func(context.Context) (*EncryptedCode, string, error),
) (_ []eventstore.Command, plainCode string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
return nil, "", err
@@ -596,7 +598,7 @@ func (wm *UserV3WriteModel) NewPhoneCreate(
func (wm *UserV3WriteModel) NewPhoneUpdate(
ctx context.Context,
phone *Phone,
code func(context.Context) (*EncryptedCode, error),
code func(context.Context) (*EncryptedCode, string, error),
) (_ []eventstore.Command, plainCode string, err error) {
if !wm.PhoneWM {
return nil, "", nil
@@ -637,7 +639,7 @@ func (wm *UserV3WriteModel) newPhoneVerifiedEvent(
func (wm *UserV3WriteModel) NewResendPhoneCode(
ctx context.Context,
code func(context.Context) (*EncryptedCode, error),
code func(context.Context) (*EncryptedCode, string, error),
isReturnCode bool,
) (_ []eventstore.Command, plainCode string, err error) {
if !wm.PhoneWM {
@@ -661,10 +663,10 @@ func (wm *UserV3WriteModel) NewResendPhoneCode(
func (wm *UserV3WriteModel) newPhoneCodeAddedEvent(
ctx context.Context,
code func(context.Context) (*EncryptedCode, error),
code func(context.Context) (*EncryptedCode, string, error),
isReturnCode bool,
) (_ *schemauser.PhoneCodeAddedEvent, plainCode string, err error) {
cryptoCode, err := code(ctx)
cryptoCode, generatorID, err := code(ctx)
if err != nil {
return nil, "", err
}
@@ -673,8 +675,9 @@ func (wm *UserV3WriteModel) newPhoneCodeAddedEvent(
}
return schemauser.NewPhoneCodeAddedEvent(ctx,
UserV3AggregateFromWriteModel(&wm.WriteModel),
cryptoCode.Crypted,
cryptoCode.Expiry,
cryptoCode.CryptedCode(),
cryptoCode.CodeExpiry(),
isReturnCode,
generatorID,
), plainCode, nil
}
+4 -4
View File
@@ -41,8 +41,8 @@ func (c *Commands) ChangeSchemaUserPhone(ctx context.Context, user *ChangeSchema
events, plainCode, err := writeModel.NewPhoneUpdate(ctx,
user.Phone,
func(ctx context.Context) (*EncryptedCode, error) {
return c.newPhoneCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
func(ctx context.Context) (*EncryptedCode, string, error) {
return c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
},
)
if err != nil {
@@ -92,8 +92,8 @@ func (c *Commands) ResendSchemaUserPhoneCode(ctx context.Context, user *ResendSc
}
events, plainCode, err := writeModel.NewResendPhoneCode(ctx,
func(ctx context.Context) (*EncryptedCode, error) {
return c.newPhoneCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
func(ctx context.Context) (*EncryptedCode, string, error) {
return c.newPhoneCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode, c.userEncryption, c.defaultSecretGenerators.PhoneVerificationCode) //nolint:staticcheck
},
user.ReturnCode,
)
+339 -20
View File
@@ -14,6 +14,7 @@ import (
"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"
"github.com/zitadel/zitadel/internal/repository/user/schema"
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
"github.com/zitadel/zitadel/internal/zerrors"
@@ -21,9 +22,9 @@ import (
func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
}
type args struct {
ctx context.Context
@@ -187,6 +188,36 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
@@ -202,11 +233,12 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -239,6 +271,36 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
"name": "user"
}`),
)),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
@@ -253,11 +315,87 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
Crypted: []byte("phoneverify"),
}, time.Hour*1,
false,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &ChangeSchemaUserPhone{
ID: "user1",
Phone: &Phone{
Number: "+41791234567",
},
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
"user updated, phone to verify (external)",
fields{
eventstore: expectEventstore(
expectFilter(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"type",
1,
json.RawMessage(`{
"name": "user"
}`),
)),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifiyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"+41791234567",
),
schemauser.NewPhoneCodeAddedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify", time.Hour),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -321,10 +459,20 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
defaultSecretGenerators: &SecretGenerators{
PhoneVerificationCode: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
},
}
details, err := c.ChangeSchemaUserPhone(tt.args.ctx, tt.args.user)
if tt.res.err == nil {
@@ -463,6 +611,7 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
eventFromEventPusher(
@@ -520,6 +669,7 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
@@ -571,6 +721,7 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
@@ -622,6 +773,7 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
@@ -671,9 +823,9 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
}
type args struct {
ctx context.Context
@@ -793,6 +945,7 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
eventFromEventPusher(
@@ -852,6 +1005,7 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
@@ -905,6 +1059,37 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
@@ -921,12 +1106,104 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify2", time.Hour),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &ResendSchemaUserPhoneCode{
ID: "user1",
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
"phone code resend, ok (external)",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"id1",
1,
json.RawMessage(`{
"name": "user1"
}`),
),
),
eventFromEventPusher(
schemauser.NewPhoneUpdatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"+41791234567",
),
),
eventFromEventPusherWithCreationDateNow(
schemauser.NewPhoneCodeAddedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
eventFromEventPusher(
schemauser.NewPhoneCodeAddedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify2", time.Hour),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -975,6 +1252,37 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
@@ -991,12 +1299,13 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify2", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify2", time.Hour),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -1016,10 +1325,20 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
defaultSecretGenerators: &SecretGenerators{
PhoneVerificationCode: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
},
}
details, err := c.ResendSchemaUserPhoneCode(tt.args.ctx, tt.args.user)
if tt.res.err == nil {
+364 -26
View File
@@ -16,17 +16,30 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/user/schema"
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommands_CreateSchemaUser(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
}
type args struct {
ctx context.Context
@@ -663,6 +676,36 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
),
),
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewCreatedEvent(
context.Background(),
@@ -687,12 +730,14 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
idGenerator: mock.ExpectID(t, "id1"),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify", time.Hour),
idGenerator: mock.ExpectID(t, "id1"),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -741,6 +786,36 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
),
),
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewCreatedEvent(
context.Background(),
@@ -765,12 +840,117 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
idGenerator: mock.ExpectID(t, "id1"),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify", time.Hour),
idGenerator: mock.ExpectID(t, "id1"),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &CreateSchemaUser{
ResourceOwner: "org1",
SchemaID: "type",
schemaRevision: 1,
Data: json.RawMessage(`{
"name": "user"
}`),
Phone: &Phone{
Number: "+41791234567",
},
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
ID: "id1",
},
},
},
{
"user created, phone to verify (external)",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
schema.NewCreatedEvent(
context.Background(),
&schema.NewAggregate("id1", "instanceID").Aggregate,
"type",
json.RawMessage(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`),
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
),
),
),
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("id1", "org1").Aggregate,
"type",
1,
json.RawMessage(`{
"name": "user"
}`),
),
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("id1", "org1").Aggregate,
"+41791234567",
),
schemauser.NewPhoneCodeAddedEvent(context.Background(),
&schemauser.NewAggregate("id1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
idGenerator: mock.ExpectID(t, "id1"),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -870,11 +1050,13 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}
details, err := c.CreateSchemaUser(tt.args.ctx, tt.args.user)
if tt.res.err == nil {
@@ -1949,10 +2131,22 @@ func TestCommandSide_ReactivateSchemaUser(t *testing.T) {
}
func TestCommands_ChangeSchemaUser(t *testing.T) {
defaultGenerators := &SecretGenerators{
OTPSMS: &crypto.GeneratorConfig{
Length: 8,
Expiry: time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
}
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
}
type args struct {
ctx context.Context
@@ -3016,6 +3210,36 @@ func TestCommands_ChangeSchemaUser(t *testing.T) {
}`),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
@@ -3031,11 +3255,13 @@ func TestCommands_ChangeSchemaUser(t *testing.T) {
},
time.Hour*1,
true,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -3069,6 +3295,36 @@ func TestCommands_ChangeSchemaUser(t *testing.T) {
}`),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
@@ -3084,11 +3340,91 @@ func TestCommands_ChangeSchemaUser(t *testing.T) {
},
time.Hour*1,
false,
"",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("phoneverify", time.Hour),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &ChangeSchemaUser{
ID: "user1",
Phone: &Phone{
Number: "+41791234567",
},
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
"user updated, phone to verify (external)",
fields{
eventstore: expectEventstore(
expectFilter(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("id1", "org1").Aggregate,
"type",
1,
json.RawMessage(`{
"name": "user"
}`),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectFilter(
eventFromEventPusher(
instance.NewSMSConfigTwilioAddedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
"",
"sid",
"senderNumber",
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("crypted")},
"verifyServiceSid",
),
),
eventFromEventPusher(
instance.NewSMSConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("instanceID").Aggregate,
"id",
),
),
),
expectPush(
schemauser.NewPhoneUpdatedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"+41791234567",
),
schemauser.NewPhoneCodeAddedEvent(context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneverify", time.Hour),
defaultSecretGenerators: defaultGenerators,
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
@@ -3157,10 +3493,12 @@ func TestCommands_ChangeSchemaUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}
details, err := c.ChangeSchemaUser(tt.args.ctx, tt.args.user)
if tt.res.err == nil {