feat: add reply-to header in email notification (#6393)

* feat: add reply-to header to smtp messages

* fix: grpc reply_to_address min 0 and js var name

* fix: add missing translations

* fix merge and linting

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Miguel Cabrerizo 2023-08-29 09:08:24 +02:00 committed by GitHub
parent 9b43e28c23
commit fd00ac533a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 307 additions and 120 deletions

View File

@ -371,10 +371,10 @@ SystemDefaults:
MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE
ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE
PasswordHasher: PasswordHasher:
# Set hasher configuration for user passwords. # Set hasher configuration for user passwords.
# Passwords previously hashed with a different algorithm # Passwords previously hashed with a different algorithm
# or cost are automatically re-hashed using this config, # or cost are automatically re-hashed using this config,
# upon password validation or update. # upon password validation or update.
Hasher: Hasher:
Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM
Cost: 14 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_COST Cost: 14 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_COST
@ -688,6 +688,7 @@ DefaultInstance:
# If the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false # If the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
From: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROM From: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROM
FromName: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROMNAME FromName: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROMNAME
ReplyToAddress: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_REPLYTOADDRESS
MessageTexts: MessageTexts:
- MessageTextType: InitCode - MessageTextType: InitCode
Language: de Language: de

View File

@ -15,12 +15,17 @@
<form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off"> <form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off">
<cnsl-form-field class="smtp-form-field" label="Sender Address" required="true"> <cnsl-form-field class="smtp-form-field" label="Sender Address" required="true">
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label> <cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
<input cnslInput name="senderAddress" formControlName="senderAddress" /> <input cnslInput name="senderAddress" formControlName="senderAddress" required />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="smtp-form-field" label="Sender Name" required="true"> <cnsl-form-field class="smtp-form-field" label="Sender Name" required="true">
<cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label> <cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label>
<input cnslInput name="senderName" formControlName="senderName" /> <input cnslInput name="senderName" formControlName="senderName" required />
</cnsl-form-field>
<cnsl-form-field class="smtp-form-field" label="Reply-To Address">
<cnsl-label>{{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }}</cnsl-label>
<input cnslInput name="senderReplyToAddress" formControlName="replyToAddress" />
</cnsl-form-field> </cnsl-form-field>
<mat-checkbox class="smtp-checkbox" formControlName="tls"> <mat-checkbox class="smtp-checkbox" formControlName="tls">
@ -29,12 +34,12 @@
<cnsl-form-field class="smtp-form-field" label="Host And Port" required="true"> <cnsl-form-field class="smtp-form-field" label="Host And Port" required="true">
<cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label> <cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" /> <input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" required />
</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">
<cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label> <cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label>
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" /> <input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" required />
</cnsl-form-field> </cnsl-form-field>
<button <button

View File

@ -58,6 +58,7 @@ export class NotificationSettingsComponent implements OnInit {
this.form = this.fb.group({ this.form = this.fb.group({
senderAddress: [{ disabled: true, value: '' }, [requiredValidator]], senderAddress: [{ disabled: true, value: '' }, [requiredValidator]],
senderName: [{ disabled: true, value: '' }, [requiredValidator]], senderName: [{ disabled: true, value: '' }, [requiredValidator]],
replyToAddress: [{ disabled: true, value: '' }],
tls: [{ disabled: true, value: true }, [requiredValidator]], tls: [{ disabled: true, value: true }, [requiredValidator]],
hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]], hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]],
user: [{ disabled: true, value: '' }, [requiredValidator]], user: [{ disabled: true, value: '' }, [requiredValidator]],
@ -143,6 +144,7 @@ export class NotificationSettingsComponent implements OnInit {
req.setHost(this.hostAndPort?.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.setReplyToAddress(this.replyToAddress?.value ?? '');
req.setTls(this.tls?.value ?? false); req.setTls(this.tls?.value ?? false);
req.setUser(this.user?.value ?? ''); req.setUser(this.user?.value ?? '');
@ -152,6 +154,7 @@ export class NotificationSettingsComponent implements OnInit {
req.setHost(this.hostAndPort?.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.setReplyToAddress(this.replyToAddress?.value ?? '');
req.setTls(this.tls?.value ?? false); req.setTls(this.tls?.value ?? false);
req.setUser(this.user?.value ?? ''); req.setUser(this.user?.value ?? '');
@ -298,6 +301,10 @@ export class NotificationSettingsComponent implements OnInit {
return this.form.get('senderName'); return this.form.get('senderName');
} }
public get replyToAddress(): AbstractControl | null {
return this.form.get('replyToAddress');
}
public get tls(): AbstractControl | null { public get tls(): AbstractControl | null {
return this.form.get('tls'); return this.form.get('tls');
} }

View File

@ -1049,6 +1049,7 @@
"TITLE": "SMTP настройки", "TITLE": "SMTP настройки",
"SENDERADDRESS": "Имейл адрес на изпращача", "SENDERADDRESS": "Имейл адрес на изпращача",
"SENDERNAME": "Име на изпращача", "SENDERNAME": "Име на изпращача",
"REPLYTOADDRESS": "Reply-to адрес",
"HOSTANDPORT": "Хост и порт", "HOSTANDPORT": "Хост и порт",
"USER": "Потребител", "USER": "Потребител",
"PASSWORD": "Парола", "PASSWORD": "Парола",

View File

@ -1055,6 +1055,7 @@
"TITLE": "SMTP Einstellungen", "TITLE": "SMTP Einstellungen",
"SENDERADDRESS": "Sender Email-Adresse", "SENDERADDRESS": "Sender Email-Adresse",
"SENDERNAME": "Sender Name", "SENDERNAME": "Sender Name",
"REPLYTOADDRESS": "Reply-to-Adresse",
"HOSTANDPORT": "Host und Port", "HOSTANDPORT": "Host und Port",
"USER": "Benutzer", "USER": "Benutzer",
"PASSWORD": "Passwort", "PASSWORD": "Passwort",

View File

@ -1056,6 +1056,7 @@
"TITLE": "SMTP Settings", "TITLE": "SMTP Settings",
"SENDERADDRESS": "Sender Email Address", "SENDERADDRESS": "Sender Email Address",
"SENDERNAME": "Sender Name", "SENDERNAME": "Sender Name",
"REPLYTOADDRESS": "Reply-to Address",
"HOSTANDPORT": "Host And Port", "HOSTANDPORT": "Host And Port",
"USER": "User", "USER": "User",
"PASSWORD": "Password", "PASSWORD": "Password",

View File

@ -1056,6 +1056,7 @@
"TITLE": "Ajustes SMTP", "TITLE": "Ajustes SMTP",
"SENDERADDRESS": "Dirección email del emisor", "SENDERADDRESS": "Dirección email del emisor",
"SENDERNAME": "Nombre del emisor", "SENDERNAME": "Nombre del emisor",
"REPLYTOADDRESS": "Dirección Reply-To",
"HOSTANDPORT": "Servidor y puerto", "HOSTANDPORT": "Servidor y puerto",
"USER": "Usuario", "USER": "Usuario",
"PASSWORD": "Contraseña", "PASSWORD": "Contraseña",

View File

@ -1055,6 +1055,7 @@
"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",
"REPLYTOADDRESS": "Adresse Reply-to",
"HOSTANDPORT": "Hôte et port", "HOSTANDPORT": "Hôte et port",
"USER": "Utilisateur", "USER": "Utilisateur",
"PASSWORD": "Mot de passe", "PASSWORD": "Mot de passe",

View File

@ -1055,6 +1055,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",
"REPLYTOADDRESS": "Indirizzo Reply-to",
"HOSTANDPORT": "Host e porta", "HOSTANDPORT": "Host e porta",
"USER": "Utente", "USER": "Utente",
"PASSWORD": "Password", "PASSWORD": "Password",

View File

@ -1056,6 +1056,7 @@
"TITLE": "SMTP設定", "TITLE": "SMTP設定",
"SENDERADDRESS": "送信者のメールアドレス", "SENDERADDRESS": "送信者のメールアドレス",
"SENDERNAME": "送信者名", "SENDERNAME": "送信者名",
"REPLYTOADDRESS": "返信先アドレス",
"HOSTANDPORT": "ホストとポート", "HOSTANDPORT": "ホストとポート",
"USER": "ユーザー", "USER": "ユーザー",
"PASSWORD": "パスワード", "PASSWORD": "パスワード",

View File

@ -1056,6 +1056,7 @@
"TITLE": "SMTP подесувања", "TITLE": "SMTP подесувања",
"SENDERADDRESS": "Адреса на испраќачот", "SENDERADDRESS": "Адреса на испраќачот",
"SENDERNAME": "Име на испраќачот", "SENDERNAME": "Име на испраќачот",
"REPLYTOADDRESS": "Reply-to адреса",
"HOSTANDPORT": "Host и Port", "HOSTANDPORT": "Host и Port",
"USER": "Корисник", "USER": "Корисник",
"PASSWORD": "Лозинка", "PASSWORD": "Лозинка",

View File

@ -1055,6 +1055,7 @@
"TITLE": "Ustawienia SMTP", "TITLE": "Ustawienia SMTP",
"SENDERADDRESS": "Adres e-mail nadawcy", "SENDERADDRESS": "Adres e-mail nadawcy",
"SENDERNAME": "Nazwa nadawcy", "SENDERNAME": "Nazwa nadawcy",
"REPLYTOADDRESS": "Adres Reply-to",
"HOSTANDPORT": "Host i port", "HOSTANDPORT": "Host i port",
"USER": "Użytkownik", "USER": "Użytkownik",
"PASSWORD": "Hasło", "PASSWORD": "Hasło",

View File

@ -1056,6 +1056,7 @@
"TITLE": "Configurações SMTP", "TITLE": "Configurações SMTP",
"SENDERADDRESS": "Endereço de e-mail do remetente", "SENDERADDRESS": "Endereço de e-mail do remetente",
"SENDERNAME": "Nome do remetente", "SENDERNAME": "Nome do remetente",
"REPLYTOADDRESS": "Endereço Reply-To",
"HOSTANDPORT": "Host e porta", "HOSTANDPORT": "Host e porta",
"USER": "Usuário", "USER": "Usuário",
"PASSWORD": "Senha", "PASSWORD": "Senha",

View File

@ -1055,6 +1055,7 @@
"TITLE": "SMTP 设置", "TITLE": "SMTP 设置",
"SENDERADDRESS": "发件人地址", "SENDERADDRESS": "发件人地址",
"SENDERNAME": "发件人名称", "SENDERNAME": "发件人名称",
"REPLYTOADDRESS": "Reply-to 地址",
"HOSTANDPORT": "主机和端口", "HOSTANDPORT": "主机和端口",
"USER": "用户名", "USER": "用户名",
"PASSWORD": "密码", "PASSWORD": "密码",

View File

@ -200,6 +200,7 @@ DefaultInstance:
# if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false # if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
From: From:
FromName: FromName:
ReplyToAddress:
``` ```
- If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/self-hosting/manage/configure#database-initialization), you can provide a FirstInstance YAML section using the --steps argument. - If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/self-hosting/manage/configure#database-initialization), you can provide a FirstInstance YAML section using the --steps argument.

View File

@ -132,9 +132,10 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType)
func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config { func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
return &smtp.Config{ return &smtp.Config{
Tls: req.Tls, Tls: req.Tls,
From: req.SenderAddress, From: req.SenderAddress,
FromName: req.SenderName, FromName: req.SenderName,
ReplyToAddress: req.ReplyToAddress,
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: req.Host, Host: req.Host,
User: req.User, User: req.User,
@ -145,9 +146,10 @@ func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config { func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
return &smtp.Config{ return &smtp.Config{
Tls: req.Tls, Tls: req.Tls,
From: req.SenderAddress, From: req.SenderAddress,
FromName: req.SenderName, FromName: req.SenderName,
ReplyToAddress: req.ReplyToAddress,
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: req.Host, Host: req.Host,
User: req.User, User: req.User,
@ -157,12 +159,13 @@ func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig { func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
mapped := &settings_pb.SMTPConfig{ mapped := &settings_pb.SMTPConfig{
Tls: smtp.TLS, Tls: smtp.TLS,
SenderAddress: smtp.SenderAddress, SenderAddress: smtp.SenderAddress,
SenderName: smtp.SenderName, SenderName: smtp.SenderName,
Host: smtp.Host, ReplyToAddress: smtp.ReplyToAddress,
User: smtp.User, Host: smtp.Host,
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID), User: smtp.User,
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID),
} }
return mapped return mapped
} }

View File

@ -414,6 +414,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
instanceAgg, instanceAgg,
setup.SMTPConfiguration.From, setup.SMTPConfiguration.From,
setup.SMTPConfiguration.FromName, setup.SMTPConfiguration.FromName,
setup.SMTPConfiguration.ReplyToAddress,
setup.SMTPConfiguration.SMTP.Host, setup.SMTPConfiguration.SMTP.Host,
setup.SMTPConfiguration.SMTP.User, setup.SMTPConfiguration.SMTP.User,
[]byte(setup.SMTPConfiguration.SMTP.Password), []byte(setup.SMTPConfiguration.SMTP.Password),

View File

@ -12,13 +12,14 @@ import (
type InstanceSMTPConfigWriteModel struct { type InstanceSMTPConfigWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
SenderAddress string SenderAddress string
SenderName string SenderName string
TLS bool ReplyToAddress string
Host string TLS bool
User string Host string
Password *crypto.CryptoValue User string
State domain.SMTPConfigState Password *crypto.CryptoValue
State domain.SMTPConfigState
domain string domain string
domainState domain.InstanceDomainState domainState domain.InstanceDomainState
@ -62,6 +63,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
wm.TLS = e.TLS wm.TLS = e.TLS
wm.SenderAddress = e.SenderAddress wm.SenderAddress = e.SenderAddress
wm.SenderName = e.SenderName wm.SenderName = e.SenderName
wm.ReplyToAddress = e.ReplyToAddress
wm.Host = e.Host wm.Host = e.Host
wm.User = e.User wm.User = e.User
wm.Password = e.Password wm.Password = e.Password
@ -76,6 +78,9 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
if e.FromName != nil { if e.FromName != nil {
wm.SenderName = *e.FromName wm.SenderName = *e.FromName
} }
if e.ReplyToAddress != nil {
wm.ReplyToAddress = *e.ReplyToAddress
}
if e.Host != nil { if e.Host != nil {
wm.Host = *e.Host wm.Host = *e.Host
} }
@ -87,6 +92,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
wm.TLS = false wm.TLS = false
wm.SenderName = "" wm.SenderName = ""
wm.SenderAddress = "" wm.SenderAddress = ""
wm.ReplyToAddress = ""
wm.Host = "" wm.Host = ""
wm.User = "" wm.User = ""
wm.Password = nil wm.Password = nil
@ -122,7 +128,7 @@ func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
Builder() Builder()
} }
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) { func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
changes := make([]instance.SMTPConfigChanges, 0) changes := make([]instance.SMTPConfigChanges, 0)
var err error var err error
@ -135,6 +141,9 @@ func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, agg
if wm.SenderName != fromName { if wm.SenderName != fromName {
changes = append(changes, instance.ChangeSMTPConfigFromName(fromName)) changes = append(changes, instance.ChangeSMTPConfigFromName(fromName))
} }
if wm.ReplyToAddress != replyToAddress {
changes = append(changes, instance.ChangeSMTPConfigReplyToAddress(replyToAddress))
}
if wm.Host != smtpHost { if wm.Host != smtpHost {
changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost)) changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost))
} }

View File

@ -17,7 +17,7 @@ import (
func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) { func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls) validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,7 +35,7 @@ func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*dom
func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) { func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.SMTP.Host, config.SMTP.User, config.Tls) validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, config.Tls)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil { if err != nil {
return nil, err return nil, err
@ -99,11 +99,14 @@ func (c *Commands) RemoveSMTPConfig(ctx context.Context) (*domain.ObjectDetails,
}, nil }, nil
} }
func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, password []byte, tls bool) preparation.Validation { func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, replyTo, 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")
} }
replyTo = strings.TrimSpace(replyTo)
hostAndPort = strings.TrimSpace(hostAndPort) hostAndPort = strings.TrimSpace(hostAndPort)
if _, _, err := net.SplitHostPort(hostAndPort); err != nil { if _, _, err := net.SplitHostPort(hostAndPort); err != nil {
return nil, errors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument")
@ -136,6 +139,7 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostA
tls, tls,
from, from,
name, name,
replyTo,
hostAndPort, hostAndPort,
user, user,
smtpPassword, smtpPassword,
@ -145,11 +149,13 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostA
} }
} }
func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, tls bool) preparation.Validation { func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, replyTo, 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")
} }
replyTo = strings.TrimSpace(replyTo)
hostAndPort = strings.TrimSpace(hostAndPort) hostAndPort = strings.TrimSpace(hostAndPort)
if _, _, err := net.SplitHostPort(hostAndPort); err != nil { if _, _, err := net.SplitHostPort(hostAndPort); err != nil {
return nil, errors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument")
@ -174,6 +180,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho
tls, tls,
from, from,
name, name,
replyTo,
hostAndPort, hostAndPort,
user, user,
) )

