diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 5496e6ea8..bef7a0731 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -465,7 +465,7 @@ DefaultInstance: SMTPConfiguration: # configuration of the host SMTP: - #for example smtp.mailtrap.io:2525 + # must include the port, like smtp.mailtrap.io:2525. IPv6 is also supported, like [2001:db8::1]:2525 Host: User: Password: diff --git a/console/src/app/modules/policies/notification-settings/notification-settings.component.html b/console/src/app/modules/policies/notification-settings/notification-settings.component.html index b88eef9a1..f01e1ddac 100644 --- a/console/src/app/modules/policies/notification-settings/notification-settings.component.html +++ b/console/src/app/modules/policies/notification-settings/notification-settings.component.html @@ -27,9 +27,9 @@ {{ 'SETTING.SMTP.TLS' | translate }} - - {{ 'SETTING.SMTP.HOST' | translate }} - + + {{ 'SETTING.SMTP.HOSTANDPORT' | translate }} + diff --git a/console/src/app/modules/policies/notification-settings/notification-settings.component.ts b/console/src/app/modules/policies/notification-settings/notification-settings.component.ts index 16aa9adce..24fac7116 100644 --- a/console/src/app/modules/policies/notification-settings/notification-settings.component.ts +++ b/console/src/app/modules/policies/notification-settings/notification-settings.component.ts @@ -59,7 +59,7 @@ export class NotificationSettingsComponent implements OnInit { senderAddress: [{ disabled: true, value: '' }, [Validators.required]], senderName: [{ disabled: true, value: '' }, [Validators.required]], tls: [{ disabled: true, value: true }, [Validators.required]], - host: [{ disabled: true, value: '' }, [Validators.required]], + hostAndPort: [{ disabled: true, value: '' }, [Validators.required]], user: [{ disabled: true, value: '' }, [Validators.required]], }); } @@ -85,6 +85,7 @@ export class NotificationSettingsComponent implements OnInit { if (smtpConfig.smtpConfig) { this.hasSMTPConfig = true; this.form.patchValue(smtpConfig.smtpConfig); + this.form.patchValue({ ['hostAndPort']: smtpConfig.smtpConfig.host }); } }) .catch((error) => { @@ -139,7 +140,7 @@ export class NotificationSettingsComponent implements OnInit { private updateData(): Promise { if (this.hasSMTPConfig) { const req = new UpdateSMTPConfigRequest(); - req.setHost(this.host?.value ?? ''); + req.setHost(this.hostAndPort?.value ?? ''); req.setSenderAddress(this.senderAddress?.value ?? ''); req.setSenderName(this.senderName?.value ?? ''); req.setTls(this.tls?.value ?? false); @@ -148,7 +149,7 @@ export class NotificationSettingsComponent implements OnInit { return this.service.updateSMTPConfig(req); } else { const req = new AddSMTPConfigRequest(); - req.setHost(this.host?.value ?? ''); + req.setHost(this.hostAndPort?.value ?? ''); req.setSenderAddress(this.senderAddress?.value ?? ''); req.setSenderName(this.senderName?.value ?? ''); req.setTls(this.tls?.value ?? false); @@ -305,7 +306,7 @@ export class NotificationSettingsComponent implements OnInit { return this.form.get('user'); } - public get host(): AbstractControl | null { - return this.form.get('host'); + public get hostAndPort(): AbstractControl | null { + return this.form.get('hostAndPort'); } } diff --git a/console/src/app/modules/policies/notification-settings/password-dialog/password-dialog.component.html b/console/src/app/modules/policies/notification-settings/password-dialog/password-dialog.component.html index fdc31555a..43709eb19 100644 --- a/console/src/app/modules/policies/notification-settings/password-dialog/password-dialog.component.html +++ b/console/src/app/modules/policies/notification-settings/password-dialog/password-dialog.component.html @@ -4,7 +4,7 @@
{{ data.i18nLabel | translate }} - +
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 2f2e80c1d..0eb21a543 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -916,7 +916,7 @@ "TITLE": "SMTP Einstellungen", "SENDERADDRESS": "Sender Email-Adresse", "SENDERNAME": "Sender Name", - "HOST": "Host", + "HOSTANDPORT": "Host und Port", "USER": "Benutzer", "PASSWORD": "Passwort", "SETPASSWORD": "SMTP Passwort setzen", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 27e4f28c3..4ebaa7c30 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -914,9 +914,9 @@ }, "SMTP": { "TITLE": "SMTP Settings", - "SENDERADDRESS": "Sender Email address", + "SENDERADDRESS": "Sender Email Address", "SENDERNAME": "Sender Name", - "HOST": "Host", + "HOSTANDPORT": "Host And Port", "USER": "User", "PASSWORD": "Password", "SETPASSWORD": "Set SMTP Password", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 461ae7b2b..f5f4bc1df 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -916,13 +916,13 @@ "TITLE": "Paramètres SMTP", "SENDERADDRESS": "Adresse e-mail de l'expéditeur", "SENDERNAME": "Nom de l'expéditeur", - "HOST": "Hôte", + "HOSTANDPORT": "Hôte et port", "USER": "Utilisateur", "PASSWORD": "Mot de passe", "SETPASSWORD": "Définir le mot de passe SMTP", "PASSWORDSET": "Le mot de passe SMTP a été défini avec succès.", "TLS": "Sécurité de la couche de transport (TLS)", - "SAVED": "Enregistré avec succès !", + "SAVED": "Enregistré avec succès!", "REQUIREDWARN": "Pour envoyer des notifications depuis votre domaine, vous devez entrer vos données SMTP." }, "SMS": { diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 685d0bbf7..213fb5f2c 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -917,7 +917,7 @@ "TITLE": "Impostazioni SMTP", "SENDERADDRESS": "Indirizzo email del mittente", "SENDERNAME": "Nome del mittente", - "HOST": "Host", + "HOSTANDPORT": "Host e porta", "USER": "Utente", "PASSWORD": "Password", "SETPASSWORD": "Imposta SMTP Password", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 97aaad6c4..8fa600e63 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -916,7 +916,7 @@ "TITLE": "SMTP 设置", "SENDERADDRESS": "发件人地址", "SENDERNAME": "发件人名称", - "HOST": "服务器地址", + "HOSTANDPORT": "主机和端口", "USER": "用户名", "PASSWORD": "密码", "SETPASSWORD": "设置 SMTP 密码", diff --git a/internal/command/smtp.go b/internal/command/smtp.go index 895ba5b4f..a86249266 100644 --- a/internal/command/smtp.go +++ b/internal/command/smtp.go @@ -2,6 +2,7 @@ package command import ( "context" + "net" "strings" "github.com/zitadel/zitadel/internal/api/authz" @@ -98,13 +99,14 @@ func (c *Commands) RemoveSMTPConfig(ctx context.Context) (*domain.ObjectDetails, }, nil } -func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, host, user string, password []byte, tls bool) preparation.Validation { +func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, password []byte, tls bool) preparation.Validation { return func() (preparation.CreateCommands, error) { if from = strings.TrimSpace(from); from == "" { return nil, errors.ThrowInvalidArgument(nil, "INST-mruNY", "Errors.Invalid.Argument") } - if host = strings.TrimSpace(host); host == "" { - return nil, errors.ThrowInvalidArgument(nil, "INST-SF3g1", "Errors.Invalid.Argument") + hostAndPort = strings.TrimSpace(hostAndPort) + if _, _, err := net.SplitHostPort(hostAndPort); err != nil { + return nil, errors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument") } return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { fromSplitted := strings.Split(from, "@") @@ -134,7 +136,7 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, host, tls, from, name, - host, + hostAndPort, user, smtpPassword, ), @@ -143,15 +145,15 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, host, } } -func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, host, user string, tls bool) preparation.Validation { +func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, tls bool) preparation.Validation { return func() (preparation.CreateCommands, error) { if from = strings.TrimSpace(from); from == "" { return nil, errors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument") } - if host = strings.TrimSpace(host); host == "" { - return nil, errors.ThrowInvalidArgument(nil, "INST-VDwvq", "Errors.Invalid.Argument") + hostAndPort = strings.TrimSpace(hostAndPort) + if _, _, err := net.SplitHostPort(hostAndPort); err != nil { + return nil, errors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument") } - return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { fromSplitted := strings.Split(from, "@") senderDomain := fromSplitted[len(fromSplitted)-1] @@ -172,7 +174,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho tls, from, name, - host, + hostAndPort, user, ) if err != nil { diff --git a/internal/command/smtp_test.go b/internal/command/smtp_test.go index 9806c8c9c..8438b9a3f 100644 --- a/internal/command/smtp_test.go +++ b/internal/command/smtp_test.go @@ -60,7 +60,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { From: "from@domain.ch", FromName: "name", SMTP: smtp.SMTP{ - Host: "host", + Host: "host:587", User: "user", Password: "password", }, @@ -95,7 +95,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { true, "from@domain.ch", "name", - "host", + "host:587", "user", &crypto.CryptoValue{}, ), @@ -110,7 +110,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { From: "from@domain.ch", FromName: "name", SMTP: smtp.SMTP{ - Host: "host", + Host: "host:587", User: "user", Password: "password", }, @@ -150,7 +150,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { true, "from@domain.ch", "name", - "host", + "host:587", "user", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, @@ -165,6 +165,30 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { ), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.EmailConfig{ + Tls: true, + From: "from@domain.ch", + FromName: "name", + SMTP: smtp.SMTP{ + Host: "host:587", + User: "user", + Password: "password", + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "smtp config, port is missing", + fields: fields{ + eventstore: eventstoreExpect(t), + }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), smtp: &smtp.EmailConfig{ @@ -178,6 +202,90 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { }, }, }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "smtp config, host is empty", + fields: fields{ + eventstore: eventstoreExpect(t), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.EmailConfig{ + Tls: true, + From: "from@domain.ch", + FromName: "name", + SMTP: smtp.SMTP{ + Host: " ", + User: "user", + Password: "password", + }, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "add smtp config, ipv6 works", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewDomainAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "domain.ch", + false, + ), + ), + eventFromEventPusher( + instance.NewDomainPolicyAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + true, true, false, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + true, + "from@domain.ch", + "name", + "[2001:db8::1]:2525", + "user", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("password"), + }, + ), + ), + }, + ), + ), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.EmailConfig{ + Tls: true, + From: "from@domain.ch", + FromName: "name", + SMTP: smtp.SMTP{ + Host: "[2001:db8::1]:2525", + User: "user", + Password: "password", + }, + }, + }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "INSTANCE", @@ -253,7 +361,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { From: "from@domain.ch", FromName: "name", SMTP: smtp.SMTP{ - Host: "host", + Host: "host:587", User: "user", }, }, @@ -288,7 +396,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { true, "from@domain.ch", "name", - "host", + "host:587", "user", &crypto.CryptoValue{}, ), @@ -303,7 +411,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { From: "from@wrongdomain.ch", FromName: "name", SMTP: smtp.SMTP{ - Host: "host", + Host: "host:587", User: "user", }, }, @@ -338,7 +446,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { true, "from@domain.ch", "name", - "host", + "host:587", "user", &crypto.CryptoValue{}, ), @@ -353,7 +461,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { From: "from@domain.ch", FromName: "name", SMTP: smtp.SMTP{ - Host: "host", + Host: "host:587", User: "user", }, }, @@ -388,7 +496,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { true, "from@domain.ch", "name", - "host", + "host:587", "user", &crypto.CryptoValue{}, ), @@ -403,7 +511,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { false, "from2@domain.ch", "name2", - "host2", + "host2:587", "user2", ), ), @@ -418,7 +526,118 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { From: "from2@domain.ch", FromName: "name2", SMTP: smtp.SMTP{ - Host: "host2", + Host: "host2:587", + User: "user2", + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "smtp config, port is missing", + fields: fields{ + eventstore: eventstoreExpect(t), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.EmailConfig{ + Tls: true, + From: "from@domain.ch", + FromName: "name", + SMTP: smtp.SMTP{ + Host: "host", + User: "user", + Password: "password", + }, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "smtp config, host is empty", + fields: fields{ + eventstore: eventstoreExpect(t), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.EmailConfig{ + Tls: true, + From: "from@domain.ch", + FromName: "name", + SMTP: smtp.SMTP{ + Host: " ", + User: "user", + Password: "password", + }, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "smtp config change, ipv6 works", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewDomainAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "domain.ch", + false, + ), + ), + eventFromEventPusher( + instance.NewDomainPolicyAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + true, true, true, + ), + ), + eventFromEventPusher( + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + true, + "from@domain.ch", + "name", + "host:587", + "user", + &crypto.CryptoValue{}, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "INSTANCE", + newSMTPConfigChangedEvent( + context.Background(), + false, + "from2@domain.ch", + "name2", + "[2001:db8::1]:2525", + "user2", + ), + ), + }, + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + smtp: &smtp.EmailConfig{ + Tls: false, + From: "from2@domain.ch", + FromName: "name2", + SMTP: smtp.SMTP{ + Host: "[2001:db8::1]:2525", User: "user2", }, }, @@ -497,7 +716,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { true, "from", "name", - "host", + "host:587", "user", &crypto.CryptoValue{}, ), @@ -600,7 +819,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { true, "from", "name", - "host", + "host:587", "user", &crypto.CryptoValue{}, ),