mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-03 04:42:15 +00:00
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:
co-authored by
Zachary Hirschtritt
Joey Biscoglia
parent
4eaa3163b6
commit
14e2aba1bc
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user