Support SMTP server config via setting with dynamic reload
This commit is contained in:
+129
-23
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user