mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:27:42 +00:00
fix: user init mail (for wrong email) (#891)
* add resendInitialMail * disable email notifications (when not initialised) * fix resend init mail * add tests * cleanup * cleanup * fix tests * add resend trigger, dialog * refactor contact component, add sendinitmail fnc * skip email if empty * reload user on phone email changes, i18n warndialog on dl * lint * rebuild mgmt proto * remove initial focus * Update console/src/assets/i18n/de.json Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
@@ -737,6 +737,9 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
||||
if user.Human == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-33ywz", "Errors.User.NotHuman")
|
||||
}
|
||||
if user.State == usr_model.UserStateInitial {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-Hs11s", "Errors.User.NotInitialised")
|
||||
}
|
||||
|
||||
passwordCode := new(model.PasswordCode)
|
||||
err = es.generatePasswordCode(passwordCode, notifyType)
|
||||
@@ -754,6 +757,35 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) ResendInitialMail(ctx context.Context, userID, email string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-G4bmn", "Errors.User.UserIDMissing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Human == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-Hfsww", "Errors.User.NotHuman")
|
||||
}
|
||||
if user.State != usr_model.UserStateInitial {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-BGbbe", "Errors.User.AlreadyInitialised")
|
||||
}
|
||||
err = user.GenerateInitCodeIfNeeded(es.InitializeUserCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoUser := model.UserFromModel(user)
|
||||
agg := ResendInitialPasswordAggregate(es.AggregateCreator(), repoUser, user.InitCode, email)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "Errors.User.UserIDMissing")
|
||||
@@ -946,6 +978,9 @@ func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Emai
|
||||
if user.Human == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-tgBdL", "Errors.User.NotHuman")
|
||||
}
|
||||
if user.State == usr_model.UserStateInitial {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-3H4q", "Errors.User.NotInitialised")
|
||||
}
|
||||
|
||||
emailCode, err := email.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
||||
if err != nil {
|
||||
@@ -1018,6 +1053,9 @@ func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userI
|
||||
if user.Human == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-hqUZP", "Errors.User.NotHuman")
|
||||
}
|
||||
if user.State == usr_model.UserStateInitial {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-E3fbw", "Errors.User.NotInitialised")
|
||||
}
|
||||
if user.Email == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "Errors.User.EmailNotFound")
|
||||
}
|
||||
|
@@ -4,15 +4,15 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
mock_cache "github.com/caos/zitadel/internal/cache/mock"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/mock"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
global_model "github.com/caos/zitadel/internal/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *UserEventstore {
|
||||
@@ -162,11 +162,18 @@ func GetMockManipulateUserWithPasswordAndEmailCodeGen(ctrl *gomock.Controller, u
|
||||
return GetMockedEventstoreWithPw(ctrl, mockEs, false, true, false, true)
|
||||
}
|
||||
|
||||
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User, verified bool) *UserEventstore {
|
||||
data, _ := json.Marshal(user)
|
||||
events := []*es_models.Event{
|
||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||
}
|
||||
if verified {
|
||||
email, _ := json.Marshal(model.Email{EmailAddress: "address"})
|
||||
events = append(events,
|
||||
&es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 2, Type: model.HumanEmailVerified},
|
||||
&es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 3, Type: model.HumanEmailChanged, Data: email},
|
||||
)
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
@@ -186,12 +193,15 @@ func GetMockManipulateUserWithPhoneCodeGen(ctrl *gomock.Controller, user model.U
|
||||
return GetMockedEventstoreWithPw(ctrl, mockEs, false, false, true, false)
|
||||
}
|
||||
|
||||
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User, verified bool) *UserEventstore {
|
||||
data, _ := json.Marshal(user)
|
||||
code, _ := json.Marshal(user.PasswordCode)
|
||||
events := []*es_models.Event{
|
||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserPasswordCodeAdded, Data: code},
|
||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 2, Type: model.UserPasswordCodeAdded, Data: code},
|
||||
}
|
||||
if verified {
|
||||
events = append(events, &es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 3, Type: model.HumanEmailVerified})
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
@@ -382,7 +392,7 @@ func GetMockManipulateUserVerifiedPhone(ctrl *gomock.Controller) *UserEventstore
|
||||
return GetMockedEventstore(ctrl, mockEs)
|
||||
}
|
||||
|
||||
func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
||||
func GetMockManipulateUserFull(ctrl *gomock.Controller, verified bool) *UserEventstore {
|
||||
user := model.Human{
|
||||
Profile: &model.Profile{
|
||||
DisplayName: "DisplayName",
|
||||
@@ -407,6 +417,9 @@ func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
||||
events := []*es_models.Event{
|
||||
{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||
}
|
||||
if verified {
|
||||
events = append(events, &es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 2, Type: model.HumanEmailVerified})
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
|
@@ -3,7 +3,6 @@ package eventsourcing
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
repo_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
)
|
||||
@@ -1287,7 +1287,7 @@ func TestPasswordID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
@@ -1366,7 +1366,7 @@ func TestSetOneTimePassword(t *testing.T) {
|
||||
{
|
||||
name: "create one time pw",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{Email: &repo_model.Email{EmailAddress: "email"}}}, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
|
||||
@@ -1516,8 +1516,10 @@ func TestCheckPassword(t *testing.T) {
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("password"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "userID",
|
||||
@@ -1582,8 +1584,10 @@ func TestSetPassword(t *testing.T) {
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@@ -1627,8 +1631,11 @@ func TestSetPassword(t *testing.T) {
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
||||
repo_model.User{
|
||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
Human: &repo_model.Human{},
|
||||
Human: &repo_model.Human{
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@@ -1653,8 +1660,10 @@ func TestSetPassword(t *testing.T) {
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code2"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@@ -1759,8 +1768,11 @@ func TestChangePassword(t *testing.T) {
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
|
||||
repo_model.User{
|
||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
Human: &repo_model.Human{},
|
||||
Human: &repo_model.Human{
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@@ -1784,8 +1796,10 @@ func TestChangePassword(t *testing.T) {
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("older"),
|
||||
}},
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
policy: &iam_model.PasswordComplexityPolicyView{},
|
||||
@@ -1859,7 +1873,12 @@ func TestRequestSetPassword(t *testing.T) {
|
||||
{
|
||||
name: "create pw",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{
|
||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
Human: &repo_model.Human{
|
||||
Email: &repo_model.Email{EmailAddress: "email"},
|
||||
}},
|
||||
true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "AggregateID",
|
||||
notifyType: model.NotificationTypeEmail,
|
||||
@@ -1868,6 +1887,18 @@ func TestRequestSetPassword(t *testing.T) {
|
||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "initial state",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "AggregateID",
|
||||
notifyType: model.NotificationTypeEmail,
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
@@ -1905,6 +1936,84 @@ func TestRequestSetPassword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResendInitialMail(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *UserEventstore
|
||||
ctx context.Context
|
||||
userID string
|
||||
mail string
|
||||
}
|
||||
type res struct {
|
||||
password *model.Password
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "resend ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithInitCode(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "AggregateID",
|
||||
mail: "",
|
||||
},
|
||||
res: res{
|
||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resend with email ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithInitCode(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, Human: &repo_model.Human{}}),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "AggregateID",
|
||||
mail: "email",
|
||||
},
|
||||
res: res{
|
||||
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
mail: "",
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing user not found",
|
||||
args: args{
|
||||
es: GetMockManipulateUserNoEvents(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "AggregateID",
|
||||
mail: "",
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.ResendInitialMail(tt.args.ctx, tt.args.userID, tt.args.mail)
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("should not get err")
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordCodeSent(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
@@ -2170,7 +2279,7 @@ func TestProfileByID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Human: &model.Human{}},
|
||||
},
|
||||
@@ -2237,7 +2346,7 @@ func TestChangeProfile(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
profile: &model.Profile{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, FirstName: "FirstName Changed", LastName: "LastName Changed"},
|
||||
},
|
||||
@@ -2304,7 +2413,7 @@ func TestEmailByID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
@@ -2371,7 +2480,7 @@ func TestChangeEmail(t *testing.T) {
|
||||
{
|
||||
name: "change email address, verified",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: true},
|
||||
},
|
||||
@@ -2389,7 +2498,7 @@ func TestChangeEmail(t *testing.T) {
|
||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||
},
|
||||
}),
|
||||
}, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
||||
},
|
||||
@@ -2397,6 +2506,24 @@ func TestChangeEmail(t *testing.T) {
|
||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user state initial",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithEmailCodeGen(ctrl, repo_model.User{
|
||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||
UserName: "UserName",
|
||||
Human: &repo_model.Human{
|
||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||
},
|
||||
}, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
email: &model.Email{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, EmailAddress: "EmailAddressChanged", IsEmailVerified: false},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
@@ -2423,7 +2550,9 @@ func TestChangeEmail(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.ChangeEmail(tt.args.ctx, tt.args.email)
|
||||
|
||||
if (tt.res.errFunc != nil && !tt.res.errFunc(err)) || (tt.res.errFunc == nil && err != nil) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.errFunc == nil && result.AggregateID == "" {
|
||||
t.Errorf("result has no id")
|
||||
}
|
||||
@@ -2433,9 +2562,6 @@ func TestChangeEmail(t *testing.T) {
|
||||
if tt.res.errFunc == nil && result.IsEmailVerified != tt.res.email.IsEmailVerified {
|
||||
t.Errorf("got wrong result change required: expected: %v, actual: %v ", tt.res.email.IsEmailVerified, result.IsEmailVerified)
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2551,12 +2677,30 @@ func TestCreateEmailVerificationCode(t *testing.T) {
|
||||
Human: &repo_model.Human{
|
||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||
}}),
|
||||
}}, true),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "userID",
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "initial state",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithEmailCodeGen(ctrl, repo_model.User{
|
||||
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||
UserName: "UserName",
|
||||
Human: &repo_model.Human{
|
||||
Profile: &repo_model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &repo_model.Email{EmailAddress: "EmailAddress"},
|
||||
},
|
||||
}, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
userID: "userID",
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
@@ -2606,7 +2750,7 @@ func TestCreateEmailVerificationCode(t *testing.T) {
|
||||
err := tt.args.es.CreateEmailVerificationCode(tt.args.ctx, tt.args.userID)
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("should not ger err")
|
||||
t.Errorf("should not get err, got: %v", err)
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
@@ -2695,7 +2839,7 @@ func TestPhoneByID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
@@ -2762,7 +2906,7 @@ func TestChangePhone(t *testing.T) {
|
||||
{
|
||||
name: "change phone, verified",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "0711234567", IsPhoneVerified: true},
|
||||
},
|
||||
@@ -3149,7 +3293,7 @@ func TestAddressByID(t *testing.T) {
|
||||
{
|
||||
name: "get by id, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
@@ -3216,7 +3360,7 @@ func TestChangeAddress(t *testing.T) {
|
||||
{
|
||||
name: "change address ok",
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
es: GetMockManipulateUserFull(ctrl, false),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
address: &model.Address{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Country: "CountryChanged"},
|
||||
},
|
||||
|
@@ -189,7 +189,7 @@ func (h *Human) AppendEvent(event *es_models.Event) (err error) {
|
||||
}
|
||||
|
||||
func (h *Human) ComputeObject() {
|
||||
if h.State == int32(model.UserStateUnspecified) {
|
||||
if h.State == int32(model.UserStateUnspecified) || h.State == int32(model.UserStateInitial) {
|
||||
if h.Email != nil && h.IsEmailVerified {
|
||||
h.State = int32(model.UserStateActive)
|
||||
} else {
|
||||
|
@@ -2,14 +2,15 @@ package eventsourcing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
@@ -454,6 +455,25 @@ func RequestSetPassword(aggCreator *es_models.AggregateCreator, user *model.User
|
||||
}
|
||||
}
|
||||
|
||||
func ResendInitialPasswordAggregate(aggCreator *es_models.AggregateCreator, user *model.User, code *usr_model.InitUserCode, email string) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if code == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dfs3q", "Errors.Internal")
|
||||
}
|
||||
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if email != "" && user.Email != nil && email != user.Email.EmailAddress {
|
||||
agg, err = agg.AppendEvent(model.HumanEmailChanged, map[string]interface{}{"email": email})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return agg.AppendEvent(model.InitializedHumanCodeAdded, code)
|
||||
}
|
||||
}
|
||||
|
||||
func PasswordCodeSentAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||
|
@@ -5,11 +5,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
@@ -1199,6 +1199,114 @@ func TestRequestSetPasswordAggregate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResendInitialPasswordAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
user *model.User
|
||||
aggCreator *models.AggregateCreator
|
||||
initcode *usr_model.InitUserCode
|
||||
email string
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventType models.EventType
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "resend initial password aggregate ok",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||
UserName: "UserName",
|
||||
Human: &model.Human{
|
||||
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &model.Email{},
|
||||
},
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
initcode: &usr_model.InitUserCode{Expiry: time.Hour * 1},
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resend initial password with same email ok",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||
UserName: "UserName",
|
||||
Human: &model.Human{
|
||||
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &model.Email{EmailAddress: "email"},
|
||||
},
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
initcode: &usr_model.InitUserCode{Expiry: time.Hour * 1},
|
||||
email: "email",
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resend initial password with new email ok",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||
UserName: "UserName",
|
||||
Human: &model.Human{
|
||||
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||
Email: &model.Email{EmailAddress: "old"},
|
||||
},
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
initcode: &usr_model.InitUserCode{Expiry: time.Hour * 1},
|
||||
email: "new",
|
||||
},
|
||||
res: res{
|
||||
eventLen: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "request nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||
UserName: "UserName",
|
||||
Human: &model.Human{
|
||||
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||
},
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := ResendInitialPasswordAggregate(tt.args.aggCreator, tt.args.user, tt.args.initcode, tt.args.email)(tt.args.ctx)
|
||||
if (tt.res.errFunc == nil && err != nil) || (tt.res.errFunc != nil && !tt.res.errFunc(err)) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordCodeSentAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
Reference in New Issue
Block a user