diff --git a/internal/notification/channels/smtp/config.go b/internal/notification/channels/smtp/config.go index d8323ae57..46d6eab0d 100644 --- a/internal/notification/channels/smtp/config.go +++ b/internal/notification/channels/smtp/config.go @@ -14,8 +14,14 @@ type SMTP struct { Password string } +// HasAuth reports whether SMTP authentication should be attempted. +// +// It returns true when a User is configured, regardless of whether Password +// is set. This behavior is intentional to support SMTP servers that allow +// authentication without a password. For servers that require a password, +// this means authentication may still be attempted with an empty password. func (smtp *SMTP) HasAuth() bool { - return smtp.User != "" && smtp.Password != "" + return smtp.User != "" } type ConfigHTTP struct { diff --git a/internal/notification/handlers/config_email.go b/internal/notification/handlers/config_email.go index a1c9ca344..42bbca9b7 100644 --- a/internal/notification/handlers/config_email.go +++ b/internal/notification/handlers/config_email.go @@ -12,7 +12,7 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -// GetSMTPConfig reads the iam SMTP provider config +// GetActiveEmailConfig reads the iam SMTP provider config func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email.Config, error) { config, err := n.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) if err != nil { @@ -23,13 +23,14 @@ func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email. Description: config.Description, } if config.SMTPConfig != nil { - if config.SMTPConfig.Password == nil { - return nil, zerrors.ThrowNotFound(err, "QUERY-Wrs3gw", "Errors.SMTPConfig.NotFound") - } - password, err := crypto.DecryptString(config.SMTPConfig.Password, n.SMTPPasswordCrypto) - if err != nil { - return nil, err + var password string + if config.SMTPConfig.Password != nil { + password, err = crypto.DecryptString(config.SMTPConfig.Password, n.SMTPPasswordCrypto) + if err != nil { + return nil, err + } } + return &email.Config{ ProviderConfig: provider, SMTPConfig: &smtp.Config{ diff --git a/internal/notification/handlers/config_email_test.go b/internal/notification/handlers/config_email_test.go new file mode 100644 index 000000000..c389dda3b --- /dev/null +++ b/internal/notification/handlers/config_email_test.go @@ -0,0 +1,147 @@ +package handlers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/notification/channels/email" + "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/notification/handlers/mock" + "github.com/zitadel/zitadel/internal/query" +) + +func TestNotificationQueries_GetActiveEmailConfig(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + const instId = "instance-1" + const cryptAlg = "alg1" + const keyId = "key1" + const pwd = "password" + + encryptedPwd := []byte("password-encrypted") + cryptAlgMock := crypto.NewMockEncryptionAlgorithm(ctrl) + cryptAlgMock.EXPECT().DecryptString(gomock.Any(), gomock.Any()).Return(pwd, nil) + cryptAlgMock.EXPECT().Algorithm().Return(cryptAlg) + cryptAlgMock.EXPECT().DecryptionKeyIDs().Return([]string{keyId}) + + tt := []struct { + smtpConfig *query.SMTPConfig + expected *email.Config + }{ + { + smtpConfig: &query.SMTPConfig{ + ID: "smtp1", + Description: "smtp config with authentication (user and password)", + SMTPConfig: &query.SMTP{ + TLS: true, + SenderAddress: "sender@example.com", + SenderName: "sender", + ReplyToAddress: "reply@example.com", + Host: "mail.com", + User: "mail-user", + Password: &crypto.CryptoValue{ + CryptoType: 0, + Algorithm: cryptAlg, + KeyID: keyId, + Crypted: encryptedPwd, + }, + }, + }, + expected: &email.Config{ + ProviderConfig: &email.Provider{ + ID: "smtp1", + Description: "smtp config with authentication (user and password)", + }, + SMTPConfig: &smtp.Config{ + SMTP: smtp.SMTP{ + Host: "mail.com", + User: "mail-user", + Password: pwd, + }, + Tls: true, + From: "sender@example.com", + FromName: "sender", + ReplyToAddress: "reply@example.com", + }, + }, + }, + { + smtpConfig: &query.SMTPConfig{ + ID: "smtp2", + Description: "smtp config with authentication (user and no password)", + SMTPConfig: &query.SMTP{ + TLS: true, + SenderAddress: "sender@example.com", + SenderName: "sender", + ReplyToAddress: "reply@example.com", + Host: "mail.com", + User: "mail-user", + }, + }, + expected: &email.Config{ + ProviderConfig: &email.Provider{ + ID: "smtp2", + Description: "smtp config with authentication (user and no password)", + }, + SMTPConfig: &smtp.Config{ + SMTP: smtp.SMTP{ + Host: "mail.com", + User: "mail-user", + }, + Tls: true, + From: "sender@example.com", + FromName: "sender", + ReplyToAddress: "reply@example.com", + }, + }, + }, + { + smtpConfig: &query.SMTPConfig{ + ID: "smtp3", + Description: "smtp config without authentication", + SMTPConfig: &query.SMTP{ + TLS: true, + SenderAddress: "sender@example.com", + SenderName: "sender", + ReplyToAddress: "reply@example.com", + Host: "mail.com", + }, + }, + expected: &email.Config{ + ProviderConfig: &email.Provider{ + ID: "smtp3", + Description: "smtp config without authentication", + }, + SMTPConfig: &smtp.Config{ + SMTP: smtp.SMTP{ + Host: "mail.com", + }, + Tls: true, + From: "sender@example.com", + FromName: "sender", + ReplyToAddress: "reply@example.com", + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.smtpConfig.Description, func(t *testing.T) { + ctx := authz.NewMockContext(instId, "org-1", "user-1") + + queryMock := mock.NewMockQueries(ctrl) + queryMock.EXPECT().SMTPConfigActive(gomock.Any(), instId).Return(tc.smtpConfig, nil) + + notificationQueries := NewNotificationQueries(queryMock, &eventstore.Eventstore{}, "ext domain", uint16(1234), false, "filepath", nil, cryptAlgMock, nil) + cfg, err := notificationQueries.GetActiveEmailConfig(ctx) + assert.NoError(t, err) + assert.EqualValues(t, tc.expected, cfg) + }) + } +}