fix: add port to SMTP host label (#4980)

* fix: add port to SMTP host label

* fix gRPC request message

* fix: validate port in backend

* make defaults.yaml host field more clear

* add placeholder smtp host field

* make ipv6 smtp host valid

* hide smtp password input

* fix smtp host not filled

* dont let browsers prefill smtp password
This commit is contained in:
Elio Bischof 2023-01-17 10:20:16 +01:00 committed by GitHub
parent 71bd19d690
commit 0316c2c187
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 262 additions and 40 deletions

View File

@ -465,7 +465,7 @@ DefaultInstance:
SMTPConfiguration: SMTPConfiguration:
# configuration of the host # configuration of the host
SMTP: 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: Host:
User: User:
Password: Password:

View File

@ -27,9 +27,9 @@
{{ 'SETTING.SMTP.TLS' | translate }} {{ 'SETTING.SMTP.TLS' | translate }}
</mat-checkbox> </mat-checkbox>
<cnsl-form-field class="smtp-form-field" label="Host" required="true"> <cnsl-form-field class="smtp-form-field" label="Host And Port" required="true">
<cnsl-label>{{ 'SETTING.SMTP.HOST' | translate }}</cnsl-label> <cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
<input cnslInput name="host" formControlName="host" /> <input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="smtp-form-field" label="User" required="true"> <cnsl-form-field class="smtp-form-field" label="User" required="true">

View File

@ -59,7 +59,7 @@ export class NotificationSettingsComponent implements OnInit {
senderAddress: [{ disabled: true, value: '' }, [Validators.required]], senderAddress: [{ disabled: true, value: '' }, [Validators.required]],
senderName: [{ disabled: true, value: '' }, [Validators.required]], senderName: [{ disabled: true, value: '' }, [Validators.required]],
tls: [{ disabled: true, value: true }, [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]], user: [{ disabled: true, value: '' }, [Validators.required]],
}); });
} }
@ -85,6 +85,7 @@ export class NotificationSettingsComponent implements OnInit {
if (smtpConfig.smtpConfig) { if (smtpConfig.smtpConfig) {
this.hasSMTPConfig = true; this.hasSMTPConfig = true;
this.form.patchValue(smtpConfig.smtpConfig); this.form.patchValue(smtpConfig.smtpConfig);
this.form.patchValue({ ['hostAndPort']: smtpConfig.smtpConfig.host });
} }
}) })
.catch((error) => { .catch((error) => {
@ -139,7 +140,7 @@ export class NotificationSettingsComponent implements OnInit {
private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse> { private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse> {
if (this.hasSMTPConfig) { if (this.hasSMTPConfig) {
const req = new UpdateSMTPConfigRequest(); const req = new UpdateSMTPConfigRequest();
req.setHost(this.host?.value ?? ''); req.setHost(this.hostAndPort?.value ?? '');
req.setSenderAddress(this.senderAddress?.value ?? ''); req.setSenderAddress(this.senderAddress?.value ?? '');
req.setSenderName(this.senderName?.value ?? ''); req.setSenderName(this.senderName?.value ?? '');
req.setTls(this.tls?.value ?? false); req.setTls(this.tls?.value ?? false);
@ -148,7 +149,7 @@ export class NotificationSettingsComponent implements OnInit {
return this.service.updateSMTPConfig(req); return this.service.updateSMTPConfig(req);
} else { } else {
const req = new AddSMTPConfigRequest(); const req = new AddSMTPConfigRequest();
req.setHost(this.host?.value ?? ''); req.setHost(this.hostAndPort?.value ?? '');
req.setSenderAddress(this.senderAddress?.value ?? ''); req.setSenderAddress(this.senderAddress?.value ?? '');
req.setSenderName(this.senderName?.value ?? ''); req.setSenderName(this.senderName?.value ?? '');
req.setTls(this.tls?.value ?? false); req.setTls(this.tls?.value ?? false);
@ -305,7 +306,7 @@ export class NotificationSettingsComponent implements OnInit {
return this.form.get('user'); return this.form.get('user');
} }
public get host(): AbstractControl | null { public get hostAndPort(): AbstractControl | null {
return this.form.get('host'); return this.form.get('hostAndPort');
} }
} }

View File

@ -4,7 +4,7 @@
<div mat-dialog-content> <div mat-dialog-content>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ data.i18nLabel | translate }}</cnsl-label> <cnsl-label>{{ data.i18nLabel | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="password" /> <input cnslInput [(ngModel)]="password" type="password" autocomplete="new-password" />
</cnsl-form-field> </cnsl-form-field>
</div> </div>
<div mat-dialog-actions class="action"> <div mat-dialog-actions class="action">

View File

@ -916,7 +916,7 @@
"TITLE": "SMTP Einstellungen", "TITLE": "SMTP Einstellungen",
"SENDERADDRESS": "Sender Email-Adresse", "SENDERADDRESS": "Sender Email-Adresse",
"SENDERNAME": "Sender Name", "SENDERNAME": "Sender Name",
"HOST": "Host", "HOSTANDPORT": "Host und Port",
"USER": "Benutzer", "USER": "Benutzer",
"PASSWORD": "Passwort", "PASSWORD": "Passwort",
"SETPASSWORD": "SMTP Passwort setzen", "SETPASSWORD": "SMTP Passwort setzen",

View File

@ -914,9 +914,9 @@
}, },
"SMTP": { "SMTP": {
"TITLE": "SMTP Settings", "TITLE": "SMTP Settings",
"SENDERADDRESS": "Sender Email address", "SENDERADDRESS": "Sender Email Address",
"SENDERNAME": "Sender Name", "SENDERNAME": "Sender Name",
"HOST": "Host", "HOSTANDPORT": "Host And Port",
"USER": "User", "USER": "User",
"PASSWORD": "Password", "PASSWORD": "Password",
"SETPASSWORD": "Set SMTP Password", "SETPASSWORD": "Set SMTP Password",

View File

@ -916,13 +916,13 @@
"TITLE": "Paramètres SMTP", "TITLE": "Paramètres SMTP",
"SENDERADDRESS": "Adresse e-mail de l'expéditeur", "SENDERADDRESS": "Adresse e-mail de l'expéditeur",
"SENDERNAME": "Nom de l'expéditeur", "SENDERNAME": "Nom de l'expéditeur",
"HOST": "Hôte", "HOSTANDPORT": "Hôte et port",
"USER": "Utilisateur", "USER": "Utilisateur",
"PASSWORD": "Mot de passe", "PASSWORD": "Mot de passe",
"SETPASSWORD": "Définir le mot de passe SMTP", "SETPASSWORD": "Définir le mot de passe SMTP",
"PASSWORDSET": "Le mot de passe SMTP a été défini avec succès.", "PASSWORDSET": "Le mot de passe SMTP a été défini avec succès.",
"TLS": "Sécurité de la couche de transport (TLS)", "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." "REQUIREDWARN": "Pour envoyer des notifications depuis votre domaine, vous devez entrer vos données SMTP."
}, },
"SMS": { "SMS": {

View File

@ -917,7 +917,7 @@
"TITLE": "Impostazioni SMTP", "TITLE": "Impostazioni SMTP",
"SENDERADDRESS": "Indirizzo email del mittente", "SENDERADDRESS": "Indirizzo email del mittente",
"SENDERNAME": "Nome del mittente", "SENDERNAME": "Nome del mittente",
"HOST": "Host", "HOSTANDPORT": "Host e porta",
"USER": "Utente", "USER": "Utente",
"PASSWORD": "Password", "PASSWORD": "Password",
"SETPASSWORD": "Imposta SMTP Password", "SETPASSWORD": "Imposta SMTP Password",

View File

@ -916,7 +916,7 @@
"TITLE": "SMTP 设置", "TITLE": "SMTP 设置",
"SENDERADDRESS": "发件人地址", "SENDERADDRESS": "发件人地址",
"SENDERNAME": "发件人名称", "SENDERNAME": "发件人名称",
"HOST": "服务器地址", "HOSTANDPORT": "主机和端口",
"USER": "用户名", "USER": "用户名",
"PASSWORD": "密码", "PASSWORD": "密码",
"SETPASSWORD": "设置 SMTP 密码", "SETPASSWORD": "设置 SMTP 密码",

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"net"
"strings" "strings"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
@ -98,13 +99,14 @@ func (c *Commands) RemoveSMTPConfig(ctx context.Context) (*domain.ObjectDetails,
}, nil }, 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) { return func() (preparation.CreateCommands, error) {
if from = strings.TrimSpace(from); from == "" { if from = strings.TrimSpace(from); from == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-mruNY", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-mruNY", "Errors.Invalid.Argument")
} }
if host = strings.TrimSpace(host); host == "" { hostAndPort = strings.TrimSpace(hostAndPort)
return nil, errors.ThrowInvalidArgument(nil, "INST-SF3g1", "Errors.Invalid.Argument") 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
fromSplitted := strings.Split(from, "@") fromSplitted := strings.Split(from, "@")
@ -134,7 +136,7 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, host,
tls, tls,
from, from,
name, name,
host, hostAndPort,
user, user,
smtpPassword, 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) { return func() (preparation.CreateCommands, error) {
if from = strings.TrimSpace(from); from == "" { if from = strings.TrimSpace(from); from == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument")
} }
if host = strings.TrimSpace(host); host == "" { hostAndPort = strings.TrimSpace(hostAndPort)
return nil, errors.ThrowInvalidArgument(nil, "INST-VDwvq", "Errors.Invalid.Argument") 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
fromSplitted := strings.Split(from, "@") fromSplitted := strings.Split(from, "@")
senderDomain := fromSplitted[len(fromSplitted)-1] senderDomain := fromSplitted[len(fromSplitted)-1]
@ -172,7 +174,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho
tls, tls,
from, from,
name, name,
host, hostAndPort,
user, user,
) )
if err != nil { if err != nil {

View File

@ -60,7 +60,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
From: "from@domain.ch", From: "from@domain.ch",
FromName: "name", FromName: "name",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host", Host: "host:587",
User: "user", User: "user",
Password: "password", Password: "password",
}, },
@ -95,7 +95,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
), ),
@ -110,7 +110,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
From: "from@domain.ch", From: "from@domain.ch",
FromName: "name", FromName: "name",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host", Host: "host:587",
User: "user", User: "user",
Password: "password", Password: "password",
}, },
@ -150,7 +150,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
@ -165,6 +165,30 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
), ),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(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{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
smtp: &smtp.EmailConfig{ 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{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE", ResourceOwner: "INSTANCE",
@ -253,7 +361,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
From: "from@domain.ch", From: "from@domain.ch",
FromName: "name", FromName: "name",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host", Host: "host:587",
User: "user", User: "user",
}, },
}, },
@ -288,7 +396,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
), ),
@ -303,7 +411,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
From: "from@wrongdomain.ch", From: "from@wrongdomain.ch",
FromName: "name", FromName: "name",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host", Host: "host:587",
User: "user", User: "user",
}, },
}, },
@ -338,7 +446,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
), ),
@ -353,7 +461,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
From: "from@domain.ch", From: "from@domain.ch",
FromName: "name", FromName: "name",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host", Host: "host:587",
User: "user", User: "user",
}, },
}, },
@ -388,7 +496,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
), ),
@ -403,7 +511,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
false, false,
"from2@domain.ch", "from2@domain.ch",
"name2", "name2",
"host2", "host2:587",
"user2", "user2",
), ),
), ),
@ -418,7 +526,118 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
From: "from2@domain.ch", From: "from2@domain.ch",
FromName: "name2", FromName: "name2",
SMTP: smtp.SMTP{ 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", User: "user2",
}, },
}, },
@ -497,7 +716,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
true, true,
"from", "from",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
), ),
@ -600,7 +819,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
true, true,
"from", "from",
"name", "name",
"host", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
), ),