[bugfix] add Date and Message-ID headers for email (#3031)
* [bugfix] add Date and Message-ID headers for email This should make spam filters more happy, as most of them grant some negative score for not having those headers. Also the Date is convenient for the user receiving the mail. * make golangci-lint happy
This commit is contained in:
parent
15e0bf6e5a
commit
c2738474d5
|
@ -26,7 +26,9 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
@ -37,7 +39,7 @@ func (s *sender) sendTemplate(template string, subject string, data any, toAddre
|
|||
return err
|
||||
}
|
||||
|
||||
msg, err := assembleMessage(subject, buf.String(), s.from, toAddresses...)
|
||||
msg, err := assembleMessage(subject, buf.String(), s.from, s.msgIDHost, toAddresses...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -65,7 +67,7 @@ func loadTemplates(templateBaseDir string) (*template.Template, error) {
|
|||
// assembleMessage assembles a valid email message following:
|
||||
// - https://datatracker.ietf.org/doc/html/rfc2822
|
||||
// - https://pkg.go.dev/net/smtp#SendMail
|
||||
func assembleMessage(mailSubject string, mailBody string, mailFrom string, mailTo ...string) ([]byte, error) {
|
||||
func assembleMessage(mailSubject string, mailBody string, mailFrom string, msgIDHost string, mailTo ...string) ([]byte, error) {
|
||||
if strings.ContainsAny(mailSubject, "\r\n") {
|
||||
return nil, errors.New("email subject must not contain newline characters")
|
||||
}
|
||||
|
@ -103,7 +105,9 @@ func assembleMessage(mailSubject string, mailBody string, mailFrom string, mailT
|
|||
// msg headers.'
|
||||
msg.WriteString("To: Undisclosed Recipients:;" + CRLF)
|
||||
}
|
||||
msg.WriteString("Date: " + time.Now().Format(time.RFC822Z) + CRLF)
|
||||
msg.WriteString("From: " + mailFrom + CRLF)
|
||||
msg.WriteString("Message-ID: <" + uuid.New().String() + "@" + msgIDHost + ">" + CRLF)
|
||||
msg.WriteString("Subject: " + mailSubject + CRLF)
|
||||
msg.WriteString("MIME-Version: 1.0" + CRLF)
|
||||
msg.WriteString("Content-Transfer-Encoding: 8bit" + CRLF)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package email_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
@ -40,6 +41,15 @@ func (suite *EmailTestSuite) SetupTest() {
|
|||
suite.sender = testrig.NewEmailSender("../../web/template/", suite.sentEmails)
|
||||
}
|
||||
|
||||
// strips non deteministic headers from mails
|
||||
func (suite *EmailTestSuite) stripHeaders() {
|
||||
re := regexp.MustCompile(`(?m)^(Date:|Message-ID:) .*$\n`)
|
||||
for key, mail := range suite.sentEmails {
|
||||
res := re.ReplaceAllString(mail, "")
|
||||
suite.sentEmails[key] = res
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *EmailTestSuite) TestTemplateConfirmNewSignup() {
|
||||
confirmData := email.ConfirmData{
|
||||
Username: "test",
|
||||
|
@ -50,6 +60,7 @@ func (suite *EmailTestSuite) TestTemplateConfirmNewSignup() {
|
|||
}
|
||||
|
||||
suite.sender.SendConfirmEmail("user@example.org", confirmData)
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial Email Confirmation\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello test!\r\n\r\nYou are receiving this mail because you've requested an account on https://example.org.\r\n\r\nTo use your account, you must confirm that this is your email address.\r\n\r\nTo confirm your email, paste the following in your browser's address bar:\r\n\r\nhttps://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\r\n\r\n---\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org.\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -64,6 +75,7 @@ func (suite *EmailTestSuite) TestTemplateConfirm() {
|
|||
}
|
||||
|
||||
suite.sender.SendConfirmEmail("user@example.org", confirmData)
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial Email Confirmation\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello test!\r\n\r\nYou are receiving this mail because you've requested an email address change on https://example.org.\r\n\r\nTo complete the change, you must confirm that this is your email address.\r\n\r\nTo confirm your email, paste the following in your browser's address bar:\r\n\r\nhttps://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\r\n\r\n---\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org.\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -77,6 +89,7 @@ func (suite *EmailTestSuite) TestTemplateReset() {
|
|||
}
|
||||
|
||||
suite.sender.SendResetEmail("user@example.org", resetData)
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial Password Reset\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello test!\r\n\r\nYou are receiving this mail because a password reset has been requested for your account on https://example.org.\r\n\r\nTo reset your password, paste the following in your browser's address bar:\r\n\r\nhttps://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\r\n\r\n---\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org.\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -94,6 +107,7 @@ func (suite *EmailTestSuite) TestTemplateReportRemoteToLocal() {
|
|||
if err := suite.sender.SendNewReportEmail([]string{"user@example.org"}, reportData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial New Report\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello moderator of Test Instance (https://example.org)!\r\n\r\nSomeone from fossbros-anonymous.io has reported a user from your instance.\r\n\r\nTo view the report, paste the following link into your browser: https://example.org/settings/admin/reports/01GVJHN1RTYZCZTCXVPPPKBX6R\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -111,6 +125,7 @@ func (suite *EmailTestSuite) TestTemplateReportLocalToRemote() {
|
|||
if err := suite.sender.SendNewReportEmail([]string{"user@example.org"}, reportData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial New Report\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello moderator of Test Instance (https://example.org)!\r\n\r\nSomeone from your instance has reported a user from fossbros-anonymous.io.\r\n\r\nTo view the report, paste the following link into your browser: https://example.org/settings/admin/reports/01GVJHN1RTYZCZTCXVPPPKBX6R\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -128,6 +143,7 @@ func (suite *EmailTestSuite) TestTemplateReportLocalToLocal() {
|
|||
if err := suite.sender.SendNewReportEmail([]string{"user@example.org"}, reportData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial New Report\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello moderator of Test Instance (https://example.org)!\r\n\r\nSomeone from your instance has reported another user from your instance.\r\n\r\nTo view the report, paste the following link into your browser: https://example.org/settings/admin/reports/01GVJHN1RTYZCZTCXVPPPKBX6R\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -145,6 +161,7 @@ func (suite *EmailTestSuite) TestTemplateReportMoreThanOneModeratorAddress() {
|
|||
if err := suite.sender.SendNewReportEmail([]string{"user@example.org", "admin@example.org"}, reportData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: Undisclosed Recipients:;\r\nFrom: test@example.org\r\nSubject: GoToSocial New Report\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello moderator of Test Instance (https://example.org)!\r\n\r\nSomeone from fossbros-anonymous.io has reported a user from your instance.\r\n\r\nTo view the report, paste the following link into your browser: https://example.org/settings/admin/reports/01GVJHN1RTYZCZTCXVPPPKBX6R\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -164,6 +181,7 @@ func (suite *EmailTestSuite) TestTemplateReportMoreThanOneModeratorAddressDisclo
|
|||
if err := suite.sender.SendNewReportEmail([]string{"user@example.org", "admin@example.org"}, reportData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org, admin@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial New Report\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello moderator of Test Instance (https://example.org)!\r\n\r\nSomeone from fossbros-anonymous.io has reported a user from your instance.\r\n\r\nTo view the report, paste the following link into your browser: https://example.org/settings/admin/reports/01GVJHN1RTYZCZTCXVPPPKBX6R\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -180,6 +198,7 @@ func (suite *EmailTestSuite) TestTemplateReportClosedOK() {
|
|||
if err := suite.sender.SendReportClosedEmail("user@example.org", reportClosedData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial Report Closed\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello !\r\n\r\nYou recently reported the account @foss_satan@fossbros-anonymous.io to the moderator(s) of Test Instance (https://example.org).\r\n\r\nThe report you submitted has now been closed.\r\n\r\nThe moderator who closed the report left the following comment: User was yeeted. Thank you for reporting!\r\n\r\n---\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org.\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
@ -196,6 +215,7 @@ func (suite *EmailTestSuite) TestTemplateReportClosedLocalAccountNoComment() {
|
|||
if err := suite.sender.SendReportClosedEmail("user@example.org", reportClosedData); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.stripHeaders()
|
||||
suite.Len(suite.sentEmails, 1)
|
||||
suite.Equal("To: user@example.org\r\nFrom: test@example.org\r\nSubject: GoToSocial Report Closed\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\n\r\nHello !\r\n\r\nYou recently reported the account @1happyturtle to the moderator(s) of Test Instance (https://example.org).\r\n\r\nThe report you submitted has now been closed.\r\n\r\nThe moderator who closed the report did not leave a comment.\r\n\r\n---\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org.\r\n\r\n", suite.sentEmails["user@example.org"])
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
// Passing a nil function is also acceptable, in which case the send functions will just return nil.
|
||||
func NewNoopSender(sendCallback func(toAddress string, message string)) (Sender, error) {
|
||||
templateBaseDir := config.GetWebTemplateBaseDir()
|
||||
msgIDHost := config.GetHost()
|
||||
|
||||
t, err := loadTemplates(templateBaseDir)
|
||||
if err != nil {
|
||||
|
@ -39,12 +40,14 @@ func NewNoopSender(sendCallback func(toAddress string, message string)) (Sender,
|
|||
|
||||
return &noopSender{
|
||||
sendCallback: sendCallback,
|
||||
msgIDHost: msgIDHost,
|
||||
template: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type noopSender struct {
|
||||
sendCallback func(toAddress string, message string)
|
||||
msgIDHost string
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
|
@ -86,7 +89,7 @@ func (s *noopSender) sendTemplate(template string, subject string, data any, toA
|
|||
return err
|
||||
}
|
||||
|
||||
msg, err := assembleMessage(subject, buf.String(), "test@example.org", toAddresses...)
|
||||
msg, err := assembleMessage(subject, buf.String(), "test@example.org", s.msgIDHost, toAddresses...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -76,11 +76,13 @@ func NewSender() (Sender, error) {
|
|||
host := config.GetSMTPHost()
|
||||
port := config.GetSMTPPort()
|
||||
from := config.GetSMTPFrom()
|
||||
msgIDHost := config.GetHost()
|
||||
|
||||
return &sender{
|
||||
hostAddress: fmt.Sprintf("%s:%d", host, port),
|
||||
from: from,
|
||||
auth: smtp.PlainAuth("", username, password, host),
|
||||
msgIDHost: msgIDHost,
|
||||
template: t,
|
||||
}, nil
|
||||
}
|
||||
|
@ -89,5 +91,6 @@ type sender struct {
|
|||
hostAddress string
|
||||
from string
|
||||
auth smtp.Auth
|
||||
msgIDHost string
|
||||
template *template.Template
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue