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:
commit
33e75375fd
3 changed files with 40 additions and 0 deletions
|
@ -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)):
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue