Merge pull request #621 from tamcore/feature/email-with-access-control

Make email publishing work, when access-control is enabled
This commit is contained in:
Philipp C. Heckel 2023-02-20 15:47:05 -05:00 committed by GitHub
commit 33e75375fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 0 deletions

View file

@ -2582,6 +2582,11 @@ format is:
ntfy-$topic@ntfy.sh ntfy-$topic@ntfy.sh
``` ```
If [access control](config.md#access-control) is enabled, and the target topic does not support anonymous writes, e-mail publishing won't work without providing an authorized access token. That will change the format of the e-mail's recipient address to
```
ntfy-$topic+$token@ntfy.sh
```
As of today, e-mail publishing only supports adding a [message title](#message-title) (the e-mail subject). Tags, priority, As of today, e-mail publishing only supports adding a [message title](#message-title) (the e-mail subject). Tags, priority,
delay and other features are not supported (yet). Here's an example that will publish a message with the delay and other features are not supported (yet). Here's an example that will publish a message with the
title `You've Got Mail` to topic `sometopic` (see [ntfy.sh/sometopic](https://ntfy.sh/sometopic)): title `You've Got Mail` to topic `sometopic` (see [ntfy.sh/sometopic](https://ntfy.sh/sometopic)):

View file

@ -65,6 +65,7 @@ type smtpSession struct {
backend *smtpBackend backend *smtpBackend
conn *smtp.Conn conn *smtp.Conn
topic string topic string
token string
mu sync.Mutex mu sync.Mutex
} }
@ -81,6 +82,7 @@ func (s *smtpSession) Mail(from string, opts *smtp.MailOptions) error {
func (s *smtpSession) Rcpt(to string) error { func (s *smtpSession) Rcpt(to string) error {
logem(s.conn).Field("smtp_rcpt_to", to).Debug("RCPT TO: %s", to) logem(s.conn).Field("smtp_rcpt_to", to).Debug("RCPT TO: %s", to)
return s.withFailCount(func() error { return s.withFailCount(func() error {
token := ""
conf := s.backend.config conf := s.backend.config
addressList, err := mail.ParseAddressList(to) addressList, err := mail.ParseAddressList(to)
if err != nil { if err != nil {
@ -92,18 +94,27 @@ func (s *smtpSession) Rcpt(to string) error {
if !strings.HasSuffix(to, "@"+conf.SMTPServerDomain) { if !strings.HasSuffix(to, "@"+conf.SMTPServerDomain) {
return errInvalidDomain return errInvalidDomain
} }
// remove @ntfy.sh from end of email
to = strings.TrimSuffix(to, "@"+conf.SMTPServerDomain) to = strings.TrimSuffix(to, "@"+conf.SMTPServerDomain)
if conf.SMTPServerAddrPrefix != "" { if conf.SMTPServerAddrPrefix != "" {
if !strings.HasPrefix(to, conf.SMTPServerAddrPrefix) { if !strings.HasPrefix(to, conf.SMTPServerAddrPrefix) {
return errInvalidAddress return errInvalidAddress
} }
// remove ntfy- from beginning of email
to = strings.TrimPrefix(to, conf.SMTPServerAddrPrefix) to = strings.TrimPrefix(to, conf.SMTPServerAddrPrefix)
} }
// if email contains token, split topic and token
if strings.Contains(to, "+") {
parts := strings.Split(to, "+")
to = parts[0]
token = parts[1]
}
if !topicRegex.MatchString(to) { if !topicRegex.MatchString(to) {
return errInvalidTopic return errInvalidTopic
} }
s.mu.Lock() s.mu.Lock()
s.topic = to s.topic = to
s.token = token
s.mu.Unlock() s.mu.Unlock()
return nil return nil
}) })
@ -177,6 +188,9 @@ func (s *smtpSession) publishMessage(m *message) error {
if m.Title != "" { if m.Title != "" {
req.Header.Set("Title", m.Title) req.Header.Set("Title", m.Title)
} }
if s.token != "" {
req.Header.Add("Authorization", "Bearer "+s.token)
}
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
s.backend.handler(rr, req) s.backend.handler(rr, req)
if rr.Code != http.StatusOK { if rr.Code != http.StatusOK {

View file

@ -492,6 +492,27 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: multipart message nested too deep") writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: multipart message nested too deep")
} }
func TestSmtpBackend_PlaintextWithToken(t *testing.T) {
email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: ntfy-mytopic+tk_KLORUqSqvNRLpY11DfkHVbHu9NGG2@ntfy.sh
DATA
Subject: Very short mail
what's up
.
`
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/mytopic", r.URL.Path)
require.Equal(t, "Very short mail", r.Header.Get("Title"))
require.Equal(t, "Bearer tk_KLORUqSqvNRLpY11DfkHVbHu9NGG2", r.Header.Get("Authorization"))
require.Equal(t, "what's up", readAll(t, r.Body))
})
defer s.Close()
defer c.Close()
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
}
type smtpHandlerFunc func(http.ResponseWriter, *http.Request) type smtpHandlerFunc func(http.ResponseWriter, *http.Request)
func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) { func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {