diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go index 78b6b14fcf..7892cb022e 100644 --- a/internal/notification/handlers/user_notifier_test.go +++ b/internal/notification/handlers/user_notifier_test.go @@ -62,8 +62,6 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, "testcode") - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) expectTemplateQueries(queries, givenTemplate) commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) return fields{ @@ -72,9 +70,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, }, args{ event: &user.HumanInitialCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -99,8 +95,6 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, "testcode") - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ Domains: []*query.InstanceDomain{{ Domain: instancePrimaryDomain, @@ -115,8 +109,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, + userDataCrypto: codeAlg, }, args{ event: &user.HumanInitialCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -141,8 +134,6 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, testCode) - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) expectTemplateQueries(queries, givenTemplate) commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) return fields{ @@ -151,9 +142,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, }, args{ event: &user.HumanInitialCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -179,8 +168,6 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, testCode) - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ Domains: []*query.InstanceDomain{{ Domain: instancePrimaryDomain, @@ -195,9 +182,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, }, args{ event: &user.HumanInitialCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -211,6 +196,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { }, w }, }} + // TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent? fs, err := statik_fs.NewWithNamespace("notification") assert.NoError(t, err) for _, tt := range tests { @@ -219,7 +205,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { queries := mock.NewMockQueries(ctrl) commands := mock.NewMockCommands(ctrl) f, a, w := tt.test(ctrl, queries, commands) - _, err = newUserNotifier(ctrl, fs, f, a, w).reduceInitCodeAdded(a.event) + _, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceInitCodeAdded(a.event) if w.err != nil { w.err(t, err) } else { @@ -245,8 +231,6 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, "testcode") - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) expectTemplateQueries(queries, givenTemplate) commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) return fields{ @@ -255,9 +239,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, }, args{ event: &user.HumanEmailCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -284,8 +266,6 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, "testcode") - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ Domains: []*query.InstanceDomain{{ Domain: instancePrimaryDomain, @@ -300,8 +280,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, + userDataCrypto: codeAlg, }, args{ event: &user.HumanEmailCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -328,8 +307,6 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, testCode) - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) expectTemplateQueries(queries, givenTemplate) commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) return fields{ @@ -338,9 +315,8 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, }, args{ event: &user.HumanEmailCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -368,8 +344,6 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, testCode) - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ Domains: []*query.InstanceDomain{{ Domain: instancePrimaryDomain, @@ -384,9 +358,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, }, args{ event: &user.HumanEmailCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -414,8 +386,6 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { Content: expectContent, } codeAlg, code := cryptoValue(t, ctrl, testCode) - smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) expectTemplateQueries(queries, givenTemplate) commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) return fields{ @@ -424,9 +394,8 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { es: eventstore.NewEventstore(eventstore.TestConfig( es_repo_mock.NewRepo(t).ExpectFilterEvents(), )), - userDataCrypto: codeAlg, - SMTPPasswordCrypto: smtpAlg, - SMSTokenCrypto: nil, + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, }, args{ event: &user.HumanEmailCodeAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ @@ -451,7 +420,100 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { queries := mock.NewMockQueries(ctrl) commands := mock.NewMockCommands(ctrl) f, a, w := tt.test(ctrl, queries, commands) - _, err = newUserNotifier(ctrl, fs, f, a, w).reduceEmailCodeAdded(a.event) + _, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceEmailCodeAdded(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifier_reducePasswordChanged(t *testing.T) { + expectMailSubject := "Password of user has changed" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: true, + }, nil) + expectTemplateQueries(queries, givenTemplate) + commands.EXPECT().PasswordChangeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(eventstore.TestConfig( + es_repo_mock.NewRepo(t).ExpectFilterEvents(), + )), + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: true, + }, nil) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateQueries(queries, givenTemplate) + commands.EXPECT().PasswordChangeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(eventstore.TestConfig( + es_repo_mock.NewRepo(t).ExpectFilterEvents(), + )), + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + }, + }, w + }, + }} + fs, err := statik_fs.NewWithNamespace("notification") + assert.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + _, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reducePasswordChanged(a.event) if w.err != nil { w.err(t, err) } else { @@ -462,12 +524,11 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { } type fields struct { - queries *mock.MockQueries - commands *mock.MockCommands - es *eventstore.Eventstore - userDataCrypto crypto.EncryptionAlgorithm - SMTPPasswordCrypto crypto.EncryptionAlgorithm - SMSTokenCrypto crypto.EncryptionAlgorithm + queries *mock.MockQueries + commands *mock.MockCommands + es *eventstore.Eventstore + userDataCrypto crypto.EncryptionAlgorithm + SMSTokenCrypto crypto.EncryptionAlgorithm } type args struct { event eventstore.Event @@ -477,7 +538,9 @@ type want struct { err assert.ErrorAssertionFunc } -func newUserNotifier(ctrl *gomock.Controller, fs http.FileSystem, f fields, a args, w want) *userNotifier { +func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQueries, fs http.FileSystem, f fields, a args, w want) *userNotifier { + queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) + smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") channel := channel_mock.NewMockNotificationChannel(ctrl) if w.err == nil { w.message.TriggeringEvent = a.event @@ -493,7 +556,7 @@ func newUserNotifier(ctrl *gomock.Controller, fs http.FileSystem, f fields, a ar externalSecure, "", f.userDataCrypto, - f.SMTPPasswordCrypto, + smtpAlg, f.SMSTokenCrypto, fs, ), diff --git a/internal/repository/user/human_password.go b/internal/repository/user/human_password.go index 909da24261..02a4b35738 100644 --- a/internal/repository/user/human_password.go +++ b/internal/repository/user/human_password.go @@ -29,10 +29,11 @@ type HumanPasswordChangedEvent struct { // New events only use EncodedHash. However, the secret field // is preserved to handle events older than the switch to Passwap. - Secret *crypto.CryptoValue `json:"secret,omitempty"` - EncodedHash string `json:"encodedHash,omitempty"` - ChangeRequired bool `json:"changeRequired"` - UserAgentID string `json:"userAgentID,omitempty"` + Secret *crypto.CryptoValue `json:"secret,omitempty"` + EncodedHash string `json:"encodedHash,omitempty"` + ChangeRequired bool `json:"changeRequired"` + UserAgentID string `json:"userAgentID,omitempty"` + TriggeredAtOrigin string `json:"base_url,omitempty"` } func (e *HumanPasswordChangedEvent) Data() interface{} { @@ -43,6 +44,10 @@ func (e *HumanPasswordChangedEvent) UniqueConstraints() []*eventstore.EventUniqu return nil } +func (e *HumanPasswordChangedEvent) TriggerOrigin() string { + return e.TriggeredAtOrigin +} + func NewHumanPasswordChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate,