From 2cb8a3ba7634d45950328d8f77aea0d050a9748a Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 17:14:58 +0200 Subject: [PATCH 01/44] add smtp configuration --- cmd/gotosocial/flags.go | 1 + cmd/gotosocial/smtpflags.go | 59 +++++++++++++++++++++++++++++++++++++ example/config.yaml | 32 ++++++++++++++++++++ internal/config/config.go | 47 +++++++++++++++++++++++++++++ internal/config/default.go | 26 ++++++++++++++++ internal/config/smtp.go | 33 +++++++++++++++++++++ 6 files changed, 198 insertions(+) create mode 100644 cmd/gotosocial/smtpflags.go create mode 100644 internal/config/smtp.go diff --git a/cmd/gotosocial/flags.go b/cmd/gotosocial/flags.go index 355ec0b81d..8e162f6c74 100644 --- a/cmd/gotosocial/flags.go +++ b/cmd/gotosocial/flags.go @@ -39,6 +39,7 @@ func getFlags() []cli.Flag { statusesFlags(flagNames, envNames, defaults), letsEncryptFlags(flagNames, envNames, defaults), oidcFlags(flagNames, envNames, defaults), + smtpFlags(flagNames, envNames, defaults), } for _, fs := range flagSets { flags = append(flags, fs...) diff --git a/cmd/gotosocial/smtpflags.go b/cmd/gotosocial/smtpflags.go new file mode 100644 index 0000000000..84556ca009 --- /dev/null +++ b/cmd/gotosocial/smtpflags.go @@ -0,0 +1,59 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package main + +import ( + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/urfave/cli/v2" +) + +func smtpFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: flagNames.SMTPHost, + Usage: "Host of the smtp server. Eg., 'smtp.eu.mailgun.org'", + Value: defaults.SMTPHost, + EnvVars: []string{envNames.SMTPHost}, + }, + &cli.IntFlag{ + Name: flagNames.SMTPPort, + Usage: "Port of the smtp server. Eg., 587", + Value: defaults.SMTPPort, + EnvVars: []string{envNames.SMTPPort}, + }, + &cli.StringFlag{ + Name: flagNames.SMTPUsername, + Usage: "Username to authenticate with the smtp server as. Eg., 'postmaster@mail.example.org'", + Value: defaults.SMTPUsername, + EnvVars: []string{envNames.SMTPUsername}, + }, + &cli.StringFlag{ + Name: flagNames.SMTPPassword, + Usage: "Password to pass to the smtp server.", + Value: defaults.SMTPPassword, + EnvVars: []string{envNames.SMTPPassword}, + }, + &cli.StringFlag{ + Name: flagNames.SMTPFrom, + Usage: "String to use as the 'from' field of the email. Eg., 'gotosocial@example.org'", + Value: defaults.SMTPFrom, + EnvVars: []string{envNames.SMTPFrom}, + }, + } +} diff --git a/example/config.yaml b/example/config.yaml index 50b8637255..81e6e7c069 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -360,3 +360,35 @@ oidc: - "email" - "profile" - "groups" + +####################### +##### SMTP CONFIG ##### +####################### + +# Config for sending emails via an smtp server. See https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol +smtp: + + # String. The hostname of the smtp server you want to use. + # If this is not set, smtp will not be used to send emails, and you can ignore the other settings. + # Examples: ["mail.example.org", "localhost"] + # Default: "" + host: "" + # Int. Port to use to connect to the smtp server. + # Examples: [] + # Default: 0 + port: 0 + # String. Username to use when authenticating with the smtp server. + # This should have been provided to you by your smtp host. + # This is often, but not always, an email address. + # Examples: ["maillord@example.org"] + # Default: "" + username: + # String. Password to use when authenticating with the smtp server. + # This should have been provided to you by your smtp host. + # Examples: ["1234", "password"] + # Default: "" + password: + # String. 'From' field for sent emails. + # Examples: ["mail@example.org", "GoToSocial"] + # Default: "GoToSocial" + from: "GoToSocial" diff --git a/internal/config/config.go b/internal/config/config.go index bb789b7d2e..7a3a92af9e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -63,6 +63,7 @@ type Config struct { StatusesConfig *StatusesConfig `yaml:"statuses"` LetsEncryptConfig *LetsEncryptConfig `yaml:"letsEncrypt"` OIDCConfig *OIDCConfig `yaml:"oidc"` + SMTPConfig *SMTPConfig `yaml:"smtp"` /* Not parsed from .yaml configuration file. @@ -95,6 +96,7 @@ func Empty() *Config { StatusesConfig: &StatusesConfig{}, LetsEncryptConfig: &LetsEncryptConfig{}, OIDCConfig: &OIDCConfig{}, + SMTPConfig: &SMTPConfig{}, AccountCLIFlags: make(map[string]string), ExportCLIFlags: make(map[string]string), } @@ -318,6 +320,27 @@ func (c *Config) ParseCLIFlags(f KeyedFlags, version string) error { c.OIDCConfig.Scopes = f.StringSlice(fn.OIDCScopes) } + // smtp flags + if c.SMTPConfig.Host == "" || f.IsSet(fn.SMTPHost) { + c.SMTPConfig.Host = f.String(fn.SMTPHost) + } + + if c.SMTPConfig.Port == 0 || f.IsSet(fn.SMTPPort) { + c.SMTPConfig.Port = f.Int(fn.SMTPPort) + } + + if c.SMTPConfig.Username == "" || f.IsSet(fn.SMTPUsername) { + c.SMTPConfig.Username = f.String(fn.SMTPUsername) + } + + if c.SMTPConfig.Password == "" || f.IsSet(fn.SMTPPassword) { + c.SMTPConfig.Password = f.String(fn.SMTPPassword) + } + + if c.SMTPConfig.From == "" || f.IsSet(fn.SMTPFrom) { + c.SMTPConfig.From = f.String(fn.SMTPFrom) + } + // command-specific flags // admin account CLI flags @@ -399,6 +422,12 @@ type Flags struct { OIDCClientID string OIDCClientSecret string OIDCScopes string + + SMTPHost string + SMTPPort string + SMTPUsername string + SMTPPassword string + SMTPFrom string } // Defaults contains all the default values for a gotosocial config @@ -458,6 +487,12 @@ type Defaults struct { OIDCClientID string OIDCClientSecret string OIDCScopes []string + + SMTPHost string + SMTPPort int + SMTPUsername string + SMTPPassword string + SMTPFrom string } // GetFlagNames returns a struct containing the names of the various flags used for @@ -518,6 +553,12 @@ func GetFlagNames() Flags { OIDCClientID: "oidc-client-id", OIDCClientSecret: "oidc-client-secret", OIDCScopes: "oidc-scopes", + + SMTPHost: "smtp-host", + SMTPPort: "smtp-port", + SMTPUsername: "smtp-username", + SMTPPassword: "smtp-password", + SMTPFrom: "smtp-from", } } @@ -579,5 +620,11 @@ func GetEnvNames() Flags { OIDCClientID: "GTS_OIDC_CLIENT_ID", OIDCClientSecret: "GTS_OIDC_CLIENT_SECRET", OIDCScopes: "GTS_OIDC_SCOPES", + + SMTPHost: "SMTP_HOST", + SMTPPort: "SMTP_PORT", + SMTPUsername: "SMTP_USERNAME", + SMTPPassword: "SMTP_PASSWORD", + SMTPFrom: "SMTP_FROM", } } diff --git a/internal/config/default.go b/internal/config/default.go index ad171a376f..a04a5899b0 100644 --- a/internal/config/default.go +++ b/internal/config/default.go @@ -67,6 +67,13 @@ func TestDefault() *Config { ClientSecret: defaults.OIDCClientSecret, Scopes: defaults.OIDCScopes, }, + SMTPConfig: &SMTPConfig{ + Host: defaults.SMTPHost, + Port: defaults.SMTPPort, + Username: defaults.SMTPUsername, + Password: defaults.SMTPPassword, + From: defaults.SMTPFrom, + }, } } @@ -134,6 +141,13 @@ func Default() *Config { ClientSecret: defaults.OIDCClientSecret, Scopes: defaults.OIDCScopes, }, + SMTPConfig: &SMTPConfig{ + Host: defaults.SMTPHost, + Port: defaults.SMTPPort, + Username: defaults.SMTPUsername, + Password: defaults.SMTPPassword, + From: defaults.SMTPFrom, + }, } } @@ -195,6 +209,12 @@ func GetDefaults() Defaults { OIDCClientID: "", OIDCClientSecret: "", OIDCScopes: []string{oidc.ScopeOpenID, "profile", "email", "groups"}, + + SMTPHost: "", + SMTPPort: 0, + SMTPUsername: "", + SMTPPassword: "", + SMTPFrom: "GoToSocial", } } @@ -253,5 +273,11 @@ func GetTestDefaults() Defaults { OIDCClientID: "", OIDCClientSecret: "", OIDCScopes: []string{oidc.ScopeOpenID, "profile", "email", "groups"}, + + SMTPHost: "", + SMTPPort: 0, + SMTPUsername: "", + SMTPPassword: "", + SMTPFrom: "GoToSocial", } } diff --git a/internal/config/smtp.go b/internal/config/smtp.go new file mode 100644 index 0000000000..daa4967bfb --- /dev/null +++ b/internal/config/smtp.go @@ -0,0 +1,33 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package config + +// SMTPConfig holds configuration for sending emails using the smtp protocol. +type SMTPConfig struct { + // Host of the smtp server. + Host string `yaml:"host"` + // Port of the smtp server. + Port int `yaml:"port"` + // Username to use when authenticating with the smtp server. + Username string `yaml:"username"` + // Password to use when authenticating with the smtp server. + Password string `yaml:"password"` + // From address to use when sending emails. + From string `yaml:"from"` +} From f53bbfdb77264083be8931185a2dd1b6b6cce892 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 17:45:38 +0200 Subject: [PATCH 02/44] add email confirm + reset templates --- web/template/email_confirm.tmpl | 29 +++++++++++++++++++++++++++++ web/template/email_reset.tmpl | 29 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 web/template/email_confirm.tmpl create mode 100644 web/template/email_reset.tmpl diff --git a/web/template/email_confirm.tmpl b/web/template/email_confirm.tmpl new file mode 100644 index 0000000000..0a99079219 --- /dev/null +++ b/web/template/email_confirm.tmpl @@ -0,0 +1,29 @@ + + + + +
+

+ Hello {{.Username}}! +

+
+
+

+ You are receiving this mail because you've requested an account on {{.InstanceName}}. +

+

+ We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar: +

+

+ + {{.ConfirmLink}} + +

+
+
+

+ If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of {{.InstanceName}}. +

+
+ + \ No newline at end of file diff --git a/web/template/email_reset.tmpl b/web/template/email_reset.tmpl new file mode 100644 index 0000000000..7318c6a455 --- /dev/null +++ b/web/template/email_reset.tmpl @@ -0,0 +1,29 @@ + + + + +
+

+ Hello {{.Username}}! +

+
+
+

+ You are receiving this mail because a password reset has been requested for your account on {{.InstanceName}}. +

+

+ To reset your password, click here or paste the following in your browser's address bar: +

+

+ + {{.ResetLink}} + +

+
+
+

+ If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of {{.InstanceName}}. +

+
+ + \ No newline at end of file From 3b31e558ab7029c035f924e0eee96bc62de00365 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 17:45:54 +0200 Subject: [PATCH 03/44] add email sender to testrig --- testrig/email.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 testrig/email.go diff --git a/testrig/email.go b/testrig/email.go new file mode 100644 index 0000000000..be4b882e9d --- /dev/null +++ b/testrig/email.go @@ -0,0 +1,33 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package testrig + +import "github.com/superseriousbusiness/gotosocial/internal/email" + +// NewEmailSender returns an email sender with the default test config. +func NewEmailSender(templateBaseDir string) email.Sender { + cfg := NewTestConfig() + cfg.TemplateConfig.BaseDir = templateBaseDir + + s, err := email.NewSender(cfg) + if err != nil { + panic(err) + } + return s +} From c868cc3e654e303640ab2ea2b8ec983324fa67fc Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 17:47:13 +0200 Subject: [PATCH 04/44] flesh out the email sender interface --- internal/email/confirm.go | 52 +++++++++++++++++++++++++++++ internal/email/email.go | 59 ++++++++++++++++++++++++++++++++- internal/email/email_test.go | 36 ++++++++++++++++++++ internal/email/reset.go | 52 +++++++++++++++++++++++++++++ internal/email/util.go | 45 +++++++++++++++++++++++++ internal/email/util_test.go | 64 ++++++++++++++++++++++++++++++++++++ 6 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 internal/email/confirm.go create mode 100644 internal/email/email_test.go create mode 100644 internal/email/reset.go create mode 100644 internal/email/util.go create mode 100644 internal/email/util_test.go diff --git a/internal/email/confirm.go b/internal/email/confirm.go new file mode 100644 index 0000000000..06b5bc1c6c --- /dev/null +++ b/internal/email/confirm.go @@ -0,0 +1,52 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package email + +import ( + "net/smtp" +) + +const ( + confirmTemplate = "email_confirm.tmpl" + confirmSubject = "Subject: GoToSocial Email Confirmation" +) + +func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { + confirmBody, err := s.ExecuteTemplate(confirmTemplate, data) + if err != nil { + return err + } + + msg := AssembleMessage(confirmSubject, confirmBody) + + return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) +} + +// ConfirmData represents data passed into the confirm email address template. +type ConfirmData struct { + // Username to be addressed. + Username string + // URL of the instance to present to the receiver. + InstanceURL string + // Name of the instance to present to the receiver. + InstanceName string + // Link to present to the receiver to click on and do the confirmation. + // Should be a full link with protocol eg., https://example.org/confirm_email?token=some-long-token + ConfirmLink string +} diff --git a/internal/email/email.go b/internal/email/email.go index 3d6a9dd2dd..86d13c6214 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -16,5 +16,62 @@ along with this program. If not, see . */ -// Package email provides a service for interacting with an SMTP server package email + +import ( + "fmt" + "html/template" + "net/smtp" + "os" + "path/filepath" + + "github.com/superseriousbusiness/gotosocial/internal/config" +) + +// Sender contains functions for sending emails to instance users/new signups. +type Sender interface { + // SendConfirmEmail sends a 'please confirm your email' style email to the given toAddress, with the given data. + SendConfirmEmail(toAddress string, data ConfirmData) error + + // SendResetEmail sends a 'reset your password' style email to the given toAddress, with the given data. + SendResetEmail(toAddress string, data ResetData) error + + // ExecuteTemplate returns templated HTML using the given templateName and data. Mostly you won't need to call this, + // and can just call one of the 'Send' functions instead (which calls this under the hood anyway). + ExecuteTemplate(templateName string, data interface{}) (string, error) +} + +func NewSender(cfg *config.Config) (Sender, error) { + t, err := loadTemplates(cfg) + if err != nil { + return nil, err + } + + auth := smtp.PlainAuth("", cfg.SMTPConfig.Username, cfg.SMTPConfig.Password, cfg.SMTPConfig.Host) + + return &sender{ + hostAddress: fmt.Sprintf("%s:%d", cfg.SMTPConfig.Host, cfg.SMTPConfig.Port), + from: cfg.SMTPConfig.From, + auth: auth, + template: t, + }, nil +} + +type sender struct { + hostAddress string + from string + auth smtp.Auth + template *template.Template +} + +// loadTemplates loads html templates for use in emails +func loadTemplates(cfg *config.Config) (*template.Template, error) { + cwd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("error getting current working directory: %s", err) + } + + // look for all templates that start with 'email_' + tmPath := filepath.Join(cwd, fmt.Sprintf("%semail_*", cfg.TemplateConfig.BaseDir)) + return template.ParseGlob(tmPath) +} diff --git a/internal/email/email_test.go b/internal/email/email_test.go new file mode 100644 index 0000000000..54d7e71442 --- /dev/null +++ b/internal/email/email_test.go @@ -0,0 +1,36 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package email_test + +import ( + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/email" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type EmailTestSuite struct { + suite.Suite + + sender email.Sender +} + +func (suite *EmailTestSuite) SetupTest() { + testrig.InitTestLog() + suite.sender = testrig.NewEmailSender("../../web/template/") +} diff --git a/internal/email/reset.go b/internal/email/reset.go new file mode 100644 index 0000000000..473223867b --- /dev/null +++ b/internal/email/reset.go @@ -0,0 +1,52 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package email + +import ( + "net/smtp" +) + +const ( + resetTemplate = "email_reset.tmpl" + resetSubject = "Subject: GoToSocial Password Reset" +) + +func (s *sender) SendResetEmail(toAddress string, data ResetData) error { + resetBody, err := s.ExecuteTemplate(resetTemplate, data) + if err != nil { + return err + } + + msg := AssembleMessage(resetSubject, resetBody) + + return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) +} + +// ResetData represents data passed into the reset email address template. +type ResetData struct { + // Username to be addressed. + Username string + // URL of the instance to present to the receiver. + InstanceURL string + // Name of the instance to present to the receiver. + InstanceName string + // Link to present to the receiver to click on and begin the reset process. + // Should be a full link with protocol eg., https://example.org/reset_password?token=some-reset-password-token + ResetLink string +} diff --git a/internal/email/util.go b/internal/email/util.go new file mode 100644 index 0000000000..460a27f9ee --- /dev/null +++ b/internal/email/util.go @@ -0,0 +1,45 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package email + +import "bytes" + +const ( + mime = `MIME-version: 1.0; +Content-Type: text/plain; charset="UTF-8";` +) + +func (s *sender) ExecuteTemplate(templateName string, data interface{}) (string, error) { + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, templateName, data); err != nil { + return "", err + } + return buf.String(), nil +} + +// AssembleMessage concacenates the mailSubject, the mime header, and the mailBody in +// an appropriate format for sending via net/smtp. +func AssembleMessage(mailSubject string, mailBody string) []byte { + msg := []byte( + mailSubject + "\r\n" + + mime + "\r\n" + + mailBody + "\r\n") + + return msg +} diff --git a/internal/email/util_test.go b/internal/email/util_test.go new file mode 100644 index 0000000000..5d6077ecfb --- /dev/null +++ b/internal/email/util_test.go @@ -0,0 +1,64 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package email_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/email" +) + +type UtilTestSuite struct { + EmailTestSuite +} + +func (suite *UtilTestSuite) TestTemplateConfirm() { + confirmData := email.ConfirmData{ + Username: "test", + InstanceURL: "https://example.org", + InstanceName: "Test Instance", + ConfirmLink: "https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa", + } + mailBody, err := suite.sender.ExecuteTemplate("email_confirm.tmpl", confirmData) + suite.NoError(err) + suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) + + message := email.AssembleMessage("Subject: something", mailBody) + suite.Equal("Subject: something\r\nMIME-version: 1.0;\nContent-Type: text/plain; charset=\"UTF-8\";\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) +} + +func (suite *UtilTestSuite) TestTemplateReset() { + resetData := email.ResetData{ + Username: "test", + InstanceURL: "https://example.org", + InstanceName: "Test Instance", + ResetLink: "https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa", + } + mailBody, err := suite.sender.ExecuteTemplate("email_reset.tmpl", resetData) + suite.NoError(err) + suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) + + message := email.AssembleMessage("Subject: something", mailBody) + suite.Equal("Subject: something\r\nMIME-version: 1.0;\nContent-Type: text/plain; charset=\"UTF-8\";\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) +} + +func TestUtilTestSuite(t *testing.T) { + suite.Run(t, &UtilTestSuite{}) +} From ba1c49b7f9b041d885352021639e10905cb89951 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 17:47:43 +0200 Subject: [PATCH 05/44] go fmt --- internal/email/util_test.go | 2 +- testrig/email.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/email/util_test.go b/internal/email/util_test.go index 5d6077ecfb..820d4073ee 100644 --- a/internal/email/util_test.go +++ b/internal/email/util_test.go @@ -49,7 +49,7 @@ func (suite *UtilTestSuite) TestTemplateReset() { Username: "test", InstanceURL: "https://example.org", InstanceName: "Test Instance", - ResetLink: "https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa", + ResetLink: "https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa", } mailBody, err := suite.sender.ExecuteTemplate("email_reset.tmpl", resetData) suite.NoError(err) diff --git a/testrig/email.go b/testrig/email.go index be4b882e9d..9ce55e026c 100644 --- a/testrig/email.go +++ b/testrig/email.go @@ -26,7 +26,7 @@ func NewEmailSender(templateBaseDir string) email.Sender { cfg.TemplateConfig.BaseDir = templateBaseDir s, err := email.NewSender(cfg) - if err != nil { + if err != nil { panic(err) } return s From bf6da319f26cd9f2ceeb3ba5a5d6b337dc5f24d7 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 17:48:40 +0200 Subject: [PATCH 06/44] golint --- internal/email/email.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/email/email.go b/internal/email/email.go index 86d13c6214..56e5635e28 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -41,6 +41,7 @@ type Sender interface { ExecuteTemplate(templateName string, data interface{}) (string, error) } +// NewSender returns a new email Sender interface with the given configuration, or an error if something goes wrong. func NewSender(cfg *config.Config) (Sender, error) { t, err := loadTemplates(cfg) if err != nil { From f253fce5f15f76a9f70add77cb8dfe2a992c46ca Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 18:29:00 +0200 Subject: [PATCH 07/44] update from field with more clarity --- cmd/gotosocial/smtpflags.go | 2 +- example/config.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/gotosocial/smtpflags.go b/cmd/gotosocial/smtpflags.go index 84556ca009..5c790ef7ef 100644 --- a/cmd/gotosocial/smtpflags.go +++ b/cmd/gotosocial/smtpflags.go @@ -51,7 +51,7 @@ func smtpFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli }, &cli.StringFlag{ Name: flagNames.SMTPFrom, - Usage: "String to use as the 'from' field of the email. Eg., 'gotosocial@example.org'", + Usage: "Address to use as the 'from' field of the email. Eg., 'gotosocial@example.org'", Value: defaults.SMTPFrom, EnvVars: []string{envNames.SMTPFrom}, }, diff --git a/example/config.yaml b/example/config.yaml index 81e6e7c069..51472418fb 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -388,7 +388,7 @@ smtp: # Examples: ["1234", "password"] # Default: "" password: - # String. 'From' field for sent emails. - # Examples: ["mail@example.org", "GoToSocial"] - # Default: "GoToSocial" - from: "GoToSocial" + # String. 'From' address for sent emails. + # Examples: ["mail@example.org"] + # Default: "" + from: "" From 2eda7f484626ec0699e0e34cf7754142888acc76 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 18:41:36 +0200 Subject: [PATCH 08/44] tidy up the email formatting --- internal/email/confirm.go | 3 +-- internal/email/email.go | 3 +++ internal/email/reset.go | 3 +-- internal/email/util.go | 16 +++++++++++----- internal/email/util_test.go | 8 ++++---- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/internal/email/confirm.go b/internal/email/confirm.go index 06b5bc1c6c..d6b3a24071 100644 --- a/internal/email/confirm.go +++ b/internal/email/confirm.go @@ -33,8 +33,7 @@ func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { return err } - msg := AssembleMessage(confirmSubject, confirmBody) - + msg := s.AssembleMessage(confirmSubject, confirmBody, toAddress) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) } diff --git a/internal/email/email.go b/internal/email/email.go index 56e5635e28..69015e252b 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -39,6 +39,9 @@ type Sender interface { // ExecuteTemplate returns templated HTML using the given templateName and data. Mostly you won't need to call this, // and can just call one of the 'Send' functions instead (which calls this under the hood anyway). ExecuteTemplate(templateName string, data interface{}) (string, error) + // AssembleMessage concacenates the mailSubject, the mime header, mailTo, mailFrom and the mailBody in + // an appropriate format for sending via net/smtp. Mostly you won't need to call this, but it's provided just in case. + AssembleMessage(mailSubject string, mailBody string, mailTo string) []byte } // NewSender returns a new email Sender interface with the given configuration, or an error if something goes wrong. diff --git a/internal/email/reset.go b/internal/email/reset.go index 473223867b..a0cd377f65 100644 --- a/internal/email/reset.go +++ b/internal/email/reset.go @@ -33,8 +33,7 @@ func (s *sender) SendResetEmail(toAddress string, data ResetData) error { return err } - msg := AssembleMessage(resetSubject, resetBody) - + msg := s.AssembleMessage(resetSubject, resetBody, toAddress) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) } diff --git a/internal/email/util.go b/internal/email/util.go index 460a27f9ee..87933b130d 100644 --- a/internal/email/util.go +++ b/internal/email/util.go @@ -18,11 +18,14 @@ package email -import "bytes" +import ( + "bytes" + "fmt" +) const ( mime = `MIME-version: 1.0; -Content-Type: text/plain; charset="UTF-8";` +Content-Type: text/html;` ) func (s *sender) ExecuteTemplate(templateName string, data interface{}) (string, error) { @@ -33,11 +36,14 @@ func (s *sender) ExecuteTemplate(templateName string, data interface{}) (string, return buf.String(), nil } -// AssembleMessage concacenates the mailSubject, the mime header, and the mailBody in -// an appropriate format for sending via net/smtp. -func AssembleMessage(mailSubject string, mailBody string) []byte { +func (s *sender) AssembleMessage(mailSubject string, mailBody string, mailTo string) []byte { + from := fmt.Sprintf("From: GoToSocial <%s>", s.from) + to := fmt.Sprintf("To: %s", mailTo) + msg := []byte( mailSubject + "\r\n" + + from + "\r\n" + + to + "\r\n" + mime + "\r\n" + mailBody + "\r\n") diff --git a/internal/email/util_test.go b/internal/email/util_test.go index 820d4073ee..aacb026557 100644 --- a/internal/email/util_test.go +++ b/internal/email/util_test.go @@ -40,8 +40,8 @@ func (suite *UtilTestSuite) TestTemplateConfirm() { suite.NoError(err) suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) - message := email.AssembleMessage("Subject: something", mailBody) - suite.Equal("Subject: something\r\nMIME-version: 1.0;\nContent-Type: text/plain; charset=\"UTF-8\";\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) + message := suite.sender.AssembleMessage("Subject: something", mailBody, "user@example.org") + suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) } func (suite *UtilTestSuite) TestTemplateReset() { @@ -55,8 +55,8 @@ func (suite *UtilTestSuite) TestTemplateReset() { suite.NoError(err) suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) - message := email.AssembleMessage("Subject: something", mailBody) - suite.Equal("Subject: something\r\nMIME-version: 1.0;\nContent-Type: text/plain; charset=\"UTF-8\";\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) + message := suite.sender.AssembleMessage("Subject: something", mailBody, "user@example.org") + suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) } func TestUtilTestSuite(t *testing.T) { From 2ca332250b1e3abd8d8165e73aa80e44a870dd01 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sat, 16 Oct 2021 18:42:20 +0200 Subject: [PATCH 09/44] fix tests --- internal/email/util_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/email/util_test.go b/internal/email/util_test.go index aacb026557..ce06f09d05 100644 --- a/internal/email/util_test.go +++ b/internal/email/util_test.go @@ -41,7 +41,7 @@ func (suite *UtilTestSuite) TestTemplateConfirm() { suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) message := suite.sender.AssembleMessage("Subject: something", mailBody, "user@example.org") - suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) + suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) } func (suite *UtilTestSuite) TestTemplateReset() { @@ -56,7 +56,7 @@ func (suite *UtilTestSuite) TestTemplateReset() { suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) message := suite.sender.AssembleMessage("Subject: something", mailBody, "user@example.org") - suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) + suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) } func TestUtilTestSuite(t *testing.T) { From 5f6a0cd8cff8663972183ceea67559baaacecf9b Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 10:50:57 +0200 Subject: [PATCH 10/44] add email sender to processor --- internal/api/client/account/account_test.go | 17 ++++--- .../api/client/fileserver/servefile_test.go | 6 ++- .../followrequest/followrequest_test.go | 15 ++++--- internal/api/client/media/mediacreate_test.go | 5 ++- internal/api/client/status/status_test.go | 44 ++++++++++++++++--- .../api/client/status/statusboost_test.go | 28 ------------ .../api/client/status/statuscreate_test.go | 28 ------------ internal/api/client/status/statusfave_test.go | 28 ------------ .../api/client/status/statusfavedby_test.go | 28 ------------ internal/api/client/status/statusget_test.go | 29 ------------ .../api/client/status/statusunfave_test.go | 28 ------------ internal/api/client/user/user_test.go | 17 ++++--- internal/api/s2s/user/inboxpost_test.go | 12 +++-- internal/api/s2s/user/repliesget_test.go | 9 ++-- internal/api/s2s/user/user_test.go | 5 ++- internal/api/s2s/user/userget_test.go | 3 +- internal/api/s2s/webfinger/webfinger_test.go | 5 ++- .../api/s2s/webfinger/webfingerget_test.go | 4 +- internal/cliactions/server/server.go | 8 +++- internal/cliactions/testrig/testrig.go | 4 +- internal/processing/account/account.go | 5 ++- internal/processing/account/account_test.go | 5 ++- internal/processing/processor.go | 16 +++++-- internal/processing/processor_test.go | 5 ++- testrig/processor.go | 5 ++- 25 files changed, 139 insertions(+), 220 deletions(-) diff --git a/internal/api/client/account/account_test.go b/internal/api/client/account/account_test.go index f33fd735f6..9dc27457a6 100644 --- a/internal/api/client/account/account_test.go +++ b/internal/api/client/account/account_test.go @@ -12,6 +12,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/account" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -23,12 +24,13 @@ import ( type AccountStandardTestSuite struct { // standard suite interfaces suite.Suite - config *config.Config - db db.DB - tc typeutils.TypeConverter - storage *kv.KVStore - federator federation.Federator - processor processing.Processor + config *config.Config + db db.DB + tc typeutils.TypeConverter + storage *kv.KVStore + federator federation.Federator + processor processing.Processor + emailSender email.Sender // standard suite models testTokens map[string]*gtsmodel.Token @@ -59,7 +61,8 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.accountModule = account.New(suite.config, suite.processor).(*account.Module) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/client/fileserver/servefile_test.go index 60f9740b6f..0d21d12318 100644 --- a/internal/api/client/fileserver/servefile_test.go +++ b/internal/api/client/fileserver/servefile_test.go @@ -34,6 +34,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" @@ -54,6 +55,7 @@ type ServeFileTestSuite struct { processor processing.Processor mediaHandler media.Handler oauthServer oauth.Server + emailSender email.Sender // standard suite models testTokens map[string]*gtsmodel.Token @@ -78,7 +80,9 @@ func (suite *ServeFileTestSuite) SetupSuite() { testrig.InitTestLog() suite.storage = testrig.NewTestStorage() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.tc = testrig.NewTestTypeConverter(suite.db) suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) suite.oauthServer = testrig.NewTestOauthServer(suite.db) diff --git a/internal/api/client/followrequest/followrequest_test.go b/internal/api/client/followrequest/followrequest_test.go index 32b3048a13..98c69f22bb 100644 --- a/internal/api/client/followrequest/followrequest_test.go +++ b/internal/api/client/followrequest/followrequest_test.go @@ -29,6 +29,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -38,11 +39,12 @@ import ( type FollowRequestStandardTestSuite struct { suite.Suite - config *config.Config - db db.DB - storage *kv.KVStore - federator federation.Federator - processor processing.Processor + config *config.Config + db db.DB + storage *kv.KVStore + federator federation.Federator + processor processing.Processor + emailSender email.Sender // standard suite models testTokens map[string]*gtsmodel.Token @@ -73,7 +75,8 @@ func (suite *FollowRequestStandardTestSuite) SetupTest() { suite.db = testrig.NewTestDB() suite.storage = testrig.NewTestStorage() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.followRequestModule = followrequest.New(suite.config, suite.processor).(*followrequest.Module) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 1ee58e7985..7e7f80b482 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -37,6 +37,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" @@ -56,6 +57,7 @@ type MediaCreateTestSuite struct { tc typeutils.TypeConverter mediaHandler media.Handler oauthServer oauth.Server + emailSender email.Sender processor processing.Processor // standard suite models @@ -84,7 +86,8 @@ func (suite *MediaCreateTestSuite) SetupSuite() { suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) suite.oauthServer = testrig.NewTestOauthServer(suite.db) suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) // setup module being tested suite.mediaModule = mediamodule.New(suite.config, suite.processor).(*mediamodule.Module) diff --git a/internal/api/client/status/status_test.go b/internal/api/client/status/status_test.go index 0c0c391b5d..f473803059 100644 --- a/internal/api/client/status/status_test.go +++ b/internal/api/client/status/status_test.go @@ -24,22 +24,24 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/typeutils" + "github.com/superseriousbusiness/gotosocial/testrig" ) -// nolint type StatusStandardTestSuite struct { // standard suite interfaces suite.Suite - config *config.Config - db db.DB - tc typeutils.TypeConverter - federator federation.Federator - processor processing.Processor - storage *kv.KVStore + config *config.Config + db db.DB + tc typeutils.TypeConverter + federator federation.Federator + emailSender email.Sender + processor processing.Processor + storage *kv.KVStore // standard suite models testTokens map[string]*gtsmodel.Token @@ -53,3 +55,31 @@ type StatusStandardTestSuite struct { // module being tested statusModule *status.Module } + +func (suite *StatusStandardTestSuite) SetupSuite() { + suite.testTokens = testrig.NewTestTokens() + suite.testClients = testrig.NewTestClients() + suite.testApplications = testrig.NewTestApplications() + suite.testUsers = testrig.NewTestUsers() + suite.testAccounts = testrig.NewTestAccounts() + suite.testAttachments = testrig.NewTestAttachments() + suite.testStatuses = testrig.NewTestStatuses() +} + +func (suite *StatusStandardTestSuite) SetupTest() { + suite.config = testrig.NewTestConfig() + suite.db = testrig.NewTestDB() + suite.storage = testrig.NewTestStorage() + testrig.InitTestLog() + suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) + suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) + testrig.StandardDBSetup(suite.db, nil) + testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") +} + +func (suite *StatusStandardTestSuite) TearDownTest() { + testrig.StandardDBTeardown(suite.db) + testrig.StandardStorageTeardown(suite.storage) +} diff --git a/internal/api/client/status/statusboost_test.go b/internal/api/client/status/statusboost_test.go index 2a8db1f4dc..b5a3775656 100644 --- a/internal/api/client/status/statusboost_test.go +++ b/internal/api/client/status/statusboost_test.go @@ -30,40 +30,12 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusBoostTestSuite struct { StatusStandardTestSuite } -func (suite *StatusBoostTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *StatusBoostTestSuite) SetupTest() { - suite.config = testrig.NewTestConfig() - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewTestStorage() - testrig.InitTestLog() - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) - suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") -} - -func (suite *StatusBoostTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - func (suite *StatusBoostTestSuite) TestPostBoost() { t := suite.testTokens["local_account_1"] diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/status/statuscreate_test.go index e7c1125ce4..776b257697 100644 --- a/internal/api/client/status/statuscreate_test.go +++ b/internal/api/client/status/statuscreate_test.go @@ -43,34 +43,6 @@ type StatusCreateTestSuite struct { StatusStandardTestSuite } -func (suite *StatusCreateTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *StatusCreateTestSuite) SetupTest() { - suite.config = testrig.NewTestConfig() - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewTestStorage() - testrig.InitTestLog() - suite.tc = testrig.NewTestTypeConverter(suite.db) - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) - suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") -} - -func (suite *StatusCreateTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - var statusWithLinksAndTags = `#test alright, should be able to post #links with fragments in them now, let's see........ https://docs.gotosocial.org/en/latest/user_guide/posts/#links diff --git a/internal/api/client/status/statusfave_test.go b/internal/api/client/status/statusfave_test.go index 3fafe44e42..5b877a2916 100644 --- a/internal/api/client/status/statusfave_test.go +++ b/internal/api/client/status/statusfave_test.go @@ -33,40 +33,12 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusFaveTestSuite struct { StatusStandardTestSuite } -func (suite *StatusFaveTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *StatusFaveTestSuite) SetupTest() { - suite.config = testrig.NewTestConfig() - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewTestStorage() - testrig.InitTestLog() - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) - suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") -} - -func (suite *StatusFaveTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - // fave a status func (suite *StatusFaveTestSuite) TestPostFave() { diff --git a/internal/api/client/status/statusfavedby_test.go b/internal/api/client/status/statusfavedby_test.go index 2958379fa0..0f10d84497 100644 --- a/internal/api/client/status/statusfavedby_test.go +++ b/internal/api/client/status/statusfavedby_test.go @@ -33,40 +33,12 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusFavedByTestSuite struct { StatusStandardTestSuite } -func (suite *StatusFavedByTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *StatusFavedByTestSuite) SetupTest() { - suite.config = testrig.NewTestConfig() - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewTestStorage() - testrig.InitTestLog() - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) - suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") -} - -func (suite *StatusFavedByTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - func (suite *StatusFavedByTestSuite) TestGetFavedBy() { t := suite.testTokens["local_account_2"] oauthToken := oauth.DBTokenToToken(t) diff --git a/internal/api/client/status/statusget_test.go b/internal/api/client/status/statusget_test.go index 17326b466f..1439add5b7 100644 --- a/internal/api/client/status/statusget_test.go +++ b/internal/api/client/status/statusget_test.go @@ -22,41 +22,12 @@ import ( "testing" "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/api/client/status" - "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusGetTestSuite struct { StatusStandardTestSuite } -func (suite *StatusGetTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *StatusGetTestSuite) SetupTest() { - suite.config = testrig.NewTestConfig() - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewTestStorage() - testrig.InitTestLog() - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) - suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") -} - -func (suite *StatusGetTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - func TestStatusGetTestSuite(t *testing.T) { suite.Run(t, new(StatusGetTestSuite)) } diff --git a/internal/api/client/status/statusunfave_test.go b/internal/api/client/status/statusunfave_test.go index e99569e0ab..0809840daf 100644 --- a/internal/api/client/status/statusunfave_test.go +++ b/internal/api/client/status/statusunfave_test.go @@ -33,40 +33,12 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusUnfaveTestSuite struct { StatusStandardTestSuite } -func (suite *StatusUnfaveTestSuite) SetupSuite() { - suite.testTokens = testrig.NewTestTokens() - suite.testClients = testrig.NewTestClients() - suite.testApplications = testrig.NewTestApplications() - suite.testUsers = testrig.NewTestUsers() - suite.testAccounts = testrig.NewTestAccounts() - suite.testAttachments = testrig.NewTestAttachments() - suite.testStatuses = testrig.NewTestStatuses() -} - -func (suite *StatusUnfaveTestSuite) SetupTest() { - suite.config = testrig.NewTestConfig() - suite.db = testrig.NewTestDB() - suite.storage = testrig.NewTestStorage() - testrig.InitTestLog() - suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) - suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) - testrig.StandardDBSetup(suite.db, nil) - testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") -} - -func (suite *StatusUnfaveTestSuite) TearDownTest() { - testrig.StandardDBTeardown(suite.db) - testrig.StandardStorageTeardown(suite.storage) -} - // unfave a status func (suite *StatusUnfaveTestSuite) TestPostUnfave() { diff --git a/internal/api/client/user/user_test.go b/internal/api/client/user/user_test.go index 02f10b5ae2..9ad1185c3a 100644 --- a/internal/api/client/user/user_test.go +++ b/internal/api/client/user/user_test.go @@ -24,6 +24,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/user" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/processing" @@ -33,12 +34,13 @@ import ( type UserStandardTestSuite struct { suite.Suite - config *config.Config - db db.DB - tc typeutils.TypeConverter - federator federation.Federator - processor processing.Processor - storage *kv.KVStore + config *config.Config + db db.DB + tc typeutils.TypeConverter + federator federation.Federator + emailSender email.Sender + processor processing.Processor + storage *kv.KVStore testTokens map[string]*gtsmodel.Token testClients map[string]*gtsmodel.Client @@ -61,7 +63,8 @@ func (suite *UserStandardTestSuite) SetupTest() { testrig.InitTestLog() suite.tc = testrig.NewTestTypeConverter(suite.db) suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.userModule = user.New(suite.config, suite.processor).(*user.Module) testrig.StandardDBSetup(suite.db, suite.testAccounts) testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/s2s/user/inboxpost_test.go index 79c44116d9..527fcdd502 100644 --- a/internal/api/s2s/user/inboxpost_test.go +++ b/internal/api/s2s/user/inboxpost_test.go @@ -85,7 +85,8 @@ func (suite *InboxPostTestSuite) TestPostBlock() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -184,7 +185,8 @@ func (suite *InboxPostTestSuite) TestPostUnblock() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -273,7 +275,8 @@ func (suite *InboxPostTestSuite) TestPostUpdate() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -391,7 +394,8 @@ func (suite *InboxPostTestSuite) TestPostDelete() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) err = processor.Start(context.Background()) suite.NoError(err) userModule := user.New(suite.config, processor).(*user.Module) diff --git a/internal/api/s2s/user/repliesget_test.go b/internal/api/s2s/user/repliesget_test.go index 44717ad423..85ddd01648 100644 --- a/internal/api/s2s/user/repliesget_test.go +++ b/internal/api/s2s/user/repliesget_test.go @@ -49,7 +49,8 @@ func (suite *RepliesGetTestSuite) TestGetReplies() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -108,7 +109,8 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -170,7 +172,8 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request diff --git a/internal/api/s2s/user/user_test.go b/internal/api/s2s/user/user_test.go index 268523724b..e03428188d 100644 --- a/internal/api/s2s/user/user_test.go +++ b/internal/api/s2s/user/user_test.go @@ -25,6 +25,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/security" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/processing" @@ -39,6 +40,7 @@ type UserStandardTestSuite struct { db db.DB tc typeutils.TypeConverter federator federation.Federator + emailSender email.Sender processor processing.Processor storage *kv.KVStore securityModule *security.Module @@ -75,7 +77,8 @@ func (suite *UserStandardTestSuite) SetupTest() { suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.userModule = user.New(suite.config, suite.processor).(*user.Module) suite.securityModule = security.New(suite.config, suite.db).(*security.Module) testrig.StandardDBSetup(suite.db, suite.testAccounts) diff --git a/internal/api/s2s/user/userget_test.go b/internal/api/s2s/user/userget_test.go index d8d1df148a..694b119625 100644 --- a/internal/api/s2s/user/userget_test.go +++ b/internal/api/s2s/user/userget_test.go @@ -47,7 +47,8 @@ func (suite *UserGetTestSuite) TestGetUser() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/") + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request diff --git a/internal/api/s2s/webfinger/webfinger_test.go b/internal/api/s2s/webfinger/webfinger_test.go index ee1ff66078..3a7697e668 100644 --- a/internal/api/s2s/webfinger/webfinger_test.go +++ b/internal/api/s2s/webfinger/webfinger_test.go @@ -30,6 +30,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/security" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/processing" @@ -44,6 +45,7 @@ type WebfingerStandardTestSuite struct { db db.DB tc typeutils.TypeConverter federator federation.Federator + emailSender email.Sender processor processing.Processor storage *kv.KVStore securityModule *security.Module @@ -78,7 +80,8 @@ func (suite *WebfingerStandardTestSuite) SetupTest() { suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.webfingerModule = webfinger.New(suite.config, suite.processor).(*webfinger.Module) suite.securityModule = security.New(suite.config, suite.db).(*security.Module) testrig.StandardDBSetup(suite.db, suite.testAccounts) diff --git a/internal/api/s2s/webfinger/webfingerget_test.go b/internal/api/s2s/webfinger/webfingerget_test.go index 3b38c5a7e5..19c6379298 100644 --- a/internal/api/s2s/webfinger/webfingerget_test.go +++ b/internal/api/s2s/webfinger/webfingerget_test.go @@ -65,7 +65,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUser() { func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHost() { suite.config.Host = "gts.example.org" suite.config.AccountDomain = "example.org" - suite.processor = processing.NewProcessor(suite.config, suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaHandler(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db) + suite.processor = processing.NewProcessor(suite.config, suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaHandler(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db, suite.emailSender) suite.webfingerModule = webfinger.New(suite.config, suite.processor).(*webfinger.Module) targetAccount := accountDomainAccount() @@ -97,7 +97,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHo func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByAccountDomain() { suite.config.Host = "gts.example.org" suite.config.AccountDomain = "example.org" - suite.processor = processing.NewProcessor(suite.config, suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaHandler(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db) + suite.processor = processing.NewProcessor(suite.config, suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaHandler(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db, suite.emailSender) suite.webfingerModule = webfinger.New(suite.config, suite.processor).(*webfinger.Module) targetAccount := accountDomainAccount() diff --git a/internal/cliactions/server/server.go b/internal/cliactions/server/server.go index 107d84d2c1..92700d4527 100644 --- a/internal/cliactions/server/server.go +++ b/internal/cliactions/server/server.go @@ -36,6 +36,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/cliactions" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db/bundb" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" "github.com/superseriousbusiness/gotosocial/internal/gotosocial" @@ -91,7 +92,12 @@ var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config) err oauthServer := oauth.New(ctx, dbService) transportController := transport.NewController(c, dbService, &federation.Clock{}, http.DefaultClient) federator := federation.NewFederator(dbService, federatingDB, transportController, c, typeConverter, mediaHandler) - processor := processing.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storage, timelineManager, dbService) + emailSender, err := email.NewSender(c) + if err != nil { + return fmt.Errorf("error creating email sender: %s", err) + } + + processor := processing.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storage, timelineManager, dbService, emailSender) if err := processor.Start(ctx); err != nil { return fmt.Errorf("error starting processor: %s", err) } diff --git a/internal/cliactions/testrig/testrig.go b/internal/cliactions/testrig/testrig.go index 2993d86130..8295692bac 100644 --- a/internal/cliactions/testrig/testrig.go +++ b/internal/cliactions/testrig/testrig.go @@ -62,7 +62,9 @@ var Start cliactions.GTSAction = func(ctx context.Context, _ *config.Config) err }), dbService) federator := testrig.NewTestFederator(dbService, transportController, storageBackend) - processor := testrig.NewTestProcessor(dbService, storageBackend, federator) + emailSender := testrig.NewEmailSender("./web/template/") + + processor := testrig.NewTestProcessor(dbService, storageBackend, federator, emailSender) if err := processor.Start(ctx); err != nil { return fmt.Errorf("error starting processor: %s", err) } diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go index e88cd3a94f..e8715d1882 100644 --- a/internal/processing/account/account.go +++ b/internal/processing/account/account.go @@ -25,6 +25,7 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -86,10 +87,11 @@ type processor struct { formatter text.Formatter db db.DB federator federation.Federator + emailSender email.Sender } // New returns a new account processor. -func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator, config *config.Config) Processor { +func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator, emailSender email.Sender, config *config.Config) Processor { return &processor{ tc: tc, config: config, @@ -100,5 +102,6 @@ func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauth formatter: text.NewFormatter(config, db), db: db, federator: federator, + emailSender: emailSender, } } diff --git a/internal/processing/account/account_test.go b/internal/processing/account/account_test.go index f182909447..d4f60e8501 100644 --- a/internal/processing/account/account_test.go +++ b/internal/processing/account/account_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" @@ -48,6 +49,7 @@ type AccountStandardTestSuite struct { httpClient pub.HttpClient transportController transport.Controller federator federation.Federator + emailSender email.Sender // standard suite models testTokens map[string]*gtsmodel.Token @@ -84,7 +86,8 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.httpClient = testrig.NewMockHTTPClient(nil) suite.transportController = testrig.NewTestTransportController(suite.httpClient, suite.db) suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage) - suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaHandler, suite.oauthServer, suite.fromClientAPIChan, suite.federator, suite.config) + suite.emailSender = testrig.NewEmailSender("../../../web/template/") + suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaHandler, suite.oauthServer, suite.fromClientAPIChan, suite.federator, suite.emailSender, suite.config) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") } diff --git a/internal/processing/processor.go b/internal/processing/processor.go index 8fd3727357..641ed58b83 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -28,6 +28,7 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -256,14 +257,23 @@ type processor struct { userProcessor user.Processor } -// NewProcessor returns a new Processor that uses the given federator -func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, mediaHandler media.Handler, storage *kv.KVStore, timelineManager timeline.Manager, db db.DB) Processor { +// NewProcessor returns a new Processor. +func NewProcessor( + config *config.Config, + tc typeutils.TypeConverter, + federator federation.Federator, + oauthServer oauth.Server, + mediaHandler media.Handler, + storage *kv.KVStore, + timelineManager timeline.Manager, + db db.DB, + emailSender email.Sender) Processor { fromClientAPI := make(chan messages.FromClientAPI, 1000) fromFederator := make(chan messages.FromFederator, 1000) statusProcessor := status.New(db, tc, config, fromClientAPI) streamingProcessor := streaming.New(db, oauthServer) - accountProcessor := account.New(db, tc, mediaHandler, oauthServer, fromClientAPI, federator, config) + accountProcessor := account.New(db, tc, mediaHandler, oauthServer, fromClientAPI, federator, emailSender, config) adminProcessor := admin.New(db, tc, mediaHandler, fromClientAPI, config) mediaProcessor := mediaProcessor.New(db, tc, mediaHandler, storage, config) userProcessor := user.New(db, config) diff --git a/internal/processing/processor_test.go b/internal/processing/processor_test.go index beae5dba07..91e9e47c73 100644 --- a/internal/processing/processor_test.go +++ b/internal/processing/processor_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" @@ -54,6 +55,7 @@ type ProcessingStandardTestSuite struct { oauthServer oauth.Server mediaHandler media.Handler timelineManager timeline.Manager + emailSender email.Sender // standard suite models testTokens map[string]*gtsmodel.Token @@ -219,8 +221,9 @@ func (suite *ProcessingStandardTestSuite) SetupTest() { suite.oauthServer = testrig.NewTestOauthServer(suite.db) suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) suite.timelineManager = testrig.NewTestTimelineManager(suite.db) + suite.emailSender = testrig.NewEmailSender("../../web/template/") - suite.processor = processing.NewProcessor(suite.config, suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaHandler, suite.storage, suite.timelineManager, suite.db) + suite.processor = processing.NewProcessor(suite.config, suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaHandler, suite.storage, suite.timelineManager, suite.db, suite.emailSender) testrig.StandardDBSetup(suite.db, suite.testAccounts) testrig.StandardStorageSetup(suite.storage, "../../testrig/media") diff --git a/testrig/processor.go b/testrig/processor.go index 88ded9ca77..fa91250b96 100644 --- a/testrig/processor.go +++ b/testrig/processor.go @@ -21,11 +21,12 @@ package testrig import ( "git.iim.gay/grufwub/go-store/kv" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/processing" ) // NewTestProcessor returns a Processor suitable for testing purposes -func NewTestProcessor(db db.DB, storage *kv.KVStore, federator federation.Federator) processing.Processor { - return processing.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, NewTestTimelineManager(db), db) +func NewTestProcessor(db db.DB, storage *kv.KVStore, federator federation.Federator, emailSender email.Sender) processing.Processor { + return processing.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, NewTestTimelineManager(db), db, emailSender) } From ec4b3c9e4f16381387116c7cd1133f02f27e8367 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 14:07:07 +0200 Subject: [PATCH 11/44] tidy client api processing a bit --- internal/processing/fromclientapi.go | 374 +++++++++++++++------------ 1 file changed, 215 insertions(+), 159 deletions(-) diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index d4e8f5fa59..6ce3b7de33 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -38,216 +38,272 @@ func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages switch clientMsg.APObjectType { case ap.ObjectNote: // CREATE NOTE - status, ok := clientMsg.GTSModel.(*gtsmodel.Status) - if !ok { - return errors.New("note was not parseable as *gtsmodel.Status") - } - - if err := p.timelineStatus(ctx, status); err != nil { - return err - } - - if err := p.notifyStatus(ctx, status); err != nil { - return err - } - - if status.Federated { - return p.federateStatus(ctx, status) - } + return p.processCreateStatusFromClientAPI(ctx, clientMsg) case ap.ActivityFollow: // CREATE FOLLOW REQUEST - followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) - if !ok { - return errors.New("followrequest was not parseable as *gtsmodel.FollowRequest") - } - - if err := p.notifyFollowRequest(ctx, followRequest); err != nil { - return err - } - - return p.federateFollow(ctx, followRequest, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.processCreateFollowRequestFromClientAPI(ctx, clientMsg) case ap.ActivityLike: // CREATE LIKE/FAVE - fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) - if !ok { - return errors.New("fave was not parseable as *gtsmodel.StatusFave") - } - - if err := p.notifyFave(ctx, fave); err != nil { - return err - } - - return p.federateFave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.processCreateFaveFromClientAPI(ctx, clientMsg) case ap.ActivityAnnounce: // CREATE BOOST/ANNOUNCE - boostWrapperStatus, ok := clientMsg.GTSModel.(*gtsmodel.Status) - if !ok { - return errors.New("boost was not parseable as *gtsmodel.Status") - } - - if err := p.timelineStatus(ctx, boostWrapperStatus); err != nil { - return err - } - - if err := p.notifyAnnounce(ctx, boostWrapperStatus); err != nil { - return err - } - - return p.federateAnnounce(ctx, boostWrapperStatus, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.processCreateAnnounceFromClientAPI(ctx, clientMsg) case ap.ActivityBlock: // CREATE BLOCK - block, ok := clientMsg.GTSModel.(*gtsmodel.Block) - if !ok { - return errors.New("block was not parseable as *gtsmodel.Block") - } - - // remove any of the blocking account's statuses from the blocked account's timeline, and vice versa - if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil { - return err - } - if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil { - return err - } - - // TODO: same with notifications - // TODO: same with bookmarks - - return p.federateBlock(ctx, block) + return p.processCreateBlockFromClientAPI(ctx, clientMsg) } case ap.ActivityUpdate: // UPDATE switch clientMsg.APObjectType { case ap.ObjectProfile, ap.ActorPerson: // UPDATE ACCOUNT/PROFILE - account, ok := clientMsg.GTSModel.(*gtsmodel.Account) - if !ok { - return errors.New("account was not parseable as *gtsmodel.Account") - } - - return p.federateAccountUpdate(ctx, account, clientMsg.OriginAccount) + return p.processUpdateAccountFromClientAPI(ctx, clientMsg) } case ap.ActivityAccept: // ACCEPT switch clientMsg.APObjectType { case ap.ActivityFollow: // ACCEPT FOLLOW - follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) - if !ok { - return errors.New("accept was not parseable as *gtsmodel.Follow") - } - - if err := p.notifyFollow(ctx, follow, clientMsg.TargetAccount); err != nil { - return err - } - - return p.federateAcceptFollowRequest(ctx, follow) + return p.processAcceptFollowFromClientAPI(ctx, clientMsg) } case ap.ActivityReject: // REJECT switch clientMsg.APObjectType { case ap.ActivityFollow: // REJECT FOLLOW (request) - followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) - if !ok { - return errors.New("reject was not parseable as *gtsmodel.FollowRequest") - } - - return p.federateRejectFollowRequest(ctx, followRequest) + return p.processRejectFollowFromClientAPI(ctx, clientMsg) } case ap.ActivityUndo: // UNDO switch clientMsg.APObjectType { case ap.ActivityFollow: // UNDO FOLLOW - follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) - if !ok { - return errors.New("undo was not parseable as *gtsmodel.Follow") - } - return p.federateUnfollow(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.processUndoFollowFromClientAPI(ctx, clientMsg) case ap.ActivityBlock: // UNDO BLOCK - block, ok := clientMsg.GTSModel.(*gtsmodel.Block) - if !ok { - return errors.New("undo was not parseable as *gtsmodel.Block") - } - return p.federateUnblock(ctx, block) + return p.processUndoBlockFromClientAPI(ctx, clientMsg) case ap.ActivityLike: // UNDO LIKE/FAVE - fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) - if !ok { - return errors.New("undo was not parseable as *gtsmodel.StatusFave") - } - return p.federateUnfave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.processUndoFaveFromClientAPI(ctx, clientMsg) case ap.ActivityAnnounce: // UNDO ANNOUNCE/BOOST - boost, ok := clientMsg.GTSModel.(*gtsmodel.Status) - if !ok { - return errors.New("undo was not parseable as *gtsmodel.Status") - } - - if err := p.deleteStatusFromTimelines(ctx, boost); err != nil { - return err - } - - return p.federateUnannounce(ctx, boost, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.processUndoAnnounceFromClientAPI(ctx, clientMsg) } case ap.ActivityDelete: // DELETE switch clientMsg.APObjectType { case ap.ObjectNote: // DELETE STATUS/NOTE - statusToDelete, ok := clientMsg.GTSModel.(*gtsmodel.Status) - if !ok { - return errors.New("note was not parseable as *gtsmodel.Status") - } - - if statusToDelete.Account == nil { - statusToDelete.Account = clientMsg.OriginAccount - } - - // delete all attachments for this status - for _, a := range statusToDelete.AttachmentIDs { - if err := p.mediaProcessor.Delete(ctx, a); err != nil { - return err - } - } - - // delete all mentions for this status - for _, m := range statusToDelete.MentionIDs { - if err := p.db.DeleteByID(ctx, m, >smodel.Mention{}); err != nil { - return err - } - } - - // delete all notifications for this status - if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: statusToDelete.ID}}, &[]*gtsmodel.Notification{}); err != nil { - return err - } - - // delete this status from any and all timelines - if err := p.deleteStatusFromTimelines(ctx, statusToDelete); err != nil { - return err - } - - return p.federateStatusDelete(ctx, statusToDelete) + return p.processDeleteStatusFromClientAPI(ctx, clientMsg) case ap.ObjectProfile, ap.ActorPerson: // DELETE ACCOUNT/PROFILE - - // the origin of the delete could be either a domain block, or an action by another (or this) account - var origin string - if domainBlock, ok := clientMsg.GTSModel.(*gtsmodel.DomainBlock); ok { - // origin is a domain block - origin = domainBlock.ID - } else { - // origin is whichever account caused this message - origin = clientMsg.OriginAccount.ID - } - return p.accountProcessor.Delete(ctx, clientMsg.TargetAccount, origin) + return p.processDeleteAccountFromClientAPI(ctx, clientMsg) } } return nil } +func (p *processor) processCreateStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + status, ok := clientMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("note was not parseable as *gtsmodel.Status") + } + + if err := p.timelineStatus(ctx, status); err != nil { + return err + } + + if err := p.notifyStatus(ctx, status); err != nil { + return err + } + + if status.Federated { + return p.federateStatus(ctx, status) + } + return nil +} + +func (p *processor) processCreateFollowRequestFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) + if !ok { + return errors.New("followrequest was not parseable as *gtsmodel.FollowRequest") + } + + if err := p.notifyFollowRequest(ctx, followRequest); err != nil { + return err + } + + return p.federateFollow(ctx, followRequest, clientMsg.OriginAccount, clientMsg.TargetAccount) +} + +func (p *processor) processCreateFaveFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) + if !ok { + return errors.New("fave was not parseable as *gtsmodel.StatusFave") + } + + if err := p.notifyFave(ctx, fave); err != nil { + return err + } + + return p.federateFave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) +} + +func (p *processor) processCreateAnnounceFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + boostWrapperStatus, ok := clientMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("boost was not parseable as *gtsmodel.Status") + } + + if err := p.timelineStatus(ctx, boostWrapperStatus); err != nil { + return err + } + + if err := p.notifyAnnounce(ctx, boostWrapperStatus); err != nil { + return err + } + + return p.federateAnnounce(ctx, boostWrapperStatus, clientMsg.OriginAccount, clientMsg.TargetAccount) +} + +func (p *processor) processCreateBlockFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + block, ok := clientMsg.GTSModel.(*gtsmodel.Block) + if !ok { + return errors.New("block was not parseable as *gtsmodel.Block") + } + + // remove any of the blocking account's statuses from the blocked account's timeline, and vice versa + if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil { + return err + } + if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil { + return err + } + + // TODO: same with notifications + // TODO: same with bookmarks + + return p.federateBlock(ctx, block) +} + +func (p *processor) processUpdateAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + account, ok := clientMsg.GTSModel.(*gtsmodel.Account) + if !ok { + return errors.New("account was not parseable as *gtsmodel.Account") + } + + return p.federateAccountUpdate(ctx, account, clientMsg.OriginAccount) +} + +func (p *processor) processAcceptFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) + if !ok { + return errors.New("accept was not parseable as *gtsmodel.Follow") + } + + if err := p.notifyFollow(ctx, follow, clientMsg.TargetAccount); err != nil { + return err + } + + return p.federateAcceptFollowRequest(ctx, follow) +} + +func (p *processor) processRejectFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) + if !ok { + return errors.New("reject was not parseable as *gtsmodel.FollowRequest") + } + + return p.federateRejectFollowRequest(ctx, followRequest) +} + +func (p *processor) processUndoFollowFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) + if !ok { + return errors.New("undo was not parseable as *gtsmodel.Follow") + } + return p.federateUnfollow(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) +} + +func (p *processor) processUndoBlockFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + block, ok := clientMsg.GTSModel.(*gtsmodel.Block) + if !ok { + return errors.New("undo was not parseable as *gtsmodel.Block") + } + return p.federateUnblock(ctx, block) +} + +func (p *processor) processUndoFaveFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) + if !ok { + return errors.New("undo was not parseable as *gtsmodel.StatusFave") + } + return p.federateUnfave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) +} + +func (p *processor) processUndoAnnounceFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + boost, ok := clientMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("undo was not parseable as *gtsmodel.Status") + } + + if err := p.deleteStatusFromTimelines(ctx, boost); err != nil { + return err + } + + return p.federateUnannounce(ctx, boost, clientMsg.OriginAccount, clientMsg.TargetAccount) +} + +func (p *processor) processDeleteStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + statusToDelete, ok := clientMsg.GTSModel.(*gtsmodel.Status) + if !ok { + return errors.New("note was not parseable as *gtsmodel.Status") + } + + if statusToDelete.Account == nil { + statusToDelete.Account = clientMsg.OriginAccount + } + + // delete all attachments for this status + for _, a := range statusToDelete.AttachmentIDs { + if err := p.mediaProcessor.Delete(ctx, a); err != nil { + return err + } + } + + // delete all mentions for this status + for _, m := range statusToDelete.MentionIDs { + if err := p.db.DeleteByID(ctx, m, >smodel.Mention{}); err != nil { + return err + } + } + + // delete all notifications for this status + if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: statusToDelete.ID}}, &[]*gtsmodel.Notification{}); err != nil { + return err + } + + // delete this status from any and all timelines + if err := p.deleteStatusFromTimelines(ctx, statusToDelete); err != nil { + return err + } + + return p.federateStatusDelete(ctx, statusToDelete) +} + +func (p *processor) processDeleteAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + // the origin of the delete could be either a domain block, or an action by another (or this) account + var origin string + if domainBlock, ok := clientMsg.GTSModel.(*gtsmodel.DomainBlock); ok { + // origin is a domain block + origin = domainBlock.ID + } else { + // origin is whichever account caused this message + origin = clientMsg.OriginAccount.ID + } + return p.accountProcessor.Delete(ctx, clientMsg.TargetAccount, origin) +} + // TODO: move all the below functions into federation.Federator func (p *processor) federateStatus(ctx context.Context, status *gtsmodel.Status) error { From 784b648c337ba732536e4d2461a8bf8027df7f75 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 14:27:52 +0200 Subject: [PATCH 12/44] further tidying in fromClientAPI --- internal/processing/fromclientapi.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index 6ce3b7de33..b8136b7983 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -117,10 +117,7 @@ func (p *processor) processCreateStatusFromClientAPI(ctx context.Context, client return err } - if status.Federated { - return p.federateStatus(ctx, status) - } - return nil + return p.federateStatus(ctx, status) } func (p *processor) processCreateFollowRequestFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { @@ -307,6 +304,11 @@ func (p *processor) processDeleteAccountFromClientAPI(ctx context.Context, clien // TODO: move all the below functions into federation.Federator func (p *processor) federateStatus(ctx context.Context, status *gtsmodel.Status) error { + // do nothing if the status shouldn't be federated + if !status.Federated { + return nil + } + if status.Account == nil { statusAccount, err := p.db.GetAccountByID(ctx, status.AccountID) if err != nil { From 556ac3da81594946ede1bd36820e8a2932353627 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 14:38:19 +0200 Subject: [PATCH 13/44] pin new account to user --- internal/db/bundb/admin.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go index 79ef4d1427..4381c1de85 100644 --- a/internal/db/bundb/admin.go +++ b/internal/db/bundb/admin.go @@ -24,12 +24,13 @@ import ( "crypto/rsa" "database/sql" "fmt" - "github.com/sirupsen/logrus" "net" "net/mail" "strings" "time" + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -145,6 +146,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, u := >smodel.User{ ID: newUserID, AccountID: acct.ID, + Account: acct, EncryptedPassword: string(pw), SignUpIP: signUpIP.To4(), Locale: locale, From 212fd84cfea56dc291f8796fdb9ed1221b2fa4a3 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 14:38:32 +0200 Subject: [PATCH 14/44] send msg to processor on new account creation --- internal/processing/account/create.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/processing/account/create.go b/internal/processing/account/create.go index 56557af42b..a13e84a784 100644 --- a/internal/processing/account/create.go +++ b/internal/processing/account/create.go @@ -21,10 +21,13 @@ package account import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/ap" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/text" "github.com/superseriousbusiness/oauth2/v4" ) @@ -66,6 +69,23 @@ func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInf return nil, fmt.Errorf("error creating new access token for user %s: %s", user.ID, err) } + if user.Account == nil { + a, err := p.db.GetAccountByID(ctx, user.AccountID) + if err != nil { + return nil, fmt.Errorf("error getting new account from the database: %s", err) + } + user.Account = a + } + + // there are side effects for creating a new account (sending confirmation emails etc) + // so pass a message to the processor so that it can do it asynchronously + p.fromClientAPI <- messages.FromClientAPI{ + APObjectType: ap.ObjectProfile, + APActivityType: ap.ActivityCreate, + GTSModel: user.Account, + OriginAccount: user.Account, + } + return &apimodel.Token{ AccessToken: accessToken.GetAccess(), TokenType: "Bearer", From 1cc9742d6394a0f9339e2a5a90aeb22633ff0937 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 14:58:48 +0200 Subject: [PATCH 15/44] generate confirm email uri --- internal/util/uri.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/util/uri.go b/internal/util/uri.go index 5945c7bdd6..d1ae1de41a 100644 --- a/internal/util/uri.go +++ b/internal/util/uri.go @@ -54,6 +54,8 @@ const ( UpdatePath = "updates" // BlocksPath is used to generate the URI for a block BlocksPath = "blocks" + // ConfirmEmailPath is used to generate the URI for an email confirmation link + ConfirmEmailPath = "confirm_email" ) // APContextKey is a type used specifically for settings values on contexts within go-fed AP request chains @@ -136,6 +138,12 @@ func GenerateURIForBlock(username string, protocol string, host string, thisBloc return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, BlocksPath, thisBlockID) } +// GenerateURIForEmailConfirm returns a link for email confirmation -- something like: +// https://example.org/confirm_email?token=490e337c-0162-454f-ac48-4b22bb92a205 +func GenerateURIForEmailConfirm(protocol string, host string, token string) string { + return fmt.Sprintf("%s://%s/%s?token=%s", protocol, host, ConfirmEmailPath, token) +} + // GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host. func GenerateURIsForAccount(username string, protocol string, host string) *UserURIs { // The below URLs are used for serving web requests From 0fee68f5fedf9859ccc00ae8af208ee9c37f8623 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 15:23:53 +0200 Subject: [PATCH 16/44] remove emailer from account processor again --- internal/processing/account/account.go | 5 +---- internal/processing/account/account_test.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go index e8715d1882..e88cd3a94f 100644 --- a/internal/processing/account/account.go +++ b/internal/processing/account/account.go @@ -25,7 +25,6 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -87,11 +86,10 @@ type processor struct { formatter text.Formatter db db.DB federator federation.Federator - emailSender email.Sender } // New returns a new account processor. -func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator, emailSender email.Sender, config *config.Config) Processor { +func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator, config *config.Config) Processor { return &processor{ tc: tc, config: config, @@ -102,6 +100,5 @@ func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauth formatter: text.NewFormatter(config, db), db: db, federator: federator, - emailSender: emailSender, } } diff --git a/internal/processing/account/account_test.go b/internal/processing/account/account_test.go index d4f60e8501..e8ef9ca684 100644 --- a/internal/processing/account/account_test.go +++ b/internal/processing/account/account_test.go @@ -87,7 +87,7 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.transportController = testrig.NewTestTransportController(suite.httpClient, suite.db) suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage) suite.emailSender = testrig.NewEmailSender("../../../web/template/") - suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaHandler, suite.oauthServer, suite.fromClientAPIChan, suite.federator, suite.emailSender, suite.config) + suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaHandler, suite.oauthServer, suite.fromClientAPIChan, suite.federator, suite.config) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") } From a558cb1dda784a074fe6abcb7196ea4862c6c0e3 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 15:24:20 +0200 Subject: [PATCH 17/44] add processCreateAccountFromClientAPI --- internal/processing/fromclientapi.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index b8136b7983..dc518cb537 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -36,6 +36,9 @@ func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages case ap.ActivityCreate: // CREATE switch clientMsg.APObjectType { + case ap.ObjectProfile, ap.ActorPerson: + // CREATE ACCOUNT/PROFILE + return p.processCreateAccountFromClientAPI(ctx, clientMsg) case ap.ObjectNote: // CREATE NOTE return p.processCreateStatusFromClientAPI(ctx, clientMsg) @@ -103,6 +106,27 @@ func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages return nil } +func (p *processor) processCreateAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { + account, ok := clientMsg.GTSModel.(*gtsmodel.Account) + if !ok { + return errors.New("account was not parseable as *gtsmodel.Account") + } + + // return if the account isn't from this domain + if account.Domain != "" { + return nil + } + + // get the user this account belongs to + user := >smodel.User{} + if err := p.db.GetWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, user); err != nil { + return err + } + + // email a confirmation to this user + return p.userProcessor.SendConfirmEmail(ctx, user, account.Username) +} + func (p *processor) processCreateStatusFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { status, ok := clientMsg.GTSModel.(*gtsmodel.Status) if !ok { From 49a5e481e023998e34551677202da2f1b018dd17 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 15:24:47 +0200 Subject: [PATCH 18/44] move emailer accountprocessor => userprocessor --- internal/processing/processor.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/processing/processor.go b/internal/processing/processor.go index 641ed58b83..abaafecdae 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -273,10 +273,10 @@ func NewProcessor( statusProcessor := status.New(db, tc, config, fromClientAPI) streamingProcessor := streaming.New(db, oauthServer) - accountProcessor := account.New(db, tc, mediaHandler, oauthServer, fromClientAPI, federator, emailSender, config) + accountProcessor := account.New(db, tc, mediaHandler, oauthServer, fromClientAPI, federator, config) adminProcessor := admin.New(db, tc, mediaHandler, fromClientAPI, config) mediaProcessor := mediaProcessor.New(db, tc, mediaHandler, storage, config) - userProcessor := user.New(db, config) + userProcessor := user.New(db, emailSender, config) return &processor{ fromClientAPI: fromClientAPI, From 55b643cc029c4644b5776efd5a25ade57802012a Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 15:25:08 +0200 Subject: [PATCH 19/44] add email sender to user processor --- internal/processing/user/user.go | 14 +++++++++----- internal/processing/user/user_test.go | 10 +++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/internal/processing/user/user.go b/internal/processing/user/user.go index c572becc26..3a94b219fb 100644 --- a/internal/processing/user/user.go +++ b/internal/processing/user/user.go @@ -23,6 +23,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -32,17 +33,20 @@ type Processor interface { // ChangePassword changes the specified user's password from old => new, // or returns an error if the new password is too weak, or the old password is incorrect. ChangePassword(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode + SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error } type processor struct { - config *config.Config - db db.DB + config *config.Config + emailSender email.Sender + db db.DB } // New returns a new user processor -func New(db db.DB, config *config.Config) Processor { +func New(db db.DB, emailSender email.Sender, config *config.Config) Processor { return &processor{ - config: config, - db: db, + config: config, + emailSender: emailSender, + db: db, } } diff --git a/internal/processing/user/user_test.go b/internal/processing/user/user_test.go index 4f18e03c24..5d861ec1fc 100644 --- a/internal/processing/user/user_test.go +++ b/internal/processing/user/user_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/processing/user" "github.com/superseriousbusiness/gotosocial/testrig" @@ -29,8 +30,9 @@ import ( type UserStandardTestSuite struct { suite.Suite - config *config.Config - db db.DB + config *config.Config + emailSender email.Sender + db db.DB testUsers map[string]*gtsmodel.User @@ -41,8 +43,10 @@ func (suite *UserStandardTestSuite) SetupTest() { testrig.InitTestLog() suite.config = testrig.NewTestConfig() suite.db = testrig.NewTestDB() + suite.emailSender = testrig.NewEmailSender("../../../web/template/") suite.testUsers = testrig.NewTestUsers() - suite.user = user.New(suite.db, suite.config) + + suite.user = user.New(suite.db, suite.emailSender, suite.config) testrig.StandardDBSetup(suite.db, nil) } From a455c0186f5ed71813c4bf2350b62cf220b888d5 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 15:25:20 +0200 Subject: [PATCH 20/44] SendConfirmEmail function --- internal/processing/user/emailconfirm.go | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 internal/processing/user/emailconfirm.go diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go new file mode 100644 index 0000000000..2a68ddd7a5 --- /dev/null +++ b/internal/processing/user/emailconfirm.go @@ -0,0 +1,78 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package user + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/email" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error { + if user.UnconfirmedEmail == user.Email { + // user has already confirmed this email address, so there's nothing to do + return nil + } + + // We need a token and a link for the user to click on. + // We'll use a uuid as our token since it's basically impossible to guess. + // From the uuid package we use (which uses crypto/rand under the hood): + // Randomly generated UUIDs have 122 random bits. One's annual risk of being + // hit by a meteorite is estimated to be one chance in 17 billion, that + // means the probability is about 0.00000000006 (6 × 10−11), + // equivalent to the odds of creating a few tens of trillions of UUIDs in a + // year and having one duplicate. + confirmationToken := uuid.NewString() + confirmationLink := util.GenerateURIForEmailConfirm(p.config.Protocol, p.config.Host, confirmationToken) + + // pull our instance entry from the database so we can greet the user nicely in the email + instance := >smodel.Instance{} + if err := p.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: p.config.Host}}, instance); err != nil { + return fmt.Errorf("SendConfirmEmail: error getting instance: %s", err) + } + + // assemble the email contents and send the email + confirmData := email.ConfirmData{ + Username: username, + InstanceURL: instance.URI, + InstanceName: instance.Title, + ConfirmLink: confirmationLink, + } + if err := p.emailSender.SendConfirmEmail(user.Email, confirmData); err != nil { + return fmt.Errorf("SendConfirmEmail: error sending to email address %s belonging to user %s: %s", user.Email, username, err) + } + + // email sent, now we need to update the user entry with the token we just sent them + user.ConfirmationSentAt = time.Now() + user.ConfirmationToken = confirmationToken + user.LastEmailedAt = time.Now() + user.UpdatedAt = time.Now() + + if err := p.db.UpdateByPrimaryKey(ctx, user); err != nil { + return fmt.Errorf("SendConfirmEmail: error updating user entry after email sent: %s", err) + } + + return nil +} From b0cc815189b7247d2fabd16c557bdfa4acf1e5d7 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 16:19:13 +0200 Subject: [PATCH 21/44] add noop email sender --- internal/email/noop.go | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 internal/email/noop.go diff --git a/internal/email/noop.go b/internal/email/noop.go new file mode 100644 index 0000000000..e80c078a48 --- /dev/null +++ b/internal/email/noop.go @@ -0,0 +1,57 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package email + +// NewNoopSender returns a no-op email sender that will just execute the given sendCallback +// every time it would otherwise send an email. +// +// The 'data' parameter in the callback will be either a ConfirmData or a ResetData struct. +// +// Passing a nil function is also acceptable, in which case the send functions will just return nil. +func NewNoopSender(sendCallback func (toAddress string, data interface{})) Sender { + return &noopSender{ + sendCallback: sendCallback, + } +} + +type noopSender struct { + sendCallback func (toAddress string, data interface{}) +} + +func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error { + if s.sendCallback != nil { + s.sendCallback(toAddress, data) + } + return nil +} + +func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { + if s.sendCallback != nil { + s.sendCallback(toAddress, data) + } + return nil +} + +func (s *noopSender) ExecuteTemplate(templateName string, data interface{}) (string, error) { + return "", nil +} + +func (s *noopSender) AssembleMessage(mailSubject string, mailBody string, mailTo string) []byte { + return []byte{} +} From 8fdb3256e50d956bacdcd54c6c40eb43b3000ebf Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 16:59:57 +0200 Subject: [PATCH 22/44] use noop email sender in tests --- internal/api/client/account/account_test.go | 4 +- .../api/client/fileserver/servefile_test.go | 2 +- .../followrequest/followrequest_test.go | 2 +- internal/api/client/media/mediacreate_test.go | 2 +- internal/api/client/status/status_test.go | 3 +- internal/api/client/user/user_test.go | 5 +- internal/api/s2s/user/inboxpost_test.go | 8 +-- internal/api/s2s/user/repliesget_test.go | 6 +-- internal/api/s2s/user/user_test.go | 2 +- internal/api/s2s/user/userget_test.go | 2 +- internal/api/s2s/webfinger/webfinger_test.go | 2 +- internal/cliactions/testrig/testrig.go | 2 +- internal/email/confirm.go | 8 +-- internal/email/{noop.go => noopsender.go} | 50 +++++++++++++------ internal/email/reset.go | 8 +-- internal/email/{email.go => sender.go} | 23 +-------- .../email/{email_test.go => sender_test.go} | 5 +- internal/email/util.go | 21 +++++--- internal/email/util_test.go | 16 +++--- internal/processing/account/account_test.go | 4 +- internal/processing/processor_test.go | 2 +- internal/processing/user/user_test.go | 2 +- testrig/email.go | 19 +++++-- 23 files changed, 110 insertions(+), 88 deletions(-) rename internal/email/{noop.go => noopsender.go} (56%) rename internal/email/{email.go => sender.go} (64%) rename internal/email/{email_test.go => sender_test.go} (87%) diff --git a/internal/api/client/account/account_test.go b/internal/api/client/account/account_test.go index 9dc27457a6..8e9bc132a1 100644 --- a/internal/api/client/account/account_test.go +++ b/internal/api/client/account/account_test.go @@ -31,6 +31,7 @@ type AccountStandardTestSuite struct { federator federation.Federator processor processing.Processor emailSender email.Sender + sentEmails map[string]string // standard suite models testTokens map[string]*gtsmodel.Token @@ -61,7 +62,8 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.sentEmails = make(map[string]string) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.accountModule = account.New(suite.config, suite.processor).(*account.Module) testrig.StandardDBSetup(suite.db, nil) diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/client/fileserver/servefile_test.go index 0d21d12318..ab5cfadf2a 100644 --- a/internal/api/client/fileserver/servefile_test.go +++ b/internal/api/client/fileserver/servefile_test.go @@ -80,7 +80,7 @@ func (suite *ServeFileTestSuite) SetupSuite() { testrig.InitTestLog() suite.storage = testrig.NewTestStorage() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.tc = testrig.NewTestTypeConverter(suite.db) diff --git a/internal/api/client/followrequest/followrequest_test.go b/internal/api/client/followrequest/followrequest_test.go index 98c69f22bb..94f6cbb0ee 100644 --- a/internal/api/client/followrequest/followrequest_test.go +++ b/internal/api/client/followrequest/followrequest_test.go @@ -75,7 +75,7 @@ func (suite *FollowRequestStandardTestSuite) SetupTest() { suite.db = testrig.NewTestDB() suite.storage = testrig.NewTestStorage() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.followRequestModule = followrequest.New(suite.config, suite.processor).(*followrequest.Module) testrig.StandardDBSetup(suite.db, nil) diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 7e7f80b482..2bc5984995 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -86,7 +86,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() { suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) suite.oauthServer = testrig.NewTestOauthServer(suite.db) suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) // setup module being tested diff --git a/internal/api/client/status/status_test.go b/internal/api/client/status/status_test.go index f473803059..fa8c9823ee 100644 --- a/internal/api/client/status/status_test.go +++ b/internal/api/client/status/status_test.go @@ -69,10 +69,11 @@ func (suite *StatusStandardTestSuite) SetupSuite() { func (suite *StatusStandardTestSuite) SetupTest() { suite.config = testrig.NewTestConfig() suite.db = testrig.NewTestDB() + suite.tc = testrig.NewTestTypeConverter(suite.db) suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.statusModule = status.New(suite.config, suite.processor).(*status.Module) testrig.StandardDBSetup(suite.db, nil) diff --git a/internal/api/client/user/user_test.go b/internal/api/client/user/user_test.go index 9ad1185c3a..fe420b5c99 100644 --- a/internal/api/client/user/user_test.go +++ b/internal/api/client/user/user_test.go @@ -48,6 +48,8 @@ type UserStandardTestSuite struct { testUsers map[string]*gtsmodel.User testAccounts map[string]*gtsmodel.Account + sentEmails map[string]string + userModule *user.Module } @@ -63,7 +65,8 @@ func (suite *UserStandardTestSuite) SetupTest() { testrig.InitTestLog() suite.tc = testrig.NewTestTypeConverter(suite.db) suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.sentEmails = make(map[string]string) + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.userModule = user.New(suite.config, suite.processor).(*user.Module) testrig.StandardDBSetup(suite.db, suite.testAccounts) diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/s2s/user/inboxpost_test.go index 527fcdd502..86b34fe5e1 100644 --- a/internal/api/s2s/user/inboxpost_test.go +++ b/internal/api/s2s/user/inboxpost_test.go @@ -85,7 +85,7 @@ func (suite *InboxPostTestSuite) TestPostBlock() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) @@ -185,7 +185,7 @@ func (suite *InboxPostTestSuite) TestPostUnblock() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) @@ -275,7 +275,7 @@ func (suite *InboxPostTestSuite) TestPostUpdate() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) @@ -394,7 +394,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) err = processor.Start(context.Background()) suite.NoError(err) diff --git a/internal/api/s2s/user/repliesget_test.go b/internal/api/s2s/user/repliesget_test.go index 85ddd01648..32cd0c3669 100644 --- a/internal/api/s2s/user/repliesget_test.go +++ b/internal/api/s2s/user/repliesget_test.go @@ -49,7 +49,7 @@ func (suite *RepliesGetTestSuite) TestGetReplies() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) @@ -109,7 +109,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) @@ -172,7 +172,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) diff --git a/internal/api/s2s/user/user_test.go b/internal/api/s2s/user/user_test.go index e03428188d..fc4232bdef 100644 --- a/internal/api/s2s/user/user_test.go +++ b/internal/api/s2s/user/user_test.go @@ -77,7 +77,7 @@ func (suite *UserStandardTestSuite) SetupTest() { suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.userModule = user.New(suite.config, suite.processor).(*user.Module) suite.securityModule = security.New(suite.config, suite.db).(*security.Module) diff --git a/internal/api/s2s/user/userget_test.go b/internal/api/s2s/user/userget_test.go index 694b119625..68f16fc76d 100644 --- a/internal/api/s2s/user/userget_test.go +++ b/internal/api/s2s/user/userget_test.go @@ -47,7 +47,7 @@ func (suite *UserGetTestSuite) TestGetUser() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - emailSender := testrig.NewEmailSender("../../../../web/template/") + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) diff --git a/internal/api/s2s/webfinger/webfinger_test.go b/internal/api/s2s/webfinger/webfinger_test.go index 3a7697e668..69fd32408c 100644 --- a/internal/api/s2s/webfinger/webfinger_test.go +++ b/internal/api/s2s/webfinger/webfinger_test.go @@ -80,7 +80,7 @@ func (suite *WebfingerStandardTestSuite) SetupTest() { suite.storage = testrig.NewTestStorage() testrig.InitTestLog() suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) suite.webfingerModule = webfinger.New(suite.config, suite.processor).(*webfinger.Module) suite.securityModule = security.New(suite.config, suite.db).(*security.Module) diff --git a/internal/cliactions/testrig/testrig.go b/internal/cliactions/testrig/testrig.go index 8295692bac..c6e9b79aa3 100644 --- a/internal/cliactions/testrig/testrig.go +++ b/internal/cliactions/testrig/testrig.go @@ -62,7 +62,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, _ *config.Config) err }), dbService) federator := testrig.NewTestFederator(dbService, transportController, storageBackend) - emailSender := testrig.NewEmailSender("./web/template/") + emailSender := testrig.NewEmailSender("./web/template/", nil) processor := testrig.NewTestProcessor(dbService, storageBackend, federator, emailSender) if err := processor.Start(ctx); err != nil { diff --git a/internal/email/confirm.go b/internal/email/confirm.go index d6b3a24071..78f661fe14 100644 --- a/internal/email/confirm.go +++ b/internal/email/confirm.go @@ -19,6 +19,7 @@ package email import ( + "bytes" "net/smtp" ) @@ -28,12 +29,13 @@ const ( ) func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { - confirmBody, err := s.ExecuteTemplate(confirmTemplate, data) - if err != nil { + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { return err } + confirmBody := buf.String() - msg := s.AssembleMessage(confirmSubject, confirmBody, toAddress) + msg := assembleMessage(confirmSubject, confirmBody, toAddress, s.from) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) } diff --git a/internal/email/noop.go b/internal/email/noopsender.go similarity index 56% rename from internal/email/noop.go rename to internal/email/noopsender.go index e80c078a48..8e01711ec8 100644 --- a/internal/email/noop.go +++ b/internal/email/noopsender.go @@ -18,40 +18,58 @@ package email +import ( + "bytes" + "html/template" +) + // NewNoopSender returns a no-op email sender that will just execute the given sendCallback -// every time it would otherwise send an email. -// -// The 'data' parameter in the callback will be either a ConfirmData or a ResetData struct. +// every time it would otherwise send an email to the given toAddress with the given message value. // // Passing a nil function is also acceptable, in which case the send functions will just return nil. -func NewNoopSender(sendCallback func (toAddress string, data interface{})) Sender { +func NewNoopSender(templateBaseDir string, sendCallback func(toAddress string, message string)) (Sender, error) { + t, err := loadTemplates(templateBaseDir) + if err != nil { + return nil, err + } + return &noopSender{ sendCallback: sendCallback, - } + template: t, + }, nil } type noopSender struct { - sendCallback func (toAddress string, data interface{}) + sendCallback func(toAddress string, message string) + template *template.Template } func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error { + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { + return err + } + confirmBody := buf.String() + + msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") + if s.sendCallback != nil { - s.sendCallback(toAddress, data) + s.sendCallback(toAddress, string(msg)) } return nil } func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { - if s.sendCallback != nil { - s.sendCallback(toAddress, data) + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { + return err } - return nil -} + resetBody := buf.String() -func (s *noopSender) ExecuteTemplate(templateName string, data interface{}) (string, error) { - return "", nil -} + msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") -func (s *noopSender) AssembleMessage(mailSubject string, mailBody string, mailTo string) []byte { - return []byte{} + if s.sendCallback != nil { + s.sendCallback(toAddress, string(msg)) + } + return nil } diff --git a/internal/email/reset.go b/internal/email/reset.go index a0cd377f65..786777f1b1 100644 --- a/internal/email/reset.go +++ b/internal/email/reset.go @@ -19,6 +19,7 @@ package email import ( + "bytes" "net/smtp" ) @@ -28,12 +29,13 @@ const ( ) func (s *sender) SendResetEmail(toAddress string, data ResetData) error { - resetBody, err := s.ExecuteTemplate(resetTemplate, data) - if err != nil { + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { return err } + resetBody := buf.String() - msg := s.AssembleMessage(resetSubject, resetBody, toAddress) + msg := assembleMessage(resetSubject, resetBody, toAddress, s.from) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) } diff --git a/internal/email/email.go b/internal/email/sender.go similarity index 64% rename from internal/email/email.go rename to internal/email/sender.go index 69015e252b..a6a9abe555 100644 --- a/internal/email/email.go +++ b/internal/email/sender.go @@ -22,8 +22,6 @@ import ( "fmt" "html/template" "net/smtp" - "os" - "path/filepath" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -35,18 +33,11 @@ type Sender interface { // SendResetEmail sends a 'reset your password' style email to the given toAddress, with the given data. SendResetEmail(toAddress string, data ResetData) error - - // ExecuteTemplate returns templated HTML using the given templateName and data. Mostly you won't need to call this, - // and can just call one of the 'Send' functions instead (which calls this under the hood anyway). - ExecuteTemplate(templateName string, data interface{}) (string, error) - // AssembleMessage concacenates the mailSubject, the mime header, mailTo, mailFrom and the mailBody in - // an appropriate format for sending via net/smtp. Mostly you won't need to call this, but it's provided just in case. - AssembleMessage(mailSubject string, mailBody string, mailTo string) []byte } // NewSender returns a new email Sender interface with the given configuration, or an error if something goes wrong. func NewSender(cfg *config.Config) (Sender, error) { - t, err := loadTemplates(cfg) + t, err := loadTemplates(cfg.TemplateConfig.BaseDir) if err != nil { return nil, err } @@ -67,15 +58,3 @@ type sender struct { auth smtp.Auth template *template.Template } - -// loadTemplates loads html templates for use in emails -func loadTemplates(cfg *config.Config) (*template.Template, error) { - cwd, err := os.Getwd() - if err != nil { - return nil, fmt.Errorf("error getting current working directory: %s", err) - } - - // look for all templates that start with 'email_' - tmPath := filepath.Join(cwd, fmt.Sprintf("%semail_*", cfg.TemplateConfig.BaseDir)) - return template.ParseGlob(tmPath) -} diff --git a/internal/email/email_test.go b/internal/email/sender_test.go similarity index 87% rename from internal/email/email_test.go rename to internal/email/sender_test.go index 54d7e71442..4e2619546d 100644 --- a/internal/email/email_test.go +++ b/internal/email/sender_test.go @@ -28,9 +28,12 @@ type EmailTestSuite struct { suite.Suite sender email.Sender + + sentEmails map[string]string } func (suite *EmailTestSuite) SetupTest() { testrig.InitTestLog() - suite.sender = testrig.NewEmailSender("../../web/template/") + suite.sentEmails = make(map[string]string) + suite.sender = testrig.NewEmailSender("../../web/template/", suite.sentEmails) } diff --git a/internal/email/util.go b/internal/email/util.go index 87933b130d..1d3db58026 100644 --- a/internal/email/util.go +++ b/internal/email/util.go @@ -19,8 +19,10 @@ package email import ( - "bytes" "fmt" + "html/template" + "os" + "path/filepath" ) const ( @@ -28,16 +30,19 @@ const ( Content-Type: text/html;` ) -func (s *sender) ExecuteTemplate(templateName string, data interface{}) (string, error) { - buf := &bytes.Buffer{} - if err := s.template.ExecuteTemplate(buf, templateName, data); err != nil { - return "", err +func loadTemplates(templateBaseDir string) (*template.Template, error) { + cwd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("error getting current working directory: %s", err) } - return buf.String(), nil + + // look for all templates that start with 'email_' + tmPath := filepath.Join(cwd, fmt.Sprintf("%semail_*", templateBaseDir)) + return template.ParseGlob(tmPath) } -func (s *sender) AssembleMessage(mailSubject string, mailBody string, mailTo string) []byte { - from := fmt.Sprintf("From: GoToSocial <%s>", s.from) +func assembleMessage(mailSubject string, mailBody string, mailTo string, mailFrom string) []byte { + from := fmt.Sprintf("From: GoToSocial <%s>", mailFrom) to := fmt.Sprintf("To: %s", mailTo) msg := []byte( diff --git a/internal/email/util_test.go b/internal/email/util_test.go index ce06f09d05..201bcd92da 100644 --- a/internal/email/util_test.go +++ b/internal/email/util_test.go @@ -36,12 +36,10 @@ func (suite *UtilTestSuite) TestTemplateConfirm() { InstanceName: "Test Instance", ConfirmLink: "https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa", } - mailBody, err := suite.sender.ExecuteTemplate("email_confirm.tmpl", confirmData) - suite.NoError(err) - suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) - message := suite.sender.AssembleMessage("Subject: something", mailBody, "user@example.org") - suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) + suite.sender.SendConfirmEmail("user@example.org", confirmData) + suite.Len(suite.sentEmails, 1) + suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", suite.sentEmails["user@example.org"]) } func (suite *UtilTestSuite) TestTemplateReset() { @@ -51,12 +49,10 @@ func (suite *UtilTestSuite) TestTemplateReset() { InstanceName: "Test Instance", ResetLink: "https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa", } - mailBody, err := suite.sender.ExecuteTemplate("email_reset.tmpl", resetData) - suite.NoError(err) - suite.Equal("\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n", mailBody) - message := suite.sender.AssembleMessage("Subject: something", mailBody, "user@example.org") - suite.Equal("Subject: something\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", string(message)) + suite.sender.SendResetEmail("user@example.org", resetData) + suite.Len(suite.sentEmails, 1) + suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", suite.sentEmails["user@example.org"]) } func TestUtilTestSuite(t *testing.T) { diff --git a/internal/processing/account/account_test.go b/internal/processing/account/account_test.go index e8ef9ca684..9bc65d3765 100644 --- a/internal/processing/account/account_test.go +++ b/internal/processing/account/account_test.go @@ -50,6 +50,7 @@ type AccountStandardTestSuite struct { transportController transport.Controller federator federation.Federator emailSender email.Sender + sentEmails map[string]string // standard suite models testTokens map[string]*gtsmodel.Token @@ -86,7 +87,8 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.httpClient = testrig.NewMockHTTPClient(nil) suite.transportController = testrig.NewTestTransportController(suite.httpClient, suite.db) suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage) - suite.emailSender = testrig.NewEmailSender("../../../web/template/") + suite.sentEmails = make(map[string]string) + suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails) suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaHandler, suite.oauthServer, suite.fromClientAPIChan, suite.federator, suite.config) testrig.StandardDBSetup(suite.db, nil) testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") diff --git a/internal/processing/processor_test.go b/internal/processing/processor_test.go index 91e9e47c73..868c96bfe5 100644 --- a/internal/processing/processor_test.go +++ b/internal/processing/processor_test.go @@ -221,7 +221,7 @@ func (suite *ProcessingStandardTestSuite) SetupTest() { suite.oauthServer = testrig.NewTestOauthServer(suite.db) suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) suite.timelineManager = testrig.NewTestTimelineManager(suite.db) - suite.emailSender = testrig.NewEmailSender("../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../web/template/", nil) suite.processor = processing.NewProcessor(suite.config, suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaHandler, suite.storage, suite.timelineManager, suite.db, suite.emailSender) diff --git a/internal/processing/user/user_test.go b/internal/processing/user/user_test.go index 5d861ec1fc..b1fd83285a 100644 --- a/internal/processing/user/user_test.go +++ b/internal/processing/user/user_test.go @@ -43,7 +43,7 @@ func (suite *UserStandardTestSuite) SetupTest() { testrig.InitTestLog() suite.config = testrig.NewTestConfig() suite.db = testrig.NewTestDB() - suite.emailSender = testrig.NewEmailSender("../../../web/template/") + suite.emailSender = testrig.NewEmailSender("../../../web/template/", nil) suite.testUsers = testrig.NewTestUsers() suite.user = user.New(suite.db, suite.emailSender, suite.config) diff --git a/testrig/email.go b/testrig/email.go index 9ce55e026c..60ee96ac2c 100644 --- a/testrig/email.go +++ b/testrig/email.go @@ -20,12 +20,21 @@ package testrig import "github.com/superseriousbusiness/gotosocial/internal/email" -// NewEmailSender returns an email sender with the default test config. -func NewEmailSender(templateBaseDir string) email.Sender { - cfg := NewTestConfig() - cfg.TemplateConfig.BaseDir = templateBaseDir +// NewEmailSender returns a noop email sender that won't make any remote calls. +// +// If sentEmails is not nil, the noop callback function will place sent emails in +// the map, with email address of the recipient as the key, and the value as the +// parsed email message as it would have been sent. +func NewEmailSender(templateBaseDir string, sentEmails map[string]string) email.Sender { + var sendCallback func(toAddress string, message string) - s, err := email.NewSender(cfg) + if sentEmails != nil { + sendCallback = func(toAddress string, message string) { + sentEmails[toAddress] = message + } + } + + s, err := email.NewNoopSender(templateBaseDir, sendCallback) if err != nil { panic(err) } From b524b12100a385e8c3464bee31aeaa43ac05c97c Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 17:06:47 +0200 Subject: [PATCH 23/44] only assemble message if callback is not nil --- internal/email/noopsender.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/internal/email/noopsender.go b/internal/email/noopsender.go index 8e01711ec8..a572dcc73f 100644 --- a/internal/email/noopsender.go +++ b/internal/email/noopsender.go @@ -45,30 +45,28 @@ type noopSender struct { } func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error { - buf := &bytes.Buffer{} - if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { - return err - } - confirmBody := buf.String() - - msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") - if s.sendCallback != nil { + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { + return err + } + confirmBody := buf.String() + + msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") s.sendCallback(toAddress, string(msg)) } return nil } func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { - buf := &bytes.Buffer{} - if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { - return err - } - resetBody := buf.String() - - msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") - if s.sendCallback != nil { + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { + return err + } + resetBody := buf.String() + + msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") s.sendCallback(toAddress, string(msg)) } return nil From 42881aa0f1610ae4b993d5c9caaba7ed02314647 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 17:07:07 +0200 Subject: [PATCH 24/44] use noop email sender if no smtp host is defined --- internal/cliactions/server/server.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/internal/cliactions/server/server.go b/internal/cliactions/server/server.go index 92700d4527..3837952c75 100644 --- a/internal/cliactions/server/server.go +++ b/internal/cliactions/server/server.go @@ -77,26 +77,39 @@ var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config) err return fmt.Errorf("error creating router: %s", err) } + // build converters and util + typeConverter := typeutils.NewConverter(c, dbService) + timelineManager := timelineprocessing.NewManager(dbService, typeConverter, c) + // Open the storage backend storage, err := kv.OpenFile(c.StorageConfig.BasePath, nil) if err != nil { return fmt.Errorf("error creating storage backend: %s", err) } - // build converters and util - typeConverter := typeutils.NewConverter(c, dbService) - timelineManager := timelineprocessing.NewManager(dbService, typeConverter, c) - // build backend handlers mediaHandler := media.New(c, dbService, storage) oauthServer := oauth.New(ctx, dbService) transportController := transport.NewController(c, dbService, &federation.Clock{}, http.DefaultClient) federator := federation.NewFederator(dbService, federatingDB, transportController, c, typeConverter, mediaHandler) - emailSender, err := email.NewSender(c) - if err != nil { - return fmt.Errorf("error creating email sender: %s", err) + + // decide whether to create a noop email sender (won't send emails) or a real one + var emailSender email.Sender + if c.SMTPConfig.Host != "" { + // host is defined so create a proper sender + emailSender, err = email.NewSender(c) + if err != nil { + return fmt.Errorf("error creating email sender: %s", err) + } + } else { + // no host is defined so create a noop sender + emailSender, err = email.NewNoopSender(c.TemplateConfig.BaseDir, nil) + if err != nil { + return fmt.Errorf("error creating noop email sender: %s", err) + } } + // create and start the message processor using the other services we've created so far processor := processing.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storage, timelineManager, dbService, emailSender) if err := processor.Start(ctx); err != nil { return fmt.Errorf("error starting processor: %s", err) From d3a80b69f0fb90efe9cb8690a48bc5364e12f4f6 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 17:45:07 +0200 Subject: [PATCH 25/44] minify email html before sending --- internal/email/confirm.go | 8 +++++++- internal/email/{sender_test.go => email_test.go} | 0 internal/email/noopsender.go | 14 ++++++++++++-- internal/email/reset.go | 8 +++++++- internal/email/util_test.go | 4 ++-- internal/text/common.go | 2 +- internal/text/minify.go | 4 ++-- 7 files changed, 31 insertions(+), 9 deletions(-) rename internal/email/{sender_test.go => email_test.go} (100%) diff --git a/internal/email/confirm.go b/internal/email/confirm.go index 78f661fe14..363cdb8b03 100644 --- a/internal/email/confirm.go +++ b/internal/email/confirm.go @@ -21,6 +21,8 @@ package email import ( "bytes" "net/smtp" + + "github.com/superseriousbusiness/gotosocial/internal/text" ) const ( @@ -33,7 +35,11 @@ func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { return err } - confirmBody := buf.String() + + confirmBody, err := text.MinifyHTML(buf.String()) + if err != nil { + return err + } msg := assembleMessage(confirmSubject, confirmBody, toAddress, s.from) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) diff --git a/internal/email/sender_test.go b/internal/email/email_test.go similarity index 100% rename from internal/email/sender_test.go rename to internal/email/email_test.go diff --git a/internal/email/noopsender.go b/internal/email/noopsender.go index a572dcc73f..d8a7b4db7f 100644 --- a/internal/email/noopsender.go +++ b/internal/email/noopsender.go @@ -21,6 +21,8 @@ package email import ( "bytes" "html/template" + + "github.com/superseriousbusiness/gotosocial/internal/text" ) // NewNoopSender returns a no-op email sender that will just execute the given sendCallback @@ -50,7 +52,11 @@ func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { return err } - confirmBody := buf.String() + + confirmBody, err := text.MinifyHTML(buf.String()) + if err != nil { + return err + } msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") s.sendCallback(toAddress, string(msg)) @@ -64,7 +70,11 @@ func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { return err } - resetBody := buf.String() + + resetBody, err := text.MinifyHTML(buf.String()) + if err != nil { + return err + } msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") s.sendCallback(toAddress, string(msg)) diff --git a/internal/email/reset.go b/internal/email/reset.go index 786777f1b1..6fb72084f1 100644 --- a/internal/email/reset.go +++ b/internal/email/reset.go @@ -21,6 +21,8 @@ package email import ( "bytes" "net/smtp" + + "github.com/superseriousbusiness/gotosocial/internal/text" ) const ( @@ -33,7 +35,11 @@ func (s *sender) SendResetEmail(toAddress string, data ResetData) error { if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { return err } - resetBody := buf.String() + + resetBody, err := text.MinifyHTML(buf.String()) + if err != nil { + return err + } msg := assembleMessage(resetSubject, resetBody, toAddress, s.from) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) diff --git a/internal/email/util_test.go b/internal/email/util_test.go index 201bcd92da..8a16d093d8 100644 --- a/internal/email/util_test.go +++ b/internal/email/util_test.go @@ -39,7 +39,7 @@ func (suite *UtilTestSuite) TestTemplateConfirm() { suite.sender.SendConfirmEmail("user@example.org", confirmData) suite.Len(suite.sentEmails, 1) - suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", suite.sentEmails["user@example.org"]) + suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n

Hello test!

You are receiving this mail because you've requested an account on Test Instance.

We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:

https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa

If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.

\r\n", suite.sentEmails["user@example.org"]) } func (suite *UtilTestSuite) TestTemplateReset() { @@ -52,7 +52,7 @@ func (suite *UtilTestSuite) TestTemplateReset() { suite.sender.SendResetEmail("user@example.org", resetData) suite.Len(suite.sentEmails, 1) - suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", suite.sentEmails["user@example.org"]) + suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n

Hello test!

You are receiving this mail because a password reset has been requested for your account on Test Instance.

To reset your password, click here or paste the following in your browser's address bar:

https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa

If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.

\r\n", suite.sentEmails["user@example.org"]) } func TestUtilTestSuite(t *testing.T) { diff --git a/internal/text/common.go b/internal/text/common.go index 1c7d52905b..0173b1d018 100644 --- a/internal/text/common.go +++ b/internal/text/common.go @@ -52,7 +52,7 @@ func postformat(in string) string { s = html.UnescapeString(s) // 3. minify html to remove any trailing newlines, spaces, unnecessary elements, etc etc - mini, err := minifyHTML(s) + mini, err := MinifyHTML(s) if err != nil { // if the minify failed, just return what we have return s diff --git a/internal/text/minify.go b/internal/text/minify.go index c6d7b9bc19..1c7c8ce244 100644 --- a/internal/text/minify.go +++ b/internal/text/minify.go @@ -25,8 +25,8 @@ import ( var m *minify.M -// minifyHTML runs html through a minifier, reducing it in size. -func minifyHTML(in string) (string, error) { +// MinifyHTML runs html through a minifier, reducing it in size. +func MinifyHTML(in string) (string, error) { if m == nil { m = minify.New() m.Add("text/html", &html.Minifier{ From 4bb1e901c664c79b8e583bf4f381b242af8d44ca Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 17:45:19 +0200 Subject: [PATCH 26/44] fix wrong email address --- internal/processing/user/emailconfirm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go index 2a68ddd7a5..0eb5357306 100644 --- a/internal/processing/user/emailconfirm.go +++ b/internal/processing/user/emailconfirm.go @@ -31,7 +31,7 @@ import ( ) func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error { - if user.UnconfirmedEmail == user.Email { + if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email { // user has already confirmed this email address, so there's nothing to do return nil } @@ -60,8 +60,8 @@ func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, u InstanceName: instance.Title, ConfirmLink: confirmationLink, } - if err := p.emailSender.SendConfirmEmail(user.Email, confirmData); err != nil { - return fmt.Errorf("SendConfirmEmail: error sending to email address %s belonging to user %s: %s", user.Email, username, err) + if err := p.emailSender.SendConfirmEmail(user.UnconfirmedEmail, confirmData); err != nil { + return fmt.Errorf("SendConfirmEmail: error sending to email address %s belonging to user %s: %s", user.UnconfirmedEmail, username, err) } // email sent, now we need to update the user entry with the token we just sent them From 11a53dab926cea1341f3868fcbeaed6fd7ea87a1 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 17:45:29 +0200 Subject: [PATCH 27/44] email confirm test --- internal/processing/user/emailconfirm_test.go | 66 +++++++++++++++++++ internal/processing/user/user_test.go | 5 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 internal/processing/user/emailconfirm_test.go diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/emailconfirm_test.go new file mode 100644 index 0000000000..e81e8d09b9 --- /dev/null +++ b/internal/processing/user/emailconfirm_test.go @@ -0,0 +1,66 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package user_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/suite" +) + +type EmailConfirmTestSuite struct { + UserStandardTestSuite +} + +func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() { + user := suite.testUsers["local_account_1"] + + // set a bunch of stuff on the user as though zork hasn't been confirmed (perish the thought) + user.UnconfirmedEmail = "some.email@example.org" + user.Email = "" + user.ConfirmedAt = time.Time{} + user.ConfirmationSentAt = time.Time{} + user.ConfirmationToken = "" + + err := suite.user.SendConfirmEmail(context.Background(), user, "the_mighty_zork") + suite.NoError(err) + + // zork should have an email now + suite.Len(suite.sentEmails, 1) + email, ok := suite.sentEmails["some.email@example.org"] + suite.True(ok) + + // a token should be set on zork + token := user.ConfirmationToken + suite.NotEmpty(token) + + // email should contain the token + emailShould := fmt.Sprintf("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: some.email@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n

Hello the_mighty_zork!

You are receiving this mail because you've requested an account on localhost:8080.

We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:

http://localhost:8080/confirm_email?token=%s

If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of localhost:8080.

\r\n", token, token) + suite.Equal(emailShould, email) + + // confirmationSentAt should be recent + suite.WithinDuration(time.Now(), user.ConfirmationSentAt, 1 * time.Minute) +} + +func TestEmailConfirmTestSuite(t *testing.T) { + suite.Run(t, &EmailConfirmTestSuite{}) +} diff --git a/internal/processing/user/user_test.go b/internal/processing/user/user_test.go index b1fd83285a..5c3cd7597d 100644 --- a/internal/processing/user/user_test.go +++ b/internal/processing/user/user_test.go @@ -36,6 +36,8 @@ type UserStandardTestSuite struct { testUsers map[string]*gtsmodel.User + sentEmails map[string]string + user user.Processor } @@ -43,7 +45,8 @@ func (suite *UserStandardTestSuite) SetupTest() { testrig.InitTestLog() suite.config = testrig.NewTestConfig() suite.db = testrig.NewTestDB() - suite.emailSender = testrig.NewEmailSender("../../../web/template/", nil) + suite.sentEmails = make(map[string]string) + suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails) suite.testUsers = testrig.NewTestUsers() suite.user = user.New(suite.db, suite.emailSender, suite.config) From 04059f8ffffb973636385571323d64f186950ca7 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 17 Oct 2021 17:46:19 +0200 Subject: [PATCH 28/44] fmt --- internal/processing/user/emailconfirm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/emailconfirm_test.go index e81e8d09b9..0ad4f7f700 100644 --- a/internal/processing/user/emailconfirm_test.go +++ b/internal/processing/user/emailconfirm_test.go @@ -58,7 +58,7 @@ func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() { suite.Equal(emailShould, email) // confirmationSentAt should be recent - suite.WithinDuration(time.Now(), user.ConfirmationSentAt, 1 * time.Minute) + suite.WithinDuration(time.Now(), user.ConfirmationSentAt, 1*time.Minute) } func TestEmailConfirmTestSuite(t *testing.T) { From d1c271fc8eb99c221bdcd428e4d9195dfe5a1ba8 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 10:41:33 +0200 Subject: [PATCH 29/44] serve web hndler --- internal/web/base.go | 9 +++++++++ internal/web/confirmemail.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 internal/web/confirmemail.go diff --git a/internal/web/base.go b/internal/web/base.go index 0eda1185a8..d8eec3b905 100644 --- a/internal/web/base.go +++ b/internal/web/base.go @@ -30,6 +30,12 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/router" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +const ( + confirmEmailPath = "/" + util.ConfirmEmailPath + tokenParam = "token" ) // Module implements the api.ClientModule interface for web pages. @@ -100,6 +106,9 @@ func (m *Module) Route(s router.Router) error { // serve statuses s.AttachHandler(http.MethodGet, "/:user/statuses/:id", m.threadTemplateHandler) + // serve email confirmation page at /confirm_email?token=whatever + s.AttachHandler(http.MethodGet, confirmEmailPath, m.ConfirmEmailGETHandler) + // 404 handler s.AttachNoRouteHandler(m.NotFoundHandler) diff --git a/internal/web/confirmemail.go b/internal/web/confirmemail.go new file mode 100644 index 0000000000..5dd8986049 --- /dev/null +++ b/internal/web/confirmemail.go @@ -0,0 +1,33 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package web + +import "github.com/gin-gonic/gin" + + +func (m *Module) ConfirmEmailGETHandler(c *gin.Context) { + // if there's no token in the query, just serve the 404 web handler + token := c.Query(tokenParam) + if token == "" { + m.NotFoundHandler(c) + return + } + + +} From 652b41d1ce4cec0bae8b095c383399de71f25fc8 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 14:44:12 +0200 Subject: [PATCH 30/44] add email confirm handler --- internal/processing/processor.go | 3 ++ internal/processing/user.go | 5 +++ internal/processing/user/emailconfirm.go | 45 ++++++++++++++++++++++++ internal/processing/user/user.go | 3 ++ internal/web/confirmemail.go | 26 +++++++++++++- web/template/confirmed.tmpl | 9 +++++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 web/template/confirmed.tmpl diff --git a/internal/processing/processor.go b/internal/processing/processor.go index abaafecdae..a36d2ee144 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -179,6 +179,9 @@ type Processor interface { // UserChangePassword changes the password for the given user, with the given form. UserChangePassword(ctx context.Context, authed *oauth.Auth, form *apimodel.PasswordChangeRequest) gtserror.WithCode + // UserConfirmEmail confirms an email address using the given token. + // The user belonging to the confirmed email is also returned. + UserConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) /* FEDERATION API-FACING PROCESSING FUNCTIONS diff --git a/internal/processing/user.go b/internal/processing/user.go index a5fca53ddf..612788ed8f 100644 --- a/internal/processing/user.go +++ b/internal/processing/user.go @@ -23,9 +23,14 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) func (p *processor) UserChangePassword(ctx context.Context, authed *oauth.Auth, form *apimodel.PasswordChangeRequest) gtserror.WithCode { return p.userProcessor.ChangePassword(ctx, authed.User, form.OldPassword, form.NewPassword) } + +func (p *processor) UserConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) { + return p.userProcessor.ConfirmEmail(ctx, token) +} diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go index 0eb5357306..7049e0e8ff 100644 --- a/internal/processing/user/emailconfirm.go +++ b/internal/processing/user/emailconfirm.go @@ -20,12 +20,14 @@ package user import ( "context" + "errors" "fmt" "time" "github.com/google/uuid" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/email" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/util" ) @@ -76,3 +78,46 @@ func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, u return nil } + +func (p *processor) ConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) { + if token == "" { + return nil, gtserror.NewErrorNotFound(errors.New("no token provided")) + } + + user := >smodel.User{} + if err := p.db.GetWhere(ctx, []db.Where{{Key: "confirmation_token", Value: token}}, user); err != nil { + if err == db.ErrNoEntries { + return nil, gtserror.NewErrorNotFound(err) + } + return nil, gtserror.NewErrorInternalError(err) + } + + if user.Account == nil { + a, err := p.db.GetAccountByID(ctx, user.AccountID) + if err != nil { + return nil, gtserror.NewErrorNotFound(err) + } + user.Account = a + } + + if !user.Account.SuspendedAt.IsZero() { + return nil, gtserror.NewErrorForbidden(fmt.Errorf("ConfirmEmail: account %s is suspended", user.AccountID)) + } + + if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email { + // no pending email confirmations so just return OK + return user, nil + } + + // mark the user's email address as confirmed + remove the unconfirmed address and the token + user.Email = user.UnconfirmedEmail + user.UnconfirmedEmail = "" + user.ConfirmedAt = time.Now() + user.ConfirmationToken = "" + + if err := p.db.UpdateByPrimaryKey(ctx, user); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return user, nil +} diff --git a/internal/processing/user/user.go b/internal/processing/user/user.go index 3a94b219fb..73cdb49013 100644 --- a/internal/processing/user/user.go +++ b/internal/processing/user/user.go @@ -33,7 +33,10 @@ type Processor interface { // ChangePassword changes the specified user's password from old => new, // or returns an error if the new password is too weak, or the old password is incorrect. ChangePassword(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode + // SendConfirmEmail sends a 'confirm-your-email-address' type email to a user. SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error + // ConfirmEmail confirms an email address using the given token. + ConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) } type processor struct { diff --git a/internal/web/confirmemail.go b/internal/web/confirmemail.go index 5dd8986049..566e303442 100644 --- a/internal/web/confirmemail.go +++ b/internal/web/confirmemail.go @@ -18,8 +18,12 @@ package web -import "github.com/gin-gonic/gin" +import ( + "net/http" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) func (m *Module) ConfirmEmailGETHandler(c *gin.Context) { // if there's no token in the query, just serve the 404 web handler @@ -29,5 +33,25 @@ func (m *Module) ConfirmEmailGETHandler(c *gin.Context) { return } + ctx := c.Request.Context() + user, errWithCode := m.processor.UserConfirmEmail(ctx, token) + if errWithCode != nil { + logrus.Debugf("error confirming email: %s", errWithCode.Error()) + // if something goes wrong, just log it and direct to the 404 handler to not give anything away + m.NotFoundHandler(c) + return + } + + instance, err := m.processor.InstanceGet(ctx, m.config.Host) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.HTML(http.StatusOK, "confirmed.tmpl", gin.H{ + "instance": instance, + "email": user.Email, + "username": user.Account.Username, + }) } diff --git a/web/template/confirmed.tmpl b/web/template/confirmed.tmpl new file mode 100644 index 0000000000..920f7d3b23 --- /dev/null +++ b/web/template/confirmed.tmpl @@ -0,0 +1,9 @@ +{{ template "header.tmpl" .}} +
+
+

Email Address Confirmed

+

Thanks {{.username}}! Your email address {{.email}} has been confirmed.

+

+
+ +{{ template "footer.tmpl" .}} \ No newline at end of file From 88e27da16464ee42e9cec88a7b0502c7ab8f415c Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 14:44:31 +0200 Subject: [PATCH 31/44] init test log properly on testrig --- internal/cliactions/testrig/testrig.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/cliactions/testrig/testrig.go b/internal/cliactions/testrig/testrig.go index c6e9b79aa3..2cdbfaf52d 100644 --- a/internal/cliactions/testrig/testrig.go +++ b/internal/cliactions/testrig/testrig.go @@ -44,6 +44,8 @@ import ( // Start creates and starts a gotosocial testrig server var Start cliactions.GTSAction = func(ctx context.Context, _ *config.Config) error { + testrig.InitTestLog() + c := testrig.NewTestConfig() dbService := testrig.NewTestDB() testrig.StandardDBSetup(dbService, nil) From 6ee46db3de5f3ba314ec69a97088e55395899567 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 14:44:52 +0200 Subject: [PATCH 32/44] log emails that *would* have been sent --- internal/email/noopsender.go | 48 +++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/internal/email/noopsender.go b/internal/email/noopsender.go index d8a7b4db7f..5efb963a49 100644 --- a/internal/email/noopsender.go +++ b/internal/email/noopsender.go @@ -22,6 +22,7 @@ import ( "bytes" "html/template" + "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/text" ) @@ -47,37 +48,44 @@ type noopSender struct { } func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error { - if s.sendCallback != nil { - buf := &bytes.Buffer{} - if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { - return err - } + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { + return err + } + + confirmBody, err := text.MinifyHTML(buf.String()) + if err != nil { + return err + } + + msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") - confirmBody, err := text.MinifyHTML(buf.String()) - if err != nil { - return err - } + logrus.Tracef("NOT SENDING confirmation email to %s with contents: %s", toAddress, msg) - msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") + if s.sendCallback != nil { s.sendCallback(toAddress, string(msg)) } return nil } func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { - if s.sendCallback != nil { - buf := &bytes.Buffer{} - if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { - return err - } + buf := &bytes.Buffer{} + if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { + return err + } + + resetBody, err := text.MinifyHTML(buf.String()) + if err != nil { + return err + } - resetBody, err := text.MinifyHTML(buf.String()) - if err != nil { - return err - } + msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") - msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") + logrus.Tracef("NOT SENDING reset email to %s with contents: %s", toAddress, msg) + + if s.sendCallback != nil { s.sendCallback(toAddress, string(msg)) } + return nil } From 92ac358193ff637d258ae9011d5d2f068b5f0e28 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 14:45:20 +0200 Subject: [PATCH 33/44] go fmt ./... --- internal/processing/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/processing/user.go b/internal/processing/user.go index 612788ed8f..1507267e80 100644 --- a/internal/processing/user.go +++ b/internal/processing/user.go @@ -32,5 +32,5 @@ func (p *processor) UserChangePassword(ctx context.Context, authed *oauth.Auth, } func (p *processor) UserConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) { - return p.userProcessor.ConfirmEmail(ctx, token) + return p.userProcessor.ConfirmEmail(ctx, token) } From 91819eada28a6b82eb9c4b9ecfe7d00ecfa0a20b Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 14:53:20 +0200 Subject: [PATCH 34/44] unexport confirm email handler --- internal/web/base.go | 2 +- internal/web/confirmemail.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/web/base.go b/internal/web/base.go index d8eec3b905..5d19a3f709 100644 --- a/internal/web/base.go +++ b/internal/web/base.go @@ -107,7 +107,7 @@ func (m *Module) Route(s router.Router) error { s.AttachHandler(http.MethodGet, "/:user/statuses/:id", m.threadTemplateHandler) // serve email confirmation page at /confirm_email?token=whatever - s.AttachHandler(http.MethodGet, confirmEmailPath, m.ConfirmEmailGETHandler) + s.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler) // 404 handler s.AttachNoRouteHandler(m.NotFoundHandler) diff --git a/internal/web/confirmemail.go b/internal/web/confirmemail.go index 566e303442..97ed597d38 100644 --- a/internal/web/confirmemail.go +++ b/internal/web/confirmemail.go @@ -25,7 +25,7 @@ import ( "github.com/sirupsen/logrus" ) -func (m *Module) ConfirmEmailGETHandler(c *gin.Context) { +func (m *Module) confirmEmailGETHandler(c *gin.Context) { // if there's no token in the query, just serve the 404 web handler token := c.Query(tokenParam) if token == "" { From 7caf98afd714268e54a10974232456e54d772532 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 15:54:16 +0200 Subject: [PATCH 35/44] updatedAt --- internal/processing/user/emailconfirm.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go index 7049e0e8ff..e4ebb0b331 100644 --- a/internal/processing/user/emailconfirm.go +++ b/internal/processing/user/emailconfirm.go @@ -114,6 +114,7 @@ func (p *processor) ConfirmEmail(ctx context.Context, token string) (*gtsmodel.U user.UnconfirmedEmail = "" user.ConfirmedAt = time.Now() user.ConfirmationToken = "" + user.UpdatedAt = time.Now() if err := p.db.UpdateByPrimaryKey(ctx, user); err != nil { return nil, gtserror.NewErrorInternalError(err) From 141f9852e37eb3e7bfbf7c7c0c79876eb5738fc5 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 15:54:27 +0200 Subject: [PATCH 36/44] test confirm email function --- internal/processing/user/emailconfirm_test.go | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/emailconfirm_test.go index 0ad4f7f700..cb9b6f86a0 100644 --- a/internal/processing/user/emailconfirm_test.go +++ b/internal/processing/user/emailconfirm_test.go @@ -61,6 +61,33 @@ func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() { suite.WithinDuration(time.Now(), user.ConfirmationSentAt, 1*time.Minute) } +func (suite *EmailConfirmTestSuite) TestConfirmEmail() { + ctx := context.Background() + + user := suite.testUsers["local_account_1"] + + // set a bunch of stuff on the user as though zork hasn't been confirmed yet, but has had an email sent 5 minutes ago + user.UnconfirmedEmail = "some.email@example.org" + user.Email = "" + user.ConfirmedAt = time.Time{} + user.ConfirmationSentAt = time.Now().Add(-5 * time.Minute) + user.ConfirmationToken = "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6" + + err := suite.db.UpdateByPrimaryKey(ctx, user) + suite.NoError(err) + + // confirm with the token set above + updatedUser, errWithCode := suite.user.ConfirmEmail(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") + suite.NoError(errWithCode) + + // email should now be confirmed and token cleared + suite.Equal("some.email@example.org", updatedUser.Email) + suite.Empty(updatedUser.UnconfirmedEmail) + suite.Empty(updatedUser.ConfirmationToken) + suite.WithinDuration(updatedUser.ConfirmedAt, time.Now(), 1*time.Minute) + suite.WithinDuration(updatedUser.UpdatedAt, time.Now(), 1*time.Minute) +} + func TestEmailConfirmTestSuite(t *testing.T) { suite.Run(t, &EmailConfirmTestSuite{}) } From 3382f4deab7067465bc6090c087da9003aa591ab Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 16:02:49 +0200 Subject: [PATCH 37/44] don't allow tokens older than 7 days --- internal/processing/user/emailconfirm.go | 8 +++++++ internal/processing/user/emailconfirm_test.go | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go index e4ebb0b331..f3b7ab0897 100644 --- a/internal/processing/user/emailconfirm.go +++ b/internal/processing/user/emailconfirm.go @@ -32,6 +32,10 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/util" ) +var ( + oneWeek = 168 * time.Hour +) + func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error { if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email { // user has already confirmed this email address, so there's nothing to do @@ -109,6 +113,10 @@ func (p *processor) ConfirmEmail(ctx context.Context, token string) (*gtsmodel.U return user, nil } + if user.ConfirmationSentAt.Before(time.Now().Add(-oneWeek)) { + return nil, gtserror.NewErrorForbidden(errors.New("confirmation token more than a week old, please request a new one")) + } + // mark the user's email address as confirmed + remove the unconfirmed address and the token user.Email = user.UnconfirmedEmail user.UnconfirmedEmail = "" diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/emailconfirm_test.go index cb9b6f86a0..f269f4db41 100644 --- a/internal/processing/user/emailconfirm_test.go +++ b/internal/processing/user/emailconfirm_test.go @@ -88,6 +88,27 @@ func (suite *EmailConfirmTestSuite) TestConfirmEmail() { suite.WithinDuration(updatedUser.UpdatedAt, time.Now(), 1*time.Minute) } +func (suite *EmailConfirmTestSuite) TestConfirmEmailOldToken() { + ctx := context.Background() + + user := suite.testUsers["local_account_1"] + + // set a bunch of stuff on the user as though zork hasn't been confirmed yet, but has had an email sent 8 days ago + user.UnconfirmedEmail = "some.email@example.org" + user.Email = "" + user.ConfirmedAt = time.Time{} + user.ConfirmationSentAt = time.Now().Add(-192 * time.Hour) + user.ConfirmationToken = "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6" + + err := suite.db.UpdateByPrimaryKey(ctx, user) + suite.NoError(err) + + // confirm with the token set above + updatedUser, errWithCode := suite.user.ConfirmEmail(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") + suite.Nil(updatedUser) + suite.EqualError(errWithCode, "confirmation token more than a week old, please request a new one") +} + func TestEmailConfirmTestSuite(t *testing.T) { suite.Run(t, &EmailConfirmTestSuite{}) } From e94c686dc553e23fb4be7f7afc2177f83c0b9b8e Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 16:07:18 +0200 Subject: [PATCH 38/44] change error message a bit --- internal/processing/user/emailconfirm.go | 2 +- internal/processing/user/emailconfirm_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go index f3b7ab0897..1f9cb0a104 100644 --- a/internal/processing/user/emailconfirm.go +++ b/internal/processing/user/emailconfirm.go @@ -114,7 +114,7 @@ func (p *processor) ConfirmEmail(ctx context.Context, token string) (*gtsmodel.U } if user.ConfirmationSentAt.Before(time.Now().Add(-oneWeek)) { - return nil, gtserror.NewErrorForbidden(errors.New("confirmation token more than a week old, please request a new one")) + return nil, gtserror.NewErrorForbidden(errors.New("ConfirmEmail: confirmation token expired")) } // mark the user's email address as confirmed + remove the unconfirmed address and the token diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/emailconfirm_test.go index f269f4db41..826f585cdf 100644 --- a/internal/processing/user/emailconfirm_test.go +++ b/internal/processing/user/emailconfirm_test.go @@ -106,7 +106,7 @@ func (suite *EmailConfirmTestSuite) TestConfirmEmailOldToken() { // confirm with the token set above updatedUser, errWithCode := suite.user.ConfirmEmail(ctx, "1d1aa44b-afa4-49c8-ac4b-eceb61715cc6") suite.Nil(updatedUser) - suite.EqualError(errWithCode, "confirmation token more than a week old, please request a new one") + suite.EqualError(errWithCode, "ConfirmEmail: confirmation token expired") } func TestEmailConfirmTestSuite(t *testing.T) { From 478ddc70ae3025c5c58309749ece12d319efd559 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 16:50:42 +0200 Subject: [PATCH 39/44] add basic smtp docs --- docs/configuration/smtp.md | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/configuration/smtp.md diff --git a/docs/configuration/smtp.md b/docs/configuration/smtp.md new file mode 100644 index 0000000000..14959d245e --- /dev/null +++ b/docs/configuration/smtp.md @@ -0,0 +1,59 @@ +# Email Config (smtp) + +GoToSocial supports sending emails to users via the [Simple Mail Transfer Protocol](https://nl.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) or **smtp**. + +Configuring GoToSocial to send emails is **not required** in order to have a properly running instance. Still, it's very useful for doing things like sending confirmation emails and notifications, and handling password reset requests. + +In order to make GoToSocial email sending work, you need an smtp-compatible mail service running somewhere, either as a server on the same machine that GoToSocial is running on, or via an external service like [Mailgun](https://mailgun.com). It may also be possible to use free a personal email address for sending emails, if your email provider supports smtp (check with them), but you might run into trouble sending lots of emails. + +## Settings + +The configuration options for smtp are as follows: + +```yaml +####################### +##### SMTP CONFIG ##### +####################### + +# Config for sending emails via an smtp server. See https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol +smtp: + + # String. The hostname of the smtp server you want to use. + # If this is not set, smtp will not be used to send emails, and you can ignore the other settings. + # Examples: ["mail.example.org", "localhost"] + # Default: "" + host: "" + # Int. Port to use to connect to the smtp server. + # Examples: [] + # Default: 0 + port: 0 + # String. Username to use when authenticating with the smtp server. + # This should have been provided to you by your smtp host. + # This is often, but not always, an email address. + # Examples: ["maillord@example.org"] + # Default: "" + username: + # String. Password to use when authenticating with the smtp server. + # This should have been provided to you by your smtp host. + # Examples: ["1234", "password"] + # Default: "" + password: + # String. 'From' address for sent emails. + # Examples: ["mail@example.org"] + # Default: "" + from: "" +``` + +Note that if you don't set `Host`, then email sending via smtp will be disabled, and the other settings will be ignored. GoToSocial will still log (at trace level) emails that *would* have been sent if smtp was enabled. + +## Behavior + +### SSL + +GoToSocial requires your smtp server to present valid SSL certificates. Most of the big services like Mailgun do this anyway, but if you're running your own mail server without SSL for some reason, and you're trying to connect GoToSocial to it, it will not work. + +The exception to this requirement is if you're running your mail server (or bridge to a mail server) on `localhost`, in which case SSL certs are not required. + +### When are emails sent? + +Currently, emails are only sent to users to request email confirmation when a new account is created, or to serve password reset requests. More email functionality will probably be added later. From 667e3cab07a3d8c5b5ddddfed66a9b724f09530d Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 16:52:37 +0200 Subject: [PATCH 40/44] add a few more snippets --- docs/configuration/smtp.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/configuration/smtp.md b/docs/configuration/smtp.md index 14959d245e..e5fa7b68ae 100644 --- a/docs/configuration/smtp.md +++ b/docs/configuration/smtp.md @@ -57,3 +57,11 @@ The exception to this requirement is if you're running your mail server (or brid ### When are emails sent? Currently, emails are only sent to users to request email confirmation when a new account is created, or to serve password reset requests. More email functionality will probably be added later. + +### HTML versus Plaintext + +Emails are sent in HTML by default. At this point, there is no option to send emails in plaintext, but this is something that might be added later if there's enough demand for it. + +## Customization + +If you like, you can customize the templates that are used for generating emails. Follow the examples in `web/templates`. From d0fce9b065165e349e55f7964b60dbf04b4cac56 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 18 Oct 2021 16:54:38 +0200 Subject: [PATCH 41/44] typo --- docs/configuration/smtp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/smtp.md b/docs/configuration/smtp.md index e5fa7b68ae..01ba55560c 100644 --- a/docs/configuration/smtp.md +++ b/docs/configuration/smtp.md @@ -4,7 +4,7 @@ GoToSocial supports sending emails to users via the [Simple Mail Transfer Protoc Configuring GoToSocial to send emails is **not required** in order to have a properly running instance. Still, it's very useful for doing things like sending confirmation emails and notifications, and handling password reset requests. -In order to make GoToSocial email sending work, you need an smtp-compatible mail service running somewhere, either as a server on the same machine that GoToSocial is running on, or via an external service like [Mailgun](https://mailgun.com). It may also be possible to use free a personal email address for sending emails, if your email provider supports smtp (check with them), but you might run into trouble sending lots of emails. +In order to make GoToSocial email sending work, you need an smtp-compatible mail service running somewhere, either as a server on the same machine that GoToSocial is running on, or via an external service like [Mailgun](https://mailgun.com). It may also be possible to use a free personal email address for sending emails, if your email provider supports smtp (check with them--most do), but you might run into trouble sending lots of emails. ## Settings From 9c7e06318c5afa9b3dfdda405cbbd21b71905b61 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 24 Oct 2021 16:35:23 +0200 Subject: [PATCH 42/44] add email sender to outbox tests --- internal/api/s2s/user/outboxget_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/api/s2s/user/outboxget_test.go b/internal/api/s2s/user/outboxget_test.go index f1818683e6..4f5ea3f174 100644 --- a/internal/api/s2s/user/outboxget_test.go +++ b/internal/api/s2s/user/outboxget_test.go @@ -46,7 +46,8 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -99,7 +100,8 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request @@ -152,7 +154,8 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() { tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) federator := testrig.NewTestFederator(suite.db, tc, suite.storage) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator) + emailSender := testrig.NewEmailSender("../../../../web/template/", nil) + processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) userModule := user.New(suite.config, processor).(*user.Module) // setup request From 1470202245ce412383e1c86a0df88b215a8228ca Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 31 Oct 2021 15:30:56 +0100 Subject: [PATCH 43/44] don't use dutch wikipedia link --- docs/configuration/smtp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/smtp.md b/docs/configuration/smtp.md index 01ba55560c..d191be3c8f 100644 --- a/docs/configuration/smtp.md +++ b/docs/configuration/smtp.md @@ -1,6 +1,6 @@ # Email Config (smtp) -GoToSocial supports sending emails to users via the [Simple Mail Transfer Protocol](https://nl.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) or **smtp**. +GoToSocial supports sending emails to users via the [Simple Mail Transfer Protocol](https://wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) or **smtp**. Configuring GoToSocial to send emails is **not required** in order to have a properly running instance. Still, it's very useful for doing things like sending confirmation emails and notifications, and handling password reset requests. From edfc7fe34d2510f6ffd5225162a342d1d70e0857 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 31 Oct 2021 15:40:03 +0100 Subject: [PATCH 44/44] don't minify email html --- internal/email/confirm.go | 8 +------- internal/email/noopsender.go | 13 ++----------- internal/email/reset.go | 8 +------- internal/email/util_test.go | 4 ++-- internal/processing/user/emailconfirm_test.go | 2 +- 5 files changed, 7 insertions(+), 28 deletions(-) diff --git a/internal/email/confirm.go b/internal/email/confirm.go index 363cdb8b03..78f661fe14 100644 --- a/internal/email/confirm.go +++ b/internal/email/confirm.go @@ -21,8 +21,6 @@ package email import ( "bytes" "net/smtp" - - "github.com/superseriousbusiness/gotosocial/internal/text" ) const ( @@ -35,11 +33,7 @@ func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { return err } - - confirmBody, err := text.MinifyHTML(buf.String()) - if err != nil { - return err - } + confirmBody := buf.String() msg := assembleMessage(confirmSubject, confirmBody, toAddress, s.from) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) diff --git a/internal/email/noopsender.go b/internal/email/noopsender.go index 5efb963a49..82eb8db441 100644 --- a/internal/email/noopsender.go +++ b/internal/email/noopsender.go @@ -23,7 +23,6 @@ import ( "html/template" "github.com/sirupsen/logrus" - "github.com/superseriousbusiness/gotosocial/internal/text" ) // NewNoopSender returns a no-op email sender that will just execute the given sendCallback @@ -52,11 +51,7 @@ func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil { return err } - - confirmBody, err := text.MinifyHTML(buf.String()) - if err != nil { - return err - } + confirmBody := buf.String() msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") @@ -73,11 +68,7 @@ func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { return err } - - resetBody, err := text.MinifyHTML(buf.String()) - if err != nil { - return err - } + resetBody := buf.String() msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") diff --git a/internal/email/reset.go b/internal/email/reset.go index 6fb72084f1..786777f1b1 100644 --- a/internal/email/reset.go +++ b/internal/email/reset.go @@ -21,8 +21,6 @@ package email import ( "bytes" "net/smtp" - - "github.com/superseriousbusiness/gotosocial/internal/text" ) const ( @@ -35,11 +33,7 @@ func (s *sender) SendResetEmail(toAddress string, data ResetData) error { if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil { return err } - - resetBody, err := text.MinifyHTML(buf.String()) - if err != nil { - return err - } + resetBody := buf.String() msg := assembleMessage(resetSubject, resetBody, toAddress, s.from) return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) diff --git a/internal/email/util_test.go b/internal/email/util_test.go index 8a16d093d8..201bcd92da 100644 --- a/internal/email/util_test.go +++ b/internal/email/util_test.go @@ -39,7 +39,7 @@ func (suite *UtilTestSuite) TestTemplateConfirm() { suite.sender.SendConfirmEmail("user@example.org", confirmData) suite.Len(suite.sentEmails, 1) - suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n

Hello test!

You are receiving this mail because you've requested an account on Test Instance.

We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:

https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa

If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.

\r\n", suite.sentEmails["user@example.org"]) + suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on Test Instance.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", suite.sentEmails["user@example.org"]) } func (suite *UtilTestSuite) TestTemplateReset() { @@ -52,7 +52,7 @@ func (suite *UtilTestSuite) TestTemplateReset() { suite.sender.SendResetEmail("user@example.org", resetData) suite.Len(suite.sentEmails, 1) - suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n

Hello test!

You are receiving this mail because a password reset has been requested for your account on Test Instance.

To reset your password, click here or paste the following in your browser's address bar:

https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa

If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.

\r\n", suite.sentEmails["user@example.org"]) + suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial \r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello test!\n

\n
\n
\n

\n You are receiving this mail because a password reset has been requested for your account on Test Instance.\n

\n

\n To reset your password, click here or paste the following in your browser's address bar:\n

\n

\n \n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of Test Instance.\n

\n
\n \n\r\n", suite.sentEmails["user@example.org"]) } func TestUtilTestSuite(t *testing.T) { diff --git a/internal/processing/user/emailconfirm_test.go b/internal/processing/user/emailconfirm_test.go index 826f585cdf..40d5956aa4 100644 --- a/internal/processing/user/emailconfirm_test.go +++ b/internal/processing/user/emailconfirm_test.go @@ -54,7 +54,7 @@ func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() { suite.NotEmpty(token) // email should contain the token - emailShould := fmt.Sprintf("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: some.email@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n

Hello the_mighty_zork!

You are receiving this mail because you've requested an account on localhost:8080.

We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:

http://localhost:8080/confirm_email?token=%s

If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of localhost:8080.

\r\n", token, token) + emailShould := fmt.Sprintf("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial \r\nTo: some.email@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n\n\n \n \n
\n

\n Hello the_mighty_zork!\n

\n
\n
\n

\n You are receiving this mail because you've requested an account on localhost:8080.\n

\n

\n We just need to confirm that this is your email address. To confirm your email, click here or paste the following in your browser's address bar:\n

\n

\n \n http://localhost:8080/confirm_email?token=%s\n \n

\n
\n
\n

\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of localhost:8080.\n

\n
\n \n\r\n", token, token) suite.Equal(emailShould, email) // confirmationSentAt should be recent