View File

@ -95,6 +95,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -106,9 +107,10 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
smtp: &smtp.Config{ smtp: &smtp.Config{
Tls: true, Tls: true,
From: "from@domain.ch", From: "from@domain.ch",
FromName: "name", FromName: "name",
ReplyToAddress: "",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host:587", Host: "host:587",
User: "user", User: "user",
@ -150,6 +152,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{ &crypto.CryptoValue{
@ -184,6 +187,72 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
}, },
}, },
}, },
{
name: "add smtp config with reply to address, ok",
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",
"replyto@domain.ch",
"host:587",
"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.Config{
Tls: true,
From: "from@domain.ch",
FromName: "name",
ReplyToAddress: "replyto@domain.ch",
SMTP: smtp.SMTP{
Host: "host:587",
User: "user",
Password: "password",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{ {
name: "smtp config, port is missing", name: "smtp config, port is missing",
fields: fields{ fields: fields{
@ -258,6 +327,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"[2001:db8::1]:2525", "[2001:db8::1]:2525",
"user", "user",
&crypto.CryptoValue{ &crypto.CryptoValue{
@ -396,6 +466,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -446,6 +517,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -496,6 +568,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -511,6 +584,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
false, false,
"from2@domain.ch", "from2@domain.ch",
"name2", "name2",
"replyto@domain.ch",
"host2:587", "host2:587",
"user2", "user2",
), ),
@ -522,9 +596,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
smtp: &smtp.Config{ smtp: &smtp.Config{
Tls: false, Tls: false,
From: "from2@domain.ch", From: "from2@domain.ch",
FromName: "name2", FromName: "name2",
ReplyToAddress: "replyto@domain.ch",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "host2:587", Host: "host2:587",
User: "user2", User: "user2",
@ -607,6 +682,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
true, true,
"from@domain.ch", "from@domain.ch",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -622,6 +698,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
false, false,
"from2@domain.ch", "from2@domain.ch",
"name2", "name2",
"replyto@domain.ch",
"[2001:db8::1]:2525", "[2001:db8::1]:2525",
"user2", "user2",
), ),
@ -633,9 +710,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
smtp: &smtp.Config{ smtp: &smtp.Config{
Tls: false, Tls: false,
From: "from2@domain.ch", From: "from2@domain.ch",
FromName: "name2", FromName: "name2",
ReplyToAddress: "replyto@domain.ch",
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: "[2001:db8::1]:2525", Host: "[2001:db8::1]:2525",
User: "user2", User: "user2",
@ -716,6 +794,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
true, true,
"from", "from",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -819,6 +898,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
true, true,
"from", "from",
"name", "name",
"",
"host:587", "host:587",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -868,11 +948,12 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
} }
} }
func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, host, user string) *instance.SMTPConfigChangedEvent { func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, replyTo, host, user string) *instance.SMTPConfigChangedEvent {
changes := []instance.SMTPConfigChanges{ changes := []instance.SMTPConfigChanges{
instance.ChangeSMTPConfigTLS(tls), instance.ChangeSMTPConfigTLS(tls),
instance.ChangeSMTPConfigFromAddress(fromAddress), instance.ChangeSMTPConfigFromAddress(fromAddress),
instance.ChangeSMTPConfigFromName(fromName), instance.ChangeSMTPConfigFromName(fromName),
instance.ChangeSMTPConfigReplyToAddress(replyTo),
instance.ChangeSMTPConfigSMTPHost(host), instance.ChangeSMTPConfigSMTPHost(host),
instance.ChangeSMTPConfigSMTPUser(user), instance.ChangeSMTPConfigSMTPUser(user),
} }

View File

@ -17,9 +17,10 @@ import (
var _ channels.NotificationChannel = (*Email)(nil) var _ channels.NotificationChannel = (*Email)(nil)
type Email struct { type Email struct {
smtpClient *smtp.Client smtpClient *smtp.Client
senderAddress string senderAddress string
senderName string senderName string
replyToAddress string
} }
func InitChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*Config, error)) (*Email, error) { func InitChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*Config, error)) (*Email, error) {
@ -36,9 +37,10 @@ func InitChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*
logging.New().Debug("successfully initialized smtp email channel") logging.New().Debug("successfully initialized smtp email channel")
return &Email{ return &Email{
smtpClient: client, smtpClient: client,
senderName: smtpConfig.FromName, senderName: smtpConfig.FromName,
senderAddress: smtpConfig.From, senderAddress: smtpConfig.From,
replyToAddress: smtpConfig.ReplyToAddress,
}, nil }, nil
} }
@ -54,6 +56,7 @@ func (email *Email) HandleMessage(message channels.Message) error {
} }
emailMsg.SenderEmail = email.senderAddress emailMsg.SenderEmail = email.senderAddress
emailMsg.SenderName = email.senderName emailMsg.SenderName = email.senderName
emailMsg.ReplyToAddress = email.replyToAddress
// To && From // To && From
if err := email.smtpClient.Mail(emailMsg.SenderEmail); err != nil { if err := email.smtpClient.Mail(emailMsg.SenderEmail); err != nil {
return caos_errs.ThrowInternalf(err, "EMAIL-s3is3", "could not set sender: %v", emailMsg.SenderEmail) return caos_errs.ThrowInternalf(err, "EMAIL-s3is3", "could not set sender: %v", emailMsg.SenderEmail)

View File

@ -1,10 +1,11 @@
package smtp package smtp
type Config struct { type Config struct {
SMTP SMTP SMTP SMTP
Tls bool Tls bool
From string From string
FromName string FromName string
ReplyToAddress string
} }
type SMTP struct { type SMTP struct {

View File

@ -19,9 +19,10 @@ func (n *NotificationQueries) GetSMTPConfig(ctx context.Context) (*smtp.Config,
return nil, err return nil, err
} }
return &smtp.Config{ return &smtp.Config{
From: config.SenderAddress, From: config.SenderAddress,
FromName: config.SenderName, FromName: config.SenderName,
Tls: config.TLS, ReplyToAddress: config.ReplyToAddress,
Tls: config.TLS,
SMTP: smtp.SMTP{ SMTP: smtp.SMTP{
Host: config.Host, Host: config.Host,
User: config.User, User: config.User,

View File

@ -23,6 +23,7 @@ type Email struct {
CC []string CC []string
SenderEmail string SenderEmail string
SenderName string SenderName string
ReplyToAddress string
Subject string Subject string
Content string Content string
TriggeringEvent eventstore.Event TriggeringEvent eventstore.Event
@ -35,6 +36,9 @@ func (msg *Email) GetContent() (string, error) {
from = fmt.Sprintf("%s <%s>", msg.SenderName, msg.SenderEmail) from = fmt.Sprintf("%s <%s>", msg.SenderName, msg.SenderEmail)
} }
headers["From"] = from headers["From"] = from
if msg.ReplyToAddress != "" {
headers["Reply-to"] = msg.ReplyToAddress
}
headers["Return-Path"] = msg.SenderEmail headers["Return-Path"] = msg.SenderEmail
headers["To"] = strings.Join(msg.Recipients, ", ") headers["To"] = strings.Join(msg.Recipients, ", ")
headers["Cc"] = strings.Join(msg.CC, ", ") headers["Cc"] = strings.Join(msg.CC, ", ")

View File

@ -11,20 +11,21 @@ import (
) )
const ( const (
SMTPConfigProjectionTable = "projections.smtp_configs" SMTPConfigProjectionTable = "projections.smtp_configs1"
SMTPConfigColumnAggregateID = "aggregate_id" SMTPConfigColumnAggregateID = "aggregate_id"
SMTPConfigColumnCreationDate = "creation_date" SMTPConfigColumnCreationDate = "creation_date"
SMTPConfigColumnChangeDate = "change_date" SMTPConfigColumnChangeDate = "change_date"
SMTPConfigColumnSequence = "sequence" SMTPConfigColumnSequence = "sequence"
SMTPConfigColumnResourceOwner = "resource_owner" SMTPConfigColumnResourceOwner = "resource_owner"
SMTPConfigColumnInstanceID = "instance_id" SMTPConfigColumnInstanceID = "instance_id"
SMTPConfigColumnTLS = "tls" SMTPConfigColumnTLS = "tls"
SMTPConfigColumnSenderAddress = "sender_address" SMTPConfigColumnSenderAddress = "sender_address"
SMTPConfigColumnSenderName = "sender_name" SMTPConfigColumnSenderName = "sender_name"
SMTPConfigColumnSMTPHost = "host" SMTPConfigColumnReplyToAddress = "reply_to_address"
SMTPConfigColumnSMTPUser = "username" SMTPConfigColumnSMTPHost = "host"
SMTPConfigColumnSMTPPassword = "password" SMTPConfigColumnSMTPUser = "username"
SMTPConfigColumnSMTPPassword = "password"
) )
type smtpConfigProjection struct { type smtpConfigProjection struct {
@ -46,6 +47,7 @@ func newSMTPConfigProjection(ctx context.Context, config crdb.StatementHandlerCo
crdb.NewColumn(SMTPConfigColumnTLS, crdb.ColumnTypeBool), crdb.NewColumn(SMTPConfigColumnTLS, crdb.ColumnTypeBool),
crdb.NewColumn(SMTPConfigColumnSenderAddress, crdb.ColumnTypeText), crdb.NewColumn(SMTPConfigColumnSenderAddress, crdb.ColumnTypeText),
crdb.NewColumn(SMTPConfigColumnSenderName, crdb.ColumnTypeText), crdb.NewColumn(SMTPConfigColumnSenderName, crdb.ColumnTypeText),
crdb.NewColumn(SMTPConfigColumnReplyToAddress, crdb.ColumnTypeText),
crdb.NewColumn(SMTPConfigColumnSMTPHost, crdb.ColumnTypeText), crdb.NewColumn(SMTPConfigColumnSMTPHost, crdb.ColumnTypeText),
crdb.NewColumn(SMTPConfigColumnSMTPUser, crdb.ColumnTypeText), crdb.NewColumn(SMTPConfigColumnSMTPUser, crdb.ColumnTypeText),
crdb.NewColumn(SMTPConfigColumnSMTPPassword, crdb.ColumnTypeJSONB, crdb.Nullable()), crdb.NewColumn(SMTPConfigColumnSMTPPassword, crdb.ColumnTypeJSONB, crdb.Nullable()),
@ -104,6 +106,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h
handler.NewCol(SMTPConfigColumnTLS, e.TLS), handler.NewCol(SMTPConfigColumnTLS, e.TLS),
handler.NewCol(SMTPConfigColumnSenderAddress, e.SenderAddress), handler.NewCol(SMTPConfigColumnSenderAddress, e.SenderAddress),
handler.NewCol(SMTPConfigColumnSenderName, e.SenderName), handler.NewCol(SMTPConfigColumnSenderName, e.SenderName),
handler.NewCol(SMTPConfigColumnReplyToAddress, e.ReplyToAddress),
handler.NewCol(SMTPConfigColumnSMTPHost, e.Host), handler.NewCol(SMTPConfigColumnSMTPHost, e.Host),
handler.NewCol(SMTPConfigColumnSMTPUser, e.User), handler.NewCol(SMTPConfigColumnSMTPUser, e.User),
handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password), handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password),
@ -117,7 +120,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-wl0wd", "reduce.wrong.event.type %s", instance.SMTPConfigChangedEventType) return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-wl0wd", "reduce.wrong.event.type %s", instance.SMTPConfigChangedEventType)
} }
columns := make([]handler.Column, 0, 7) columns := make([]handler.Column, 0, 8)
columns = append(columns, handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), columns = append(columns, handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()),
handler.NewCol(SMTPConfigColumnSequence, e.Sequence())) handler.NewCol(SMTPConfigColumnSequence, e.Sequence()))
if e.TLS != nil { if e.TLS != nil {
@ -129,6 +132,9 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (
if e.FromName != nil { if e.FromName != nil {
columns = append(columns, handler.NewCol(SMTPConfigColumnSenderName, *e.FromName)) columns = append(columns, handler.NewCol(SMTPConfigColumnSenderName, *e.FromName))
} }
if e.ReplyToAddress != nil {
columns = append(columns, handler.NewCol(SMTPConfigColumnReplyToAddress, *e.ReplyToAddress))
}
if e.Host != nil { if e.Host != nil {
columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPHost, *e.Host)) columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPHost, *e.Host))
} }

View File

@ -30,6 +30,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
"tls": true, "tls": true,
"senderAddress": "sender", "senderAddress": "sender",
"senderName": "name", "senderName": "name",
"replyToAddress": "reply-to",
"host": "host", "host": "host",
"user": "user" "user": "user"
}`, }`,
@ -44,13 +45,14 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs SET (change_date, sequence, tls, sender_address, sender_name, host, username) = ($1, $2, $3, $4, $5, $6, $7) WHERE (aggregate_id = $8) AND (instance_id = $9)", expectedStmt: "UPDATE projections.smtp_configs1 SET (change_date, sequence, tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9) AND (instance_id = $10)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
true, true,
"sender", "sender",
"name", "name",
"reply-to",
"host", "host",
"user", "user",
"agg-id", "agg-id",
@ -71,6 +73,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
"tls": true, "tls": true,
"senderAddress": "sender", "senderAddress": "sender",
"senderName": "name", "senderName": "name",
"replyToAddress": "reply-to",
"host": "host", "host": "host",
"user": "user", "user": "user",
"password": { "password": {
@ -89,7 +92,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.smtp_configs (aggregate_id, creation_date, change_date, resource_owner, instance_id, sequence, tls, sender_address, sender_name, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", expectedStmt: "INSERT INTO projections.smtp_configs1 (aggregate_id, creation_date, change_date, resource_owner, instance_id, sequence, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -100,6 +103,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
true, true,
"sender", "sender",
"name", "name",
"reply-to",
"host", "host",
"user", "user",
anyArg{}, anyArg{},
@ -132,7 +136,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs SET (change_date, sequence, password) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs1 SET (change_date, sequence, password) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -162,7 +166,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.smtp_configs WHERE (aggregate_id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.smtp_configs1 WHERE (aggregate_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -189,7 +193,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.smtp_configs WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.smtp_configs1 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },

View File

@ -57,6 +57,10 @@ var (
name: projection.SMTPConfigColumnSenderName, name: projection.SMTPConfigColumnSenderName,
table: smtpConfigsTable, table: smtpConfigsTable,
} }
SMTPConfigColumnReplyToAddress = Column{
name: projection.SMTPConfigColumnReplyToAddress,
table: smtpConfigsTable,
}
SMTPConfigColumnSMTPHost = Column{ SMTPConfigColumnSMTPHost = Column{
name: projection.SMTPConfigColumnSMTPHost, name: projection.SMTPConfigColumnSMTPHost,
table: smtpConfigsTable, table: smtpConfigsTable,
@ -83,12 +87,13 @@ type SMTPConfig struct {
ResourceOwner string ResourceOwner string
Sequence uint64 Sequence uint64
TLS bool TLS bool
SenderAddress string SenderAddress string
SenderName string SenderName string
Host string ReplyToAddress string
User string Host string
Password *crypto.CryptoValue User string
Password *crypto.CryptoValue
} }
func (q *Queries) SMTPConfigByAggregateID(ctx context.Context, aggregateID string) (config *SMTPConfig, err error) { func (q *Queries) SMTPConfigByAggregateID(ctx context.Context, aggregateID string) (config *SMTPConfig, err error) {
@ -123,6 +128,7 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
SMTPConfigColumnTLS.identifier(), SMTPConfigColumnTLS.identifier(),
SMTPConfigColumnSenderAddress.identifier(), SMTPConfigColumnSenderAddress.identifier(),
SMTPConfigColumnSenderName.identifier(), SMTPConfigColumnSenderName.identifier(),
SMTPConfigColumnReplyToAddress.identifier(),
SMTPConfigColumnSMTPHost.identifier(), SMTPConfigColumnSMTPHost.identifier(),
SMTPConfigColumnSMTPUser.identifier(), SMTPConfigColumnSMTPUser.identifier(),
SMTPConfigColumnSMTPPassword.identifier()). SMTPConfigColumnSMTPPassword.identifier()).
@ -139,6 +145,7 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
&config.TLS, &config.TLS,
&config.SenderAddress, &config.SenderAddress,
&config.SenderName, &config.SenderName,
&config.ReplyToAddress,
&config.Host, &config.Host,
&config.User, &config.User,
&password, &password,

View File

@ -13,18 +13,19 @@ import (
) )
var ( var (
prepareSMTPConfigStmt = `SELECT projections.smtp_configs.aggregate_id,` + prepareSMTPConfigStmt = `SELECT projections.smtp_configs1.aggregate_id,` +
` projections.smtp_configs.creation_date,` + ` projections.smtp_configs1.creation_date,` +
` projections.smtp_configs.change_date,` + ` projections.smtp_configs1.change_date,` +
` projections.smtp_configs.resource_owner,` + ` projections.smtp_configs1.resource_owner,` +
` projections.smtp_configs.sequence,` + ` projections.smtp_configs1.sequence,` +
` projections.smtp_configs.tls,` + ` projections.smtp_configs1.tls,` +
` projections.smtp_configs.sender_address,` + ` projections.smtp_configs1.sender_address,` +
` projections.smtp_configs.sender_name,` + ` projections.smtp_configs1.sender_name,` +
` projections.smtp_configs.host,` + ` projections.smtp_configs1.reply_to_address,` +
` projections.smtp_configs.username,` + ` projections.smtp_configs1.host,` +
` projections.smtp_configs.password` + ` projections.smtp_configs1.username,` +
` FROM projections.smtp_configs` + ` projections.smtp_configs1.password` +
` FROM projections.smtp_configs1` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`
prepareSMTPConfigCols = []string{ prepareSMTPConfigCols = []string{
"aggregate_id", "aggregate_id",
@ -35,6 +36,7 @@ var (
"tls", "tls",
"sender_address", "sender_address",
"sender_name", "sender_name",
"reply_to_address",
"smtp_host", "smtp_host",
"smtp_user", "smtp_user",
"smtp_password", "smtp_password",
@ -86,6 +88,7 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
true, true,
"sender", "sender",
"name", "name",
"reply-to",
"host", "host",
"user", "user",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
@ -93,17 +96,18 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
), ),
}, },
object: &SMTPConfig{ object: &SMTPConfig{
AggregateID: "agg-id", AggregateID: "agg-id",
CreationDate: testNow, CreationDate: testNow,
ChangeDate: testNow, ChangeDate: testNow,
ResourceOwner: "ro", ResourceOwner: "ro",
Sequence: 20211108, Sequence: 20211108,
TLS: true, TLS: true,
SenderAddress: "sender", SenderAddress: "sender",
SenderName: "name", SenderName: "name",
Host: "host", ReplyToAddress: "reply-to",
User: "user", Host: "host",
Password: &crypto.CryptoValue{}, User: "user",
Password: &crypto.CryptoValue{},
}, },
}, },
{ {

View File

@ -21,12 +21,13 @@ const (
type SMTPConfigAddedEvent struct { type SMTPConfigAddedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
SenderAddress string `json:"senderAddress,omitempty"` SenderAddress string `json:"senderAddress,omitempty"`
SenderName string `json:"senderName,omitempty"` SenderName string `json:"senderName,omitempty"`
TLS bool `json:"tls,omitempty"` ReplyToAddress string `json:"replyToAddress,omitempty"`
Host string `json:"host,omitempty"` TLS bool `json:"tls,omitempty"`
User string `json:"user,omitempty"` Host string `json:"host,omitempty"`
Password *crypto.CryptoValue `json:"password,omitempty"` User string `json:"user,omitempty"`
Password *crypto.CryptoValue `json:"password,omitempty"`
} }
func NewSMTPConfigAddedEvent( func NewSMTPConfigAddedEvent(
@ -35,6 +36,7 @@ func NewSMTPConfigAddedEvent(
tls bool, tls bool,
senderAddress, senderAddress,
senderName, senderName,
replyToAddress,
host, host,
user string, user string,
password *crypto.CryptoValue, password *crypto.CryptoValue,
@ -45,12 +47,13 @@ func NewSMTPConfigAddedEvent(
aggregate, aggregate,
SMTPConfigAddedEventType, SMTPConfigAddedEventType,
), ),
TLS: tls, TLS: tls,
SenderAddress: senderAddress, SenderAddress: senderAddress,
SenderName: senderName, SenderName: senderName,
Host: host, ReplyToAddress: replyToAddress,
User: user, Host: host,
Password: password, User: user,
Password: password,
} }
} }
@ -77,11 +80,12 @@ func SMTPConfigAddedEventMapper(event *repository.Event) (eventstore.Event, erro
type SMTPConfigChangedEvent struct { type SMTPConfigChangedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
FromAddress *string `json:"senderAddress,omitempty"` FromAddress *string `json:"senderAddress,omitempty"`
FromName *string `json:"senderName,omitempty"` FromName *string `json:"senderName,omitempty"`
TLS *bool `json:"tls,omitempty"` ReplyToAddress *string `json:"replyToAddress,omitempty"`
Host *string `json:"host,omitempty"` TLS *bool `json:"tls,omitempty"`
User *string `json:"user,omitempty"` Host *string `json:"host,omitempty"`
User *string `json:"user,omitempty"`
} }
func (e *SMTPConfigChangedEvent) Data() interface{} { func (e *SMTPConfigChangedEvent) Data() interface{} {
@ -133,6 +137,12 @@ func ChangeSMTPConfigFromName(senderName string) func(event *SMTPConfigChangedEv
} }
} }
func ChangeSMTPConfigReplyToAddress(replyToAddress string) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) {
e.ReplyToAddress = &replyToAddress
}
}
func ChangeSMTPConfigSMTPHost(smtpHost string) func(event *SMTPConfigChangedEvent) { func ChangeSMTPConfigSMTPHost(smtpHost string) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) { return func(e *SMTPConfigChangedEvent) {
e.Host = &smtpHost e.Host = &smtpHost

View File

@ -3842,6 +3842,14 @@ message AddSMTPConfigRequest {
example: "\"this-is-my-password\""; example: "\"this-is-my-password\"";
} }
]; ];
string reply_to_address = 7 [
(validate.rules).string = {min_len: 0, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"replyto@m.zitadel.cloud\"";
min_length: 1;
max_length: 200;
}
];
} }
message AddSMTPConfigResponse { message AddSMTPConfigResponse {
@ -3883,6 +3891,14 @@ message UpdateSMTPConfigRequest {
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\""; example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
} }
]; ];
string reply_to_address = 6 [
(validate.rules).string = {min_len: 0, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"replyto@m.zitadel.cloud\"";
min_length: 1;
max_length: 200;
}
];
} }
message UpdateSMTPConfigResponse { message UpdateSMTPConfigResponse {

View File

@ -75,6 +75,11 @@ message SMTPConfig {
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\""; example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
} }
]; ];
string reply_to_address = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"replyto@m.zitadel.cloud\"";
}
];
} }
message SMSProvider { message SMSProvider {