From 02454cfb9dd9116bf4cbaa4c7d95d26e0dbedbd9 Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Thu, 8 Oct 2020 15:59:22 -0700 Subject: [PATCH 1/8] Allow custom email server for invitations --- cmd/server/main.go | 13 +++- pkg/config/server_config.go | 4 +- pkg/controller/user/importbatch.go | 2 +- pkg/controller/user/reset_password.go | 3 +- pkg/controller/user/user.go | 32 ++++----- pkg/email/config.go | 64 ++++++++++++++++++ pkg/email/firebase.go | 34 ++++++++++ pkg/email/smtp.go | 94 +++++++++++++++++++++++++++ 8 files changed, 226 insertions(+), 20 deletions(-) create mode 100644 pkg/email/config.go create mode 100644 pkg/email/firebase.go create mode 100644 pkg/email/smtp.go diff --git a/cmd/server/main.go b/cmd/server/main.go index be0057650..dafe430be 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -40,6 +40,7 @@ import ( "github.com/google/exposure-notifications-verification-server/pkg/controller/realmadmin" "github.com/google/exposure-notifications-verification-server/pkg/controller/realmkeys" "github.com/google/exposure-notifications-verification-server/pkg/controller/user" + "github.com/google/exposure-notifications-verification-server/pkg/email" "github.com/google/exposure-notifications-verification-server/pkg/ratelimit" "github.com/google/exposure-notifications-verification-server/pkg/ratelimit/limitware" "github.com/google/exposure-notifications-verification-server/pkg/render" @@ -144,6 +145,16 @@ func realMain(ctx context.Context) error { return fmt.Errorf("failed to configure internal firebase client: %w", err) } + // Setup server emailer + cfg.Email.ProviderType = email.ProviderTypeFirebase + if cfg.Email.SmtpHost != "" { + cfg.Email.ProviderType = email.ProviderTypeSmtp + } + emailer, err := email.ProviderFor(ctx, &cfg.Email, auth) + if err != nil { + return fmt.Errorf("failed to configure internal firebase client: %w", err) + } + // Create the router r := mux.NewRouter() @@ -366,7 +377,7 @@ func realMain(ctx context.Context) error { userSub.Use(requireMFA) userSub.Use(rateLimit) - userController := user.New(ctx, firebaseInternal, auth, cacher, cfg, db, h) + userController := user.New(ctx, auth, emailer, cacher, cfg, db, h) userSub.Handle("", userController.HandleIndex()).Methods("GET") userSub.Handle("", userController.HandleIndex()). Queries("offset", "{[0-9]*}", "email", "").Methods("GET") diff --git a/pkg/config/server_config.go b/pkg/config/server_config.go index 98c2b9ee1..5a003fa4e 100644 --- a/pkg/config/server_config.go +++ b/pkg/config/server_config.go @@ -21,6 +21,7 @@ import ( "github.com/google/exposure-notifications-verification-server/pkg/cache" "github.com/google/exposure-notifications-verification-server/pkg/database" + "github.com/google/exposure-notifications-verification-server/pkg/email" "github.com/google/exposure-notifications-verification-server/pkg/ratelimit" "github.com/google/exposure-notifications-server/pkg/observability" @@ -40,7 +41,7 @@ type PasswordRequirementsConfig struct { Special int `env:"MIN_PWD_SPECIAL,default=1"` } -// HasRequirements is true if any requirments are set. +// HasRequirements is true if any requirements are set. func (c *PasswordRequirementsConfig) HasRequirements() bool { return c.Length > 0 || c.Uppercase > 0 || c.Lowercase > 0 || c.Number > 0 || c.Special > 0 } @@ -51,6 +52,7 @@ type ServerConfig struct { Database database.Config Observability observability.Config Cache cache.Config + Email email.Config Port string `env:"PORT,default=8080"` diff --git a/pkg/controller/user/importbatch.go b/pkg/controller/user/importbatch.go index 72064d103..48209a299 100644 --- a/pkg/controller/user/importbatch.go +++ b/pkg/controller/user/importbatch.go @@ -73,7 +73,7 @@ func (c *Controller) HandleImportBatch() http.Handler { continue } else if created { newUsers = append(newUsers, &batchUser) - if err := c.firebaseInternal.SendNewUserInvitation(ctx, user.Email); err != nil { + if err := c.emailer.SendNewUserInvitation(ctx, user.Email); err != nil { batchErr = multierror.Append(batchErr, err) continue } diff --git a/pkg/controller/user/reset_password.go b/pkg/controller/user/reset_password.go index 5b432904b..a523289a3 100644 --- a/pkg/controller/user/reset_password.go +++ b/pkg/controller/user/reset_password.go @@ -49,7 +49,8 @@ func (c *Controller) ensureFirebaseUserExists(ctx context.Context, user *databas } if created { - if err := c.firebaseInternal.SendNewUserInvitation(ctx, user.Email); err != nil { + err := c.emailer.SendNewUserInvitation(ctx, user.Email) + if err != nil { flash.Error("Could not send new user invitation: %v", err) return true, err } diff --git a/pkg/controller/user/user.go b/pkg/controller/user/user.go index 9e345d230..afca3f64f 100644 --- a/pkg/controller/user/user.go +++ b/pkg/controller/user/user.go @@ -19,10 +19,10 @@ import ( "context" "firebase.google.com/go/auth" - "github.com/google/exposure-notifications-verification-server/internal/firebase" "github.com/google/exposure-notifications-verification-server/pkg/cache" "github.com/google/exposure-notifications-verification-server/pkg/config" "github.com/google/exposure-notifications-verification-server/pkg/database" + "github.com/google/exposure-notifications-verification-server/pkg/email" "github.com/google/exposure-notifications-verification-server/pkg/render" "github.com/google/exposure-notifications-server/pkg/logging" @@ -32,20 +32,20 @@ import ( // Controller manages users type Controller struct { - cacher cache.Cacher - firebaseInternal *firebase.Client - client *auth.Client - config *config.ServerConfig - db *database.Database - h *render.Renderer - logger *zap.SugaredLogger + cacher cache.Cacher + client *auth.Client + emailer email.Provider + config *config.ServerConfig + db *database.Database + h *render.Renderer + logger *zap.SugaredLogger } // New creates a new controller for managing users. func New( ctx context.Context, - firebaseInternal *firebase.Client, client *auth.Client, + emailer email.Provider, cacher cache.Cacher, config *config.ServerConfig, db *database.Database, @@ -53,12 +53,12 @@ func New( logger := logging.FromContext(ctx) return &Controller{ - cacher: cacher, - firebaseInternal: firebaseInternal, - client: client, - config: config, - db: db, - h: h, - logger: logger, + cacher: cacher, + client: client, + emailer: emailer, + config: config, + db: db, + h: h, + logger: logger, } } diff --git a/pkg/email/config.go b/pkg/email/config.go new file mode 100644 index 000000000..19a509e83 --- /dev/null +++ b/pkg/email/config.go @@ -0,0 +1,64 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package email + +import ( + "context" + "fmt" + + "firebase.google.com/go/auth" + "github.com/google/exposure-notifications-server/pkg/secrets" +) + +// ProviderType represents a type of email provider. +type ProviderType string + +const ( + ProviderTypeNoop ProviderType = "NOOP" + ProviderTypeFirebase ProviderType = "FIREBASE" + ProviderTypeSmtp ProviderType = "SIMPLE_SMTP" +) + +// Config represents the env var based configuration for email SMTP server connection. +type Config struct { + ProviderType ProviderType + + User string `env:"EMAIL_USER" json:",omitempty"` + Password string `env:"EMAIL_PASSWORD" json:",omitempty"` + SmtpHost string `env:"EMAIL_SMTP_HOST" json:",omitempty"` + SmtpPort string `env:"EMAIL_SMTP_PORT" json:",omitempty"` + + // Secrets is the secret configuration. This is used to resolve values that + // are actually pointers to secrets before returning them to the caller. The + // table implementation is the source of truth for which values are secrets + // and which are plaintext. + Secrets secrets.Config +} + +type Provider interface { + // SendNewUserInvitation sends an invite to join the server. + SendNewUserInvitation(ctx context.Context, email string) error +} + +func ProviderFor(ctx context.Context, c *Config, auth *auth.Client) (Provider, error) { + switch typ := c.ProviderType; typ { + case ProviderTypeFirebase: + return NewFirebase(ctx) + case ProviderTypeSmtp: + return NewSmtp(ctx, c.User, c.Password, c.SmtpHost, c.SmtpPort, auth) + default: + return nil, fmt.Errorf("unknown email provider type: %v", typ) + } +} diff --git a/pkg/email/firebase.go b/pkg/email/firebase.go new file mode 100644 index 000000000..f6fba8083 --- /dev/null +++ b/pkg/email/firebase.go @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package email is logic for sending email invitations +package email + +import ( + "context" + "fmt" + + "github.com/google/exposure-notifications-verification-server/internal/firebase" +) + +var _ Provider = (*firebase.Client)(nil) + +// NewFirebase creates a new Smtp email sender with the given auth. +func NewFirebase(ctx context.Context) (Provider, error) { + firebaseInternal, err := firebase.New(ctx) + if err != nil { + return nil, fmt.Errorf("failed to configure internal firebase client: %w", err) + } + return firebaseInternal, nil +} diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go new file mode 100644 index 000000000..6cb442192 --- /dev/null +++ b/pkg/email/smtp.go @@ -0,0 +1,94 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package email is logic for sending email invitations +package email + +import ( + "bytes" + "context" + "fmt" + "mime/quotedprintable" + "net/smtp" + + "firebase.google.com/go/auth" +) + +var _ Provider = (*SmtpProvider)(nil) + +// Smtp sends messages via an external SMTP server. +type SmtpProvider struct { + FirebaseAuth *auth.Client + + User string + Password string + SmtpHost string + SmtpPort string +} + +// NewSmtp creates a new Smtp email sender with the given auth. +func NewSmtp(ctx context.Context, user, password, host, port string, auth *auth.Client) (Provider, error) { + return &SmtpProvider{ + FirebaseAuth: auth, + User: user, + Password: password, + SmtpHost: host, + SmtpPort: port, + }, nil +} + +// SendNewUserInvitation sends a password reset email to the user. +func (s *SmtpProvider) SendNewUserInvitation(ctx context.Context, toEmail string) error { + // Header + header := make(map[string]string) + header["From"] = s.User + header["To"] = toEmail + header["Subject"] = "COVID-19 Verification Server Invitation" + + header["MIME-Version"] = "1.0" + header["Content-Type"] = fmt.Sprintf("%s; charset=\"utf-8\"", "text/html") + header["Content-Disposition"] = "inline" + header["Content-Transfer-Encoding"] = "quoted-printable" + + headerMessage := "" + for key, value := range header { + headerMessage += fmt.Sprintf("%s: %s\r\n", key, value) + } + + inviteLink, err := s.FirebaseAuth.PasswordResetLink(ctx, toEmail) + if err != nil { + return err + } + + // Message. + body := fmt.Sprintf( + "You've been invited to the COVID-19 Verification Server. Use the link below to set up your account."+ + " \n\n %s", inviteLink) + var bodyMessage bytes.Buffer + temp := quotedprintable.NewWriter(&bodyMessage) + temp.Write([]byte(body)) + temp.Close() + + finalMessage := headerMessage + "\r\n" + bodyMessage.String() + + // Authentication. + auth := smtp.PlainAuth("", s.User, s.Password, s.SmtpHost) + + // Sending email. + err = smtp.SendMail(s.SmtpHost+":"+s.SmtpPort, auth, s.User, []string{toEmail}, []byte(finalMessage)) + if err != nil { + return err + } + return nil +} From 2e4e5d285e0e0fc9b2cd16cd612f361c690db3f4 Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 09:32:09 -0700 Subject: [PATCH 2/8] strings --- pkg/email/smtp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go index 6cb442192..fbee3e307 100644 --- a/pkg/email/smtp.go +++ b/pkg/email/smtp.go @@ -57,7 +57,7 @@ func (s *SmtpProvider) SendNewUserInvitation(ctx context.Context, toEmail string header["Subject"] = "COVID-19 Verification Server Invitation" header["MIME-Version"] = "1.0" - header["Content-Type"] = fmt.Sprintf("%s; charset=\"utf-8\"", "text/html") + header["Content-Type"] = fmt.Sprintf(`%s; charset="utf-8"`, "text/html") header["Content-Disposition"] = "inline" header["Content-Transfer-Encoding"] = "quoted-printable" @@ -73,8 +73,8 @@ func (s *SmtpProvider) SendNewUserInvitation(ctx context.Context, toEmail string // Message. body := fmt.Sprintf( - "You've been invited to the COVID-19 Verification Server. Use the link below to set up your account."+ - " \n\n %s", inviteLink) + `You've been invited to the COVID-19 Verification Server. + Use the link below to set up your account. \n\n %s`, inviteLink) var bodyMessage bytes.Buffer temp := quotedprintable.NewWriter(&bodyMessage) temp.Write([]byte(body)) From 98d23bb73a7316fe73f5b8d307f06c2479b65921 Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 11:55:55 -0700 Subject: [PATCH 3/8] fix lint --- cmd/server/main.go | 4 ++-- pkg/email/config.go | 10 +++++----- pkg/email/firebase.go | 2 +- pkg/email/smtp.go | 26 +++++++++++++------------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index dafe430be..31b220564 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -147,8 +147,8 @@ func realMain(ctx context.Context) error { // Setup server emailer cfg.Email.ProviderType = email.ProviderTypeFirebase - if cfg.Email.SmtpHost != "" { - cfg.Email.ProviderType = email.ProviderTypeSmtp + if cfg.Email.SMTPHost != "" { + cfg.Email.ProviderType = email.ProviderTypeSMTP } emailer, err := email.ProviderFor(ctx, &cfg.Email, auth) if err != nil { diff --git a/pkg/email/config.go b/pkg/email/config.go index 19a509e83..1365646f0 100644 --- a/pkg/email/config.go +++ b/pkg/email/config.go @@ -28,7 +28,7 @@ type ProviderType string const ( ProviderTypeNoop ProviderType = "NOOP" ProviderTypeFirebase ProviderType = "FIREBASE" - ProviderTypeSmtp ProviderType = "SIMPLE_SMTP" + ProviderTypeSMTP ProviderType = "SIMPLE_SMTP" ) // Config represents the env var based configuration for email SMTP server connection. @@ -37,8 +37,8 @@ type Config struct { User string `env:"EMAIL_USER" json:",omitempty"` Password string `env:"EMAIL_PASSWORD" json:",omitempty"` - SmtpHost string `env:"EMAIL_SMTP_HOST" json:",omitempty"` - SmtpPort string `env:"EMAIL_SMTP_PORT" json:",omitempty"` + SMTPHost string `env:"EMAIL_SMTP_HOST" json:",omitempty"` + SMTPPort string `env:"EMAIL_SMTP_PORT" json:",omitempty"` // Secrets is the secret configuration. This is used to resolve values that // are actually pointers to secrets before returning them to the caller. The @@ -56,8 +56,8 @@ func ProviderFor(ctx context.Context, c *Config, auth *auth.Client) (Provider, e switch typ := c.ProviderType; typ { case ProviderTypeFirebase: return NewFirebase(ctx) - case ProviderTypeSmtp: - return NewSmtp(ctx, c.User, c.Password, c.SmtpHost, c.SmtpPort, auth) + case ProviderTypeSMTP: + return NewSMTP(ctx, c.User, c.Password, c.SMTPHost, c.SMTPPort, auth) default: return nil, fmt.Errorf("unknown email provider type: %v", typ) } diff --git a/pkg/email/firebase.go b/pkg/email/firebase.go index f6fba8083..3ca9d8a4b 100644 --- a/pkg/email/firebase.go +++ b/pkg/email/firebase.go @@ -24,7 +24,7 @@ import ( var _ Provider = (*firebase.Client)(nil) -// NewFirebase creates a new Smtp email sender with the given auth. +// NewFirebase creates a new SMTP email sender with the given auth. func NewFirebase(ctx context.Context) (Provider, error) { firebaseInternal, err := firebase.New(ctx) if err != nil { diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go index fbee3e307..5f0f90dec 100644 --- a/pkg/email/smtp.go +++ b/pkg/email/smtp.go @@ -25,31 +25,31 @@ import ( "firebase.google.com/go/auth" ) -var _ Provider = (*SmtpProvider)(nil) +var _ Provider = (*SMTPProvider)(nil) -// Smtp sends messages via an external SMTP server. -type SmtpProvider struct { +// SMTP sends messages via an external SMTP server. +type SMTPProvider struct { FirebaseAuth *auth.Client User string Password string - SmtpHost string - SmtpPort string + SMTPHost string + SMTPPort string } // NewSmtp creates a new Smtp email sender with the given auth. -func NewSmtp(ctx context.Context, user, password, host, port string, auth *auth.Client) (Provider, error) { - return &SmtpProvider{ +func NewSMTP(ctx context.Context, user, password, host, port string, auth *auth.Client) (Provider, error) { + return &SMTPProvider{ FirebaseAuth: auth, User: user, Password: password, - SmtpHost: host, - SmtpPort: port, + SMTPHost: host, + SMTPPort: port, }, nil } // SendNewUserInvitation sends a password reset email to the user. -func (s *SmtpProvider) SendNewUserInvitation(ctx context.Context, toEmail string) error { +func (s *SMTPProvider) SendNewUserInvitation(ctx context.Context, toEmail string) error { // Header header := make(map[string]string) header["From"] = s.User @@ -57,7 +57,7 @@ func (s *SmtpProvider) SendNewUserInvitation(ctx context.Context, toEmail string header["Subject"] = "COVID-19 Verification Server Invitation" header["MIME-Version"] = "1.0" - header["Content-Type"] = fmt.Sprintf(`%s; charset="utf-8"`, "text/html") + header["Content-Type"] = `text/html; charset="utf-8"` header["Content-Disposition"] = "inline" header["Content-Transfer-Encoding"] = "quoted-printable" @@ -83,10 +83,10 @@ func (s *SmtpProvider) SendNewUserInvitation(ctx context.Context, toEmail string finalMessage := headerMessage + "\r\n" + bodyMessage.String() // Authentication. - auth := smtp.PlainAuth("", s.User, s.Password, s.SmtpHost) + auth := smtp.PlainAuth("", s.User, s.Password, s.SMTPHost) // Sending email. - err = smtp.SendMail(s.SmtpHost+":"+s.SmtpPort, auth, s.User, []string{toEmail}, []byte(finalMessage)) + err = smtp.SendMail(s.SMTPHost+":"+s.SMTPPort, auth, s.User, []string{toEmail}, []byte(finalMessage)) if err != nil { return err } From b6c467e1945e6e058eb3165ef6c55ff0f7a01bcd Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 12:01:27 -0700 Subject: [PATCH 4/8] lint2 --- pkg/email/smtp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go index 5f0f90dec..d21ee97d9 100644 --- a/pkg/email/smtp.go +++ b/pkg/email/smtp.go @@ -27,7 +27,7 @@ import ( var _ Provider = (*SMTPProvider)(nil) -// SMTP sends messages via an external SMTP server. +// SMTPProvider sends messages via an external SMTP server. type SMTPProvider struct { FirebaseAuth *auth.Client @@ -37,7 +37,7 @@ type SMTPProvider struct { SMTPPort string } -// NewSmtp creates a new Smtp email sender with the given auth. +// NewSMTP creates a new Smtp email sender with the given auth. func NewSMTP(ctx context.Context, user, password, host, port string, auth *auth.Client) (Provider, error) { return &SMTPProvider{ FirebaseAuth: auth, From ac9b8b3e3c7762aa17d9dd5c276b777e5f22e320 Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 12:34:58 -0700 Subject: [PATCH 5/8] fix returns --- pkg/email/smtp.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go index d21ee97d9..7d4e868ac 100644 --- a/pkg/email/smtp.go +++ b/pkg/email/smtp.go @@ -73,8 +73,9 @@ func (s *SMTPProvider) SendNewUserInvitation(ctx context.Context, toEmail string // Message. body := fmt.Sprintf( - `You've been invited to the COVID-19 Verification Server. - Use the link below to set up your account. \n\n %s`, inviteLink) + `You've been invited to the COVID-19 Verification Server. Use the link below to set up your account. + + %s`, inviteLink) var bodyMessage bytes.Buffer temp := quotedprintable.NewWriter(&bodyMessage) temp.Write([]byte(body)) From 4f85a77e8ecd4e91097150373785107c96d91398 Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 12:35:58 -0700 Subject: [PATCH 6/8] fix message --- pkg/email/smtp.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go index 7d4e868ac..0fb63c490 100644 --- a/pkg/email/smtp.go +++ b/pkg/email/smtp.go @@ -73,7 +73,8 @@ func (s *SMTPProvider) SendNewUserInvitation(ctx context.Context, toEmail string // Message. body := fmt.Sprintf( - `You've been invited to the COVID-19 Verification Server. Use the link below to set up your account. + `You've been invited to the COVID-19 Verification Server. + Use the link below to set up your account. %s`, inviteLink) var bodyMessage bytes.Buffer From 559930adca6f77031d04172ba0d3182d96a989e1 Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 12:36:17 -0700 Subject: [PATCH 7/8] break --- pkg/email/smtp.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go index 0fb63c490..8bfab2dfd 100644 --- a/pkg/email/smtp.go +++ b/pkg/email/smtp.go @@ -74,9 +74,7 @@ func (s *SMTPProvider) SendNewUserInvitation(ctx context.Context, toEmail string // Message. body := fmt.Sprintf( `You've been invited to the COVID-19 Verification Server. - Use the link below to set up your account. - - %s`, inviteLink) + Use the link below to set up your account.
%s`, inviteLink) var bodyMessage bytes.Buffer temp := quotedprintable.NewWriter(&bodyMessage) temp.Write([]byte(body)) From 665c898ad00c7ea8642c5c4a456fa6bb0f561c8a Mon Sep 17 00:00:00 2001 From: Weston Haught Date: Fri, 9 Oct 2020 12:56:48 -0700 Subject: [PATCH 8/8] has creds --- cmd/server/main.go | 2 +- pkg/email/config.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 31b220564..1db333a8c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -147,7 +147,7 @@ func realMain(ctx context.Context) error { // Setup server emailer cfg.Email.ProviderType = email.ProviderTypeFirebase - if cfg.Email.SMTPHost != "" { + if cfg.Email.HasSMTPCreds() { cfg.Email.ProviderType = email.ProviderTypeSMTP } emailer, err := email.ProviderFor(ctx, &cfg.Email, auth) diff --git a/pkg/email/config.go b/pkg/email/config.go index 1365646f0..30d6b4875 100644 --- a/pkg/email/config.go +++ b/pkg/email/config.go @@ -52,6 +52,10 @@ type Provider interface { SendNewUserInvitation(ctx context.Context, email string) error } +func (c *Config) HasSMTPCreds() bool { + return c.User != "" && c.Password != "" && c.SMTPHost != "" && c.SMTPPort != "" +} + func ProviderFor(ctx context.Context, c *Config, auth *auth.Client) (Provider, error) { switch typ := c.ProviderType; typ { case ProviderTypeFirebase: