forked from mirrors/gotosocial
[bug] Send plaintext emails to fix "message refused: Message is not RFC 2822 compliant" (#366)
* trying to fix "message refused: Message is not RFC 2822 compliant" * fix "message refused: Message is not RFC 2822 compliant" 550 5.7.1 Delivery not authorized, message refused: Message is not RFC 2822 compliant * remove silly regex * lint * fix tests * we should use text/template instead of html/template now
This commit is contained in:
parent
959e38ac5c
commit
5be8a7a7ea
11 changed files with 78 additions and 28 deletions
|
@ -21,11 +21,15 @@ package email
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
confirmTemplate = "email_confirm.tmpl"
|
confirmTemplate = "email_confirm_text.tmpl"
|
||||||
confirmSubject = "Subject: GoToSocial Email Confirmation"
|
confirmSubject = "GoToSocial Email Confirmation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error {
|
func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error {
|
||||||
|
@ -35,7 +39,11 @@ func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error {
|
||||||
}
|
}
|
||||||
confirmBody := buf.String()
|
confirmBody := buf.String()
|
||||||
|
|
||||||
msg := assembleMessage(confirmSubject, confirmBody, toAddress, s.from)
|
msg, err := assembleMessage(confirmSubject, confirmBody, toAddress, s.from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.WithField("func", "SendConfirmEmail").Trace(s.hostAddress + "\n" + viper.GetString(config.Keys.SMTPUsername) + ":password" + "\n" + s.from + "\n" + toAddress + "\n\n" + string(msg) + "\n")
|
||||||
return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg)
|
return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"html/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -57,7 +57,10 @@ func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error
|
||||||
}
|
}
|
||||||
confirmBody := buf.String()
|
confirmBody := buf.String()
|
||||||
|
|
||||||
msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org")
|
msg, err := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Tracef("NOT SENDING confirmation email to %s with contents: %s", toAddress, msg)
|
logrus.Tracef("NOT SENDING confirmation email to %s with contents: %s", toAddress, msg)
|
||||||
|
|
||||||
|
@ -74,7 +77,10 @@ func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error {
|
||||||
}
|
}
|
||||||
resetBody := buf.String()
|
resetBody := buf.String()
|
||||||
|
|
||||||
msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org")
|
msg, err := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Tracef("NOT SENDING reset email to %s with contents: %s", toAddress, msg)
|
logrus.Tracef("NOT SENDING reset email to %s with contents: %s", toAddress, msg)
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
resetTemplate = "email_reset.tmpl"
|
resetTemplate = "email_reset_text.tmpl"
|
||||||
resetSubject = "Subject: GoToSocial Password Reset"
|
resetSubject = "GoToSocial Password Reset"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *sender) SendResetEmail(toAddress string, data ResetData) error {
|
func (s *sender) SendResetEmail(toAddress string, data ResetData) error {
|
||||||
|
@ -35,7 +35,10 @@ func (s *sender) SendResetEmail(toAddress string, data ResetData) error {
|
||||||
}
|
}
|
||||||
resetBody := buf.String()
|
resetBody := buf.String()
|
||||||
|
|
||||||
msg := assembleMessage(resetSubject, resetBody, toAddress, s.from)
|
msg, err := assembleMessage(resetSubject, resetBody, toAddress, s.from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg)
|
return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
|
|
@ -19,15 +19,12 @@
|
||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
"strings"
|
||||||
|
"text/template"
|
||||||
const (
|
|
||||||
mime = `MIME-version: 1.0;
|
|
||||||
Content-Type: text/html;`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadTemplates(templateBaseDir string) (*template.Template, error) {
|
func loadTemplates(templateBaseDir string) (*template.Template, error) {
|
||||||
|
@ -41,16 +38,34 @@ func loadTemplates(templateBaseDir string) (*template.Template, error) {
|
||||||
return template.ParseGlob(tmPath)
|
return template.ParseGlob(tmPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assembleMessage(mailSubject string, mailBody string, mailTo string, mailFrom string) []byte {
|
// https://datatracker.ietf.org/doc/html/rfc2822
|
||||||
from := fmt.Sprintf("From: GoToSocial <%s>", mailFrom)
|
// I did not read the RFC, I just copy and pasted from
|
||||||
to := fmt.Sprintf("To: %s", mailTo)
|
// https://pkg.go.dev/net/smtp#SendMail
|
||||||
|
// and it did seem to work.
|
||||||
|
func assembleMessage(mailSubject string, mailBody string, mailTo string, mailFrom string) ([]byte, error) {
|
||||||
|
|
||||||
|
if strings.Contains(mailSubject, "\r") || strings.Contains(mailSubject, "\n") {
|
||||||
|
return nil, errors.New("email subject must not contain newline characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(mailFrom, "\r") || strings.Contains(mailFrom, "\n") {
|
||||||
|
return nil, errors.New("email from address must not contain newline characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(mailTo, "\r") || strings.Contains(mailTo, "\n") {
|
||||||
|
return nil, errors.New("email to address must not contain newline characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize the message body to use CRLF line endings
|
||||||
|
mailBody = strings.ReplaceAll(mailBody, "\r\n", "\n")
|
||||||
|
mailBody = strings.ReplaceAll(mailBody, "\n", "\r\n")
|
||||||
|
|
||||||
msg := []byte(
|
msg := []byte(
|
||||||
mailSubject + "\r\n" +
|
"To: " + mailTo + "\r\n" +
|
||||||
from + "\r\n" +
|
"Subject: " + mailSubject + "\r\n" +
|
||||||
to + "\r\n" +
|
"\r\n" +
|
||||||
mime + "\r\n" +
|
mailBody + "\r\n",
|
||||||
mailBody + "\r\n")
|
)
|
||||||
|
|
||||||
return msg
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (suite *UtilTestSuite) TestTemplateConfirm() {
|
||||||
|
|
||||||
suite.sender.SendConfirmEmail("user@example.org", confirmData)
|
suite.sender.SendConfirmEmail("user@example.org", confirmData)
|
||||||
suite.Len(suite.sentEmails, 1)
|
suite.Len(suite.sentEmails, 1)
|
||||||
suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial <test@example.org>\r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n<!DOCTYPE html>\n<html>\n </head>\n <body>\n <div>\n <h1>\n Hello test!\n </h1>\n </div>\n <div>\n <p>\n You are receiving this mail because you've requested an account on <a href=\"https://example.org\">Test Instance</a>.\n </p>\n <p>\n We just need to confirm that this is your email address. To confirm your email, <a href=\"https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\">click here</a> or paste the following in your browser's address bar:\n </p>\n <p>\n <code>\n https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n </code>\n </p>\n </div>\n <div>\n <p>\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of <a href=\"https://example.org\">Test Instance</a>.\n </p>\n </div>\n </body>\n</html>\r\n", suite.sentEmails["user@example.org"])
|
suite.Equal("To: user@example.org\r\nSubject: GoToSocial Email Confirmation\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\nWe just need to confirm that this is your email address. To 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\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"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UtilTestSuite) TestTemplateReset() {
|
func (suite *UtilTestSuite) TestTemplateReset() {
|
||||||
|
@ -52,7 +52,7 @@ func (suite *UtilTestSuite) TestTemplateReset() {
|
||||||
|
|
||||||
suite.sender.SendResetEmail("user@example.org", resetData)
|
suite.sender.SendResetEmail("user@example.org", resetData)
|
||||||
suite.Len(suite.sentEmails, 1)
|
suite.Len(suite.sentEmails, 1)
|
||||||
suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial <test@example.org>\r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n<!DOCTYPE html>\n<html>\n </head>\n <body>\n <div>\n <h1>\n Hello test!\n </h1>\n </div>\n <div>\n <p>\n You are receiving this mail because a password reset has been requested for your account on <a href=\"https://example.org\">Test Instance</a>.\n </p>\n <p>\n To reset your password, <a href=\"https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\">click here</a> or paste the following in your browser's address bar:\n </p>\n <p>\n <code>\n https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n </code>\n </p>\n </div>\n <div>\n <p>\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of <a href=\"https://example.org\">Test Instance</a>.\n </p>\n </div>\n </body>\n</html>\r\n", suite.sentEmails["user@example.org"])
|
suite.Equal("To: user@example.org\r\nSubject: GoToSocial Password Reset\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\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"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUtilTestSuite(t *testing.T) {
|
func TestUtilTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() {
|
||||||
suite.NotEmpty(token)
|
suite.NotEmpty(token)
|
||||||
|
|
||||||
// email should contain the token
|
// email should contain the token
|
||||||
emailShould := fmt.Sprintf("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial <test@example.org>\r\nTo: some.email@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n<!DOCTYPE html>\n<html>\n </head>\n <body>\n <div>\n <h1>\n Hello the_mighty_zork!\n </h1>\n </div>\n <div>\n <p>\n You are receiving this mail because you've requested an account on <a href=\"http://localhost:8080\">localhost:8080</a>.\n </p>\n <p>\n We just need to confirm that this is your email address. To confirm your email, <a href=\"http://localhost:8080/confirm_email?token=%s\">click here</a> or paste the following in your browser's address bar:\n </p>\n <p>\n <code>\n http://localhost:8080/confirm_email?token=%s\n </code>\n </p>\n </div>\n <div>\n <p>\n If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of <a href=\"http://localhost:8080\">localhost:8080</a>.\n </p>\n </div>\n </body>\n</html>\r\n", token, token)
|
emailShould := fmt.Sprintf("To: some.email@example.org\r\nSubject: GoToSocial Email Confirmation\r\n\r\nHello the_mighty_zork!\r\n\r\nYou are receiving this mail because you've requested an account on http://localhost:8080.\r\n\r\nWe just need to confirm that this is your email address. To confirm your email, paste the following in your browser's address bar:\r\n\r\nhttp://localhost:8080/confirm_email?token=%s\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of http://localhost:8080\r\n\r\n", token)
|
||||||
suite.Equal(emailShould, email)
|
suite.Equal(emailShould, email)
|
||||||
|
|
||||||
// confirmationSentAt should be recent
|
// confirmationSentAt should be recent
|
||||||
|
|
9
web/template/email_confirm_text.tmpl
Normal file
9
web/template/email_confirm_text.tmpl
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Hello {{.Username}}!
|
||||||
|
|
||||||
|
You are receiving this mail because you've requested an account on {{.InstanceURL}}.
|
||||||
|
|
||||||
|
We just need to confirm that this is your email address. To confirm your email, 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 {{.InstanceURL}}
|
9
web/template/email_reset_text.tmpl
Normal file
9
web/template/email_reset_text.tmpl
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Hello {{.Username}}!
|
||||||
|
|
||||||
|
You are receiving this mail because a password reset has been requested for your account on {{.InstanceURL}}.
|
||||||
|
|
||||||
|
To reset your password, 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 {{.InstanceURL}}.
|
Loading…
Reference in a new issue