Просмотр исходного кода

add support to pass access-token for e-mail publishing

Philipp Born 3 лет назад
Родитель
Сommit
e892b994c3
3 измененных файлов с 40 добавлено и 0 удалено
  1. 5 0
      docs/publish.md
  2. 14 0
      server/smtp_server.go
  3. 21 0
      server/smtp_server_test.go

+ 5 - 0
docs/publish.md

@@ -2582,6 +2582,11 @@ format is:
 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,
 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)):

+ 14 - 0
server/smtp_server.go

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

+ 21 - 0
server/smtp_server_test.go

@@ -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")
 }
 
+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)
 
 func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {