Support SMTP server config via setting with dynamic reload

This commit is contained in:
Denis Arh
2021-10-21 11:45:23 +02:00
parent 415982c8fd
commit 0b69d1a2e3
6 changed files with 168 additions and 30 deletions
+129 -23
View File
@@ -114,29 +114,6 @@ func (app *CortezaApp) Setup() (err error) {
auth.SetupDefault(app.Opt.Auth.Secret, app.Opt.Auth.Expiry)
mail.SetupDialer(
app.Opt.SMTP.Host,
app.Opt.SMTP.Port,
app.Opt.SMTP.User,
app.Opt.SMTP.Pass,
app.Opt.SMTP.From,
// Apply TLS configuration
func(d *gomail.Dialer) {
if d.TLSConfig == nil {
d.TLSConfig = &tls.Config{ServerName: d.Host}
}
if app.Opt.SMTP.TlsInsecure {
d.TLSConfig.InsecureSkipVerify = true
}
if app.Opt.SMTP.TlsServerName != "" {
d.TLSConfig.ServerName = app.Opt.SMTP.TlsServerName
}
},
)
http.SetupDefaults(
app.Opt.HTTPClient.HttpClientTimeout,
app.Opt.HTTPClient.ClientTSLInsecure,
@@ -490,6 +467,12 @@ func (app *CortezaApp) Activate(ctx context.Context) (err error) {
return err
}
if err = applySmtpOptionsToSettings(ctx, app.Log, app.Opt.SMTP, sysService.CurrentSettings); err != nil {
return err
}
updateSmtpSettings(app.Log, sysService.CurrentSettings)
if app.AuthService, err = authService.New(ctx, app.Log, app.Store, app.Opt.Auth); err != nil {
return fmt.Errorf("failed to init auth service: %w", err)
}
@@ -639,3 +622,126 @@ func updateLocaleSettings(opt options.LocaleOpt) {
updateResourceLanguages(appSettings)
})
}
// takes current options (SMTP_* env variables) and copies their values to settings
func applySmtpOptionsToSettings(ctx context.Context, log *zap.Logger, opt options.SMTPOpt, current *types.AppSettings) (err error) {
if len(opt.Host) == 0 {
// nothing to do here, SMTP_HOST not set
return
}
// Create SMTP server settings struct
// from the environmental variables (SMTP_*)
// we'll use it for provisioning empty SMTP settings
// and for comparison to issue a warning
optServer := &types.SmtpServers{
Host: opt.Host,
Port: opt.Port,
User: opt.User,
Pass: opt.Pass,
From: opt.From,
TlsInsecure: opt.TlsInsecure,
TlsServerName: opt.TlsServerName,
}
if len(current.SMTP.Servers) > 0 {
if current.SMTP.Servers[0] != *optServer {
// ENV variables changed OR settings changed.
// One way or the other, this can lead to unexpected situations
//
// Let's log a warning
log.Warn(
"Environmental variables (SMTP_*) and SMTP settings " +
"(most likely changed via admin console) are not the same. " +
"When server was restarted, values from environmental" +
"variables were copied to settings for easier management. " +
"To avoid confusion and potential issues, we suggest you to " +
"remove all SMTP_* variables")
}
return
}
// SMTP server settings do not exist but
// there is something in the options (SMTP_HOST)
ctx = auth.SetIdentityToContext(ctx, auth.ServiceUser())
// When settings for the SMTP servers are missing,
// we'll try to use one from the options (environmental vars)
s := &types.SettingValue{Name: "smtp.servers"}
err = s.SetValue([]*types.SmtpServers{optServer})
if err != nil {
return
}
if err = sysService.DefaultSettings.Set(ctx, s); err != nil {
return
}
if err = sysService.DefaultSettings.UpdateCurrent(ctx); err != nil {
return
}
return
}
func updateSmtpSettings(log *zap.Logger, current *types.AppSettings) {
sysService.DefaultSettings.Register("smtp", func(ctx context.Context, current interface{}, _ types.SettingValueSet) {
appSettings, is := current.(*types.AppSettings)
if !is {
return
}
setupSmtpDialer(log, appSettings.SMTP.Servers...)
})
setupSmtpDialer(log, current.SMTP.Servers...)
}
func setupSmtpDialer(log *zap.Logger, servers ...types.SmtpServers) {
if len(servers) == 0 {
log.Warn("no SMTP servers found, email sending will be disabled")
return
}
// Supporting only one server for now
s := servers[0]
if s.Host == "" {
log.Warn("SMTP server configured without host/server, email sending will be disabled")
return
}
log.Info("reloading SMTP configuration",
zap.String("host", s.Host),
zap.Int("port", s.Port),
zap.String("user", s.User),
logger.Mask("pass", s.Pass),
zap.Bool("tsl-insecure", s.TlsInsecure),
zap.String("tls-server-name", s.TlsServerName),
)
mail.SetupDialer(
s.Host,
s.Port,
s.User,
s.Pass,
s.From,
// Apply TLS configuration
func(d *gomail.Dialer) {
if d.TLSConfig == nil {
d.TLSConfig = &tls.Config{ServerName: d.Host}
}
if s.TlsInsecure {
d.TLSConfig.InsecureSkipVerify = true
}
if s.TlsServerName != "" {
d.TLSConfig.ServerName = s.TlsServerName
}
},
)
}
+4 -3
View File
@@ -2,10 +2,11 @@ package mail
import (
"fmt"
gomail "gopkg.in/mail.v2"
"regexp"
"strconv"
"strings"
gomail "gopkg.in/mail.v2"
)
type (
@@ -36,7 +37,7 @@ func init() {
// Host variable can contain "<host>:<port>" that will override port value
func SetupDialer(host string, port int, user, pass, from string, ff ...applyCfg) {
if host == "" {
defaultDialerError = fmt.Errorf("No hostname provided for SMTP")
defaultDialerError = fmt.Errorf("no hostname provided for SMTP")
return
}
@@ -47,7 +48,7 @@ func SetupDialer(host string, port int, user, pass, from string, ff ...applyCfg)
}
if port == 0 {
defaultDialerError = fmt.Errorf("No port provided for SMTP")
defaultDialerError = fmt.Errorf("no port provided for SMTP")
return
}
+5 -1
View File
@@ -1,7 +1,11 @@
docs:
title: Email sending
intro: |-
Configure your local SMTP server or use one of the available providers
Configure your local SMTP server or use one of the available providers.
These values are copied to settings when the server starts and can be managed from the administration console.
We recommend you remove these values after they are copied to settings.
If server detects difference between these options and settings, it shows a warning in the log on server start.
props:
- name: host
+15 -2
View File
@@ -137,11 +137,24 @@ func (svc authNotification) send(ctx context.Context, name, sendTo string, paylo
}
ntf.SetBody("text/html", string(tmp))
err = mail.Send(ntf)
if err != nil {
svc.log(ctx).Error(
"auth notification send failed",
zap.String("name", name),
zap.String("email", sendTo),
zap.Error(err),
)
return fmt.Errorf("could not send email, contact your administrator")
}
svc.log(ctx).Debug(
"sending auth notification",
"auth notification sent",
zap.String("name", name),
zap.String("email", sendTo),
)
return mail.Send(ntf)
return nil
}
+1 -1
View File
@@ -221,7 +221,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
}
func Activate(ctx context.Context) (err error) {
// Run initial update of current settings with super-user credentials
// Run initial update of current settings
err = DefaultSettings.UpdateCurrent(ctx)
if err != nil {
return
+14
View File
@@ -34,6 +34,10 @@ type (
}
} `json:"-"`
SMTP struct {
Servers []SmtpServers `json:"-" kv:"servers,final"`
} `json:"-" kv:"smtp"`
Auth struct {
Internal struct {
// Is internal authentication (username + password) enabled
@@ -206,6 +210,16 @@ type (
IssuerUrl string `json:"-" kv:"issuer"`
Weight int `json:"-"`
}
SmtpServers struct {
Host string `json:"host"`
Port int `json:"port,string"`
User string `json:"user"`
Pass string `json:"pass"`
From string `json:"from"`
TlsInsecure bool `json:"tlsInsecure"`
TlsServerName string `json:"tlsServerName"`
}
)
func (set *ExternalAuthProvider) ValidConfiguration() bool {