diff --git a/README.md b/README.md index 9fbb0d91..c8d2bd0c 100644 --- a/README.md +++ b/README.md @@ -144,29 +144,23 @@ _this is the recommended way to run remark42_ | auth.dev | AUTH_DEV | `false` | local oauth2 server, development mode only | | auth.anon | AUTH_ANON | `false` | enable anonymous login | | auth.email.enable | AUTH_EMAIL_ENABLE | `false` | enable auth via email | -| auth.email.host | AUTH_EMAIL_HOST | | smtp host | -| auth.email.port | AUTH_EMAIL_PORT | `25` | smtp port | | auth.email.from | AUTH_EMAIL_FROM | | email from | | auth.email.subj | AUTH_EMAIL_SUBJ | `remark42 confirmation` | email subject | | auth.email.content-type | AUTH_EMAIL_CONTENT_TYPE | `text/html` | email content type | -| auth.email.tls | AUTH_EMAIL_TLS | `false` | enable TLS | -| auth.email.user | AUTH_EMAIL_USER | | smtp user name | -| auth.email.passwd | AUTH_EMAIL_PASSWD | | smtp password | -| auth.email.timeout | AUTH_EMAIL_TIMEOUT | `10s` | smtp timeout | | auth.email.template | AUTH_EMAIL_TEMPLATE | none (predefined) | custom email message template file | | notify.type | NOTIFY_TYPE | none | type of notification (telegram and/or email) | | notify.queue | NOTIFY_QUEUE | `100` | size of notification queue | | notify.telegram.token | NOTIFY_TELEGRAM_TOKEN | | telegram token | | notify.telegram.chan | NOTIFY_TELEGRAM_CHAN | | telegram channel | | notify.telegram.timeout | NOTIFY_TELEGRAM_TIMEOUT | `5s` | telegram timeout | -| notify.email.host | NOTIFY_EMAIL_HOST | | SMTP host | -| notify.email.port | NOTIFY_EMAIL_PORT | `587` | SMTP port | -| notify.email.tls | NOTIFY_EMAIL_TLS | | enable TLS for SMTP | | notify.email.fromAddress | NOTIFY_EMAIL_FROM | | from email address | -| notify.email.username | NOTIFY_EMAIL_USERNAME | | SMTP user name | -| notify.email.password | NOTIFY_EMAIL_PASSWORD | | SMTP password | -| notify.email.timeout | NOTIFY_EMAIL_TIMEOUT | `10s` | SMTP TCP connection timeout | | notify.email.verification_subj | NOTIFY_EMAIL_VERIFICATION_SUBJ | `Email verification` | verification message subject | +| smtp.host | SMTP_HOST | | SMTP host | +| smtp.port | SMTP_PORT | | SMTP port | +| smtp.username | SMTP_USERNAME | | SMTP user name | +| smtp.password | SMTP_PASSWORD | | SMTP password | +| smtp.tls | SMTP_TLS | | enable TLS for SMTP | +| smtp.timeout | SMTP_TIMEOUT | `10s` | SMTP TCP connection timeout | | ssl.type | SSL_TYPE | none | `none`-http, `static`-https, `auto`-https + le | | ssl.port | SSL_PORT | `8443` | port for https server | | ssl.cert | SSL_CERT | | path to cert.pem file | @@ -197,6 +191,19 @@ _this is the recommended way to run remark42_ * _multi_ parameters separated by `,` in the environment or repeated with command line key, like `--site=s1 --site=s2 ...` * _required_ parameters have to be presented in the environment or provided in command line +##### Deprecated + +
deprecated options +| Command line | Replacement | Environment | Replacement | Default | Description | Deprecation version | +| ------------------ | ------------- | ------------------ | ------------- | ------- | -------------- | ------------------- | +| auth.email.host | smtp.host | AUTH_EMAIL_HOST | SMTP_HOST | | smtp host | 1.5.0 | +| auth.email.port | smtp.port | AUTH_EMAIL_PORT | SMTP_PORT | | smtp port | 1.5.0 | +| auth.email.user | smtp.username | AUTH_EMAIL_USER | SMTP_USERNAME | | smtp user name | 1.5.0 | +| auth.email.passwd | smtp.password | AUTH_EMAIL_PASSWD | SMTP_PASSWORD | | smtp password | 1.5.0 | +| auth.email.tls | smtp.tls | AUTH_EMAIL_TLS | SMTP_TLS | `false` | enable TLS | 1.5.0 | +| auth.email.timeout | smtp.timeout | AUTH_EMAIL_TIMEOUT | SMTP_TIMEOUT | `10s` | smtp timeout | 1.5.0 | +
+ ##### Required parameters Most of the parameters have sane defaults and don't require customization. There are only a few parameters user has to define: diff --git a/backend/app/cmd/cmd.go b/backend/app/cmd/cmd.go index 541d3a5e..78071697 100644 --- a/backend/app/cmd/cmd.go +++ b/backend/app/cmd/cmd.go @@ -21,6 +21,7 @@ import ( type CommonOptionsCommander interface { SetCommon(commonOpts CommonOpts) Execute(args []string) error + HandleDeprecatedFlags() []DeprecatedFlag } // CommonOpts sets externally from main, shared across all commands @@ -30,6 +31,13 @@ type CommonOpts struct { Revision string } +// DeprecatedFlag contains information about deprecated option +type DeprecatedFlag struct { + Old string + New string + RemoveVersion string +} + // SetCommon satisfies CommonOptionsCommander interface and sets common option fields // The method called by main for each command func (c *CommonOpts) SetCommon(commonOpts CommonOpts) { @@ -38,6 +46,9 @@ func (c *CommonOpts) SetCommon(commonOpts CommonOpts) { c.Revision = commonOpts.Revision } +// HandleDeprecatedFlags sets new flags from deprecated and returns their list +func (c *CommonOpts) HandleDeprecatedFlags() []DeprecatedFlag { return nil } + // fileParser used to convert template strings like blah-{{.SITE}}-{{.YYYYMMDD}} the final format type fileParser struct { site string diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index ed951b7c..091fa303 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -47,6 +47,7 @@ type ServerCommand struct { Cache CacheGroup `group:"cache" namespace:"cache" env-namespace:"CACHE"` Admin AdminGroup `group:"admin" namespace:"admin" env-namespace:"ADMIN"` Notify NotifyGroup `group:"notify" namespace:"notify" env-namespace:"NOTIFY"` + SMTP SmtpGroup `group:"smtp" namespace:"smtp" env-namespace:"SMTP"` Image ImageGroup `group:"image" namespace:"image" env-namespace:"IMAGE"` SSL SSLGroup `group:"ssl" namespace:"ssl" env-namespace:"SSL"` Stream StreamGroup `group:"stream" namespace:"stream" env-namespace:"STREAM"` @@ -87,15 +88,15 @@ type ServerCommand struct { Anonymous bool `long:"anon" env:"ANON" description:"enable anonymous login"` Email struct { Enable bool `long:"enable" env:"ENABLE" description:"enable auth via email"` - Host string `long:"host" env:"HOST" description:"smtp host"` - Port int `long:"port" env:"PORT" description:"smtp port"` - From string `long:"from" env:"FROM" description:"email's from"` + From string `long:"from" env:"FROM" description:"from email address"` Subject string `long:"subj" env:"SUBJ" default:"remark42 confirmation" description:"email's subject"` ContentType string `long:"content-type" env:"CONTENT_TYPE" default:"text/html" description:"content type"` - TLS bool `long:"tls" env:"TLS" description:"enable TLS"` - SMTPUserName string `long:"user" env:"USER" description:"smtp user name"` - SMTPPassword string `long:"passwd" env:"PASSWD" description:"smtp password"` - TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"smtp timeout"` + Host string `long:"host" env:"HOST" description:"[deprecated, use --smtp.host] SMTP host"` + Port int `long:"port" env:"PORT" description:"[deprecated, use --smtp.port] SMTP password"` + SMTPPassword string `long:"passwd" env:"PASSWD" description:"[deprecated, use --smtp.password] SMTP port"` + SMTPUserName string `long:"user" env:"USER" description:"[deprecated, use --smtp.username] enable TLS"` + TLS bool `long:"tls" env:"TLS" description:"[deprecated, use --smtp.tls] SMTP TCP connection timeout"` + TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"[deprecated, use --smtp.timeout] SMTP TCP connection timeout"` MsgTemplate string `long:"template" env:"TEMPLATE" description:"message template file"` } `group:"email" namespace:"email" env-namespace:"EMAIL"` } `group:"auth" namespace:"auth" env-namespace:"AUTH"` @@ -168,6 +169,16 @@ type AdminGroup struct { RPC RPCGroup `group:"rpc" namespace:"rpc" env-namespace:"RPC"` } +// SmtpGroup defines options for SMTP server connection, used in auth and notify modules +type SmtpGroup struct { + Host string `long:"host" env:"HOST" description:"SMTP host"` + Port int `long:"port" env:"PORT" description:"SMTP port"` + Username string `long:"username" env:"USERNAME" description:"SMTP user name"` + Password string `long:"password" env:"PASSWORD" description:"SMTP password"` + TLS bool `long:"tls" env:"TLS" description:"enable TLS"` + TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"` +} + // NotifyGroup defines options for notification type NotifyGroup struct { Type []string `long:"type" env:"TYPE" description:"type of notification" choice:"none" choice:"telegram" choice:"email" default:"none" env-delim:","` //nolint @@ -179,14 +190,8 @@ type NotifyGroup struct { API string `long:"api" env:"API" default:"https://api.telegram.org/bot" description:"telegram api prefix"` } `group:"telegram" namespace:"telegram" env-namespace:"TELEGRAM"` Email struct { - Host string `long:"host" env:"HOST" description:"SMTP host"` - Port int `long:"port" env:"PORT" default:"587" description:"SMTP port"` - TLS bool `long:"tls" env:"TLS" description:"enable TLS for SMTP"` - From string `long:"fromAddress" env:"FROM" description:"from email address"` - Username string `long:"username" env:"USERNAME" description:"SMTP user name"` - Password string `long:"password" env:"PASSWORD" description:"SMTP password"` - TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"` - VerificationSubject string `long:"verification_subj" env:"VERIFICATION_SUBJ" description:"verification message subject"` + From string `long:"fromAddress" env:"FROM" description:"from email address"` + VerificationSubject string `long:"verification_subj" env:"VERIFICATION_SUBJ" description:"verification message subject"` } `group:"email" namespace:"email" env-namespace:"EMAIL"` } @@ -262,6 +267,36 @@ func (s *ServerCommand) Execute(args []string) error { return nil } +// HandleDeprecatedFlags sets new flags from deprecated returns their list +func (s *ServerCommand) HandleDeprecatedFlags() (result []DeprecatedFlag) { + // 1.5.0 + if s.Auth.Email.Host != "" && s.SMTP.Host == "" { + s.SMTP.Host = s.Auth.Email.Host + result = append(result, DeprecatedFlag{Old: "auth.email.host", New: "smtp.host", RemoveVersion: "1.7.0"}) + } + if s.Auth.Email.Port != 0 && s.SMTP.Port == 0 { + s.SMTP.Port = s.Auth.Email.Port + result = append(result, DeprecatedFlag{Old: "auth.email.port", New: "smtp.port", RemoveVersion: "1.7.0"}) + } + if s.Auth.Email.TLS && !s.SMTP.TLS { + s.SMTP.TLS = s.Auth.Email.TLS + result = append(result, DeprecatedFlag{Old: "auth.email.tls", New: "smtp.tls", RemoveVersion: "1.7.0"}) + } + if s.Auth.Email.SMTPUserName != "" && s.SMTP.Username == "" { + s.SMTP.Username = s.Auth.Email.SMTPUserName + result = append(result, DeprecatedFlag{Old: "auth.email.user", New: "smtp.username", RemoveVersion: "1.7.0"}) + } + if s.Auth.Email.SMTPPassword != "" && s.SMTP.Password == "" { + s.SMTP.Password = s.Auth.Email.SMTPPassword + result = append(result, DeprecatedFlag{Old: "auth.email.passwd", New: "smtp.password", RemoveVersion: "1.7.0"}) + } + if s.Auth.Email.TimeOut != 10*time.Second && s.SMTP.TimeOut == 10*time.Second { + s.SMTP.TimeOut = s.Auth.Email.TimeOut + result = append(result, DeprecatedFlag{Old: "auth.email.timeout", New: "smtp.timeout", RemoveVersion: "1.7.0"}) + } + return result +} + // newServerApp prepares application and return it with all active parts // doesn't start anything func (s *ServerCommand) newServerApp() (*serverApp, error) { @@ -644,15 +679,15 @@ func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) { if s.Auth.Email.Enable { params := sender.EmailParams{ - Host: s.Auth.Email.Host, - Port: s.Auth.Email.Port, + Host: s.SMTP.Host, + Port: s.SMTP.Port, + SMTPUserName: s.SMTP.Username, + SMTPPassword: s.SMTP.Password, + TimeOut: s.SMTP.TimeOut, + TLS: s.SMTP.TLS, From: s.Auth.Email.From, Subject: s.Auth.Email.Subject, ContentType: s.Auth.Email.ContentType, - TLS: s.Auth.Email.TLS, - SMTPUserName: s.Auth.Email.SMTPUserName, - SMTPPassword: s.Auth.Email.SMTPPassword, - TimeOut: s.Auth.Email.TimeOut, } sndr := sender.NewEmailClient(params, log.Default()) authenticator.AddVerifProvider("email", s.loadEmailTemplate(), sndr) @@ -732,12 +767,12 @@ func (s *ServerCommand) makeNotify(dataStore *service.DataStore, authenticator * }, } smtpParams := notify.SmtpParams{ - Host: s.Notify.Email.Host, - Port: s.Notify.Email.Port, - TLS: s.Notify.Email.TLS, - Username: s.Notify.Email.Username, - Password: s.Notify.Email.Password, - TimeOut: s.Notify.Email.TimeOut, + Host: s.SMTP.Host, + Port: s.SMTP.Port, + TLS: s.SMTP.TLS, + Username: s.SMTP.Username, + Password: s.SMTP.Password, + TimeOut: s.SMTP.TimeOut, } emailService, err := notify.NewEmail(emailParams, smtpParams) if err != nil { diff --git a/backend/app/cmd/server_test.go b/backend/app/cmd/server_test.go index eccb2ca1..b8611988 100644 --- a/backend/app/cmd/server_test.go +++ b/backend/app/cmd/server_test.go @@ -312,6 +312,47 @@ func TestServerApp_MainSignal(t *testing.T) { assert.True(t, time.Since(st).Seconds() < 5, "should take under five sec", time.Since(st).Seconds()) } +func TestServerApp_DeprecatedArgs(t *testing.T) { + s := ServerCommand{} + s.SetCommon(CommonOpts{RemarkURL: "https://demo.remark42.com", SharedSecret: "123456"}) + + p := flags.NewParser(&s, flags.Default) + args := []string{ + "test", + "--auth.email.host=smtp.example.org", + "--auth.email.port=666", + "--auth.email.tls", + "--auth.email.user=test_user", + "--auth.email.passwd=test_password", + "--auth.email.timeout=15s", + } + assert.Empty(t, s.SMTP.Host) + assert.Empty(t, s.SMTP.Port) + assert.Empty(t, s.SMTP.TLS) + assert.Empty(t, s.SMTP.Username) + assert.Empty(t, s.SMTP.Password) + assert.Empty(t, s.SMTP.TimeOut) + _, err := p.ParseArgs(args) + require.NoError(t, err) + deprecatedFlags := s.HandleDeprecatedFlags() + assert.ElementsMatch(t, + []DeprecatedFlag{ + {Old: "auth.email.host", New: "smtp.host", RemoveVersion: "1.7.0"}, + {Old: "auth.email.port", New: "smtp.port", RemoveVersion: "1.7.0"}, + {Old: "auth.email.tls", New: "smtp.tls", RemoveVersion: "1.7.0"}, + {Old: "auth.email.user", New: "smtp.username", RemoveVersion: "1.7.0"}, + {Old: "auth.email.passwd", New: "smtp.password", RemoveVersion: "1.7.0"}, + {Old: "auth.email.timeout", New: "smtp.timeout", RemoveVersion: "1.7.0"}, + }, + deprecatedFlags) + assert.Equal(t, "smtp.example.org", s.SMTP.Host) + assert.Equal(t, 666, s.SMTP.Port) + assert.Equal(t, true, s.SMTP.TLS) + assert.Equal(t, "test_user", s.SMTP.Username) + assert.Equal(t, "test_password", s.SMTP.Password) + assert.Equal(t, 15*time.Second, s.SMTP.TimeOut) +} + func Test_ACMEEmail(t *testing.T) { cmd := ServerCommand{} cmd.SetCommon(CommonOpts{RemarkURL: "https://remark.com:443", SharedSecret: "123456"}) @@ -512,14 +553,13 @@ func prepServerApp(t *testing.T, fn func(o ServerCommand) ServerCommand) (*serve cmd.Auth.Email.MsgTemplate = "testdata/email.tmpl" cmd.BackupLocation = "/tmp" cmd.Notify.Type = []string{"email"} - cmd.Notify.Email.Host = "127.0.0.1" - cmd.Notify.Email.Port = 25 - cmd.Notify.Email.TLS = false cmd.Notify.Email.From = "from@example.org" - cmd.Notify.Email.Username = "test_user" - cmd.Notify.Email.Password = "test_password" - cmd.Notify.Email.TimeOut = time.Second cmd.Notify.Email.VerificationSubject = "test verification email subject" + cmd.SMTP.Host = "127.0.0.1" + cmd.SMTP.Port = 25 + cmd.SMTP.Username = "test_user" + cmd.SMTP.Password = "test_password" + cmd.SMTP.TimeOut = time.Second cmd.UpdateLimit = 10 cmd = fn(cmd) diff --git a/backend/app/main.go b/backend/app/main.go index 1837a174..4fd6b51a 100644 --- a/backend/app/main.go +++ b/backend/app/main.go @@ -45,6 +45,10 @@ func main() { SharedSecret: opts.SharedSecret, Revision: revision, }) + for _, entry := range c.HandleDeprecatedFlags() { + log.Printf("[WARN] --%s is deprecated and will be removed in v%s, please use --%s instead", + entry.Old, entry.RemoveVersion, entry.New) + } err := c.Execute(args) if err != nil { log.Printf("[ERROR] failed with %+v", err) diff --git a/compose-dev-backend.yml b/compose-dev-backend.yml index 25868240..87297174 100644 --- a/compose-dev-backend.yml +++ b/compose-dev-backend.yml @@ -44,12 +44,12 @@ services: - NOTIFY_TYPE - NOTIFY_TELEGRAM_TOKEN - NOTIFY_TELEGRAM_CHAN - - NOTIFY_EMAIL_HOST - - NOTIFY_EMAIL_USERNAME - - NOTIFY_EMAIL_PASSWORD - NOTIFY_EMAIL_FROM - - NOTIFY_EMAIL_PORT - - NOTIFY_EMAIL_TLS + - SMTP_HOST + - SMTP_USERNAME + - SMTP_PASSWORD + - SMTP_PORT + - SMTP_TLS - EMOJI=true - ANON_VOTE=true - VOTES_IP=true