Ver Fonte

Merge branch 'main' into done

Philipp Heckel há 3 anos atrás
pai
commit
c40338c146
5 ficheiros alterados com 107 adições e 25 exclusões
  1. 42 18
      cmd/user.go
  2. 3 0
      docs/releases.md
  3. 2 2
      server/message_cache.go
  4. 13 5
      server/server.go
  5. 47 0
      server/server_test.go

+ 42 - 18
cmd/user.go

@@ -6,11 +6,13 @@ import (
 	"crypto/subtle"
 	"crypto/subtle"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"os"
+	"strings"
+
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2/altsrc"
 	"github.com/urfave/cli/v2/altsrc"
 	"heckel.io/ntfy/auth"
 	"heckel.io/ntfy/auth"
 	"heckel.io/ntfy/util"
 	"heckel.io/ntfy/util"
-	"strings"
 )
 )
 
 
 func init() {
 func init() {
@@ -36,7 +38,7 @@ var cmdUser = &cli.Command{
 			Name:      "add",
 			Name:      "add",
 			Aliases:   []string{"a"},
 			Aliases:   []string{"a"},
 			Usage:     "Adds a new user",
 			Usage:     "Adds a new user",
-			UsageText: "ntfy user add [--role=admin|user] USERNAME",
+			UsageText: "ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD=... ntfy user add [--role=admin|user] USERNAME",
 			Action:    execUserAdd,
 			Action:    execUserAdd,
 			Flags: []cli.Flag{
 			Flags: []cli.Flag{
 				&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"},
 				&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"},
@@ -48,8 +50,12 @@ granted otherwise by the auth-default-access setting). An admin user has read an
 topics.
 topics.
 
 
 Examples:
 Examples:
-  ntfy user add phil                 # Add regular user phil  
-  ntfy user add --role=admin phil    # Add admin user phil
+  ntfy user add phil                     # Add regular user phil  
+  ntfy user add --role=admin phil        # Add admin user phil
+  NTFY_PASSWORD=... ntfy user add phil   # Add user, using env variable to set password (for scripts)
+
+You may set the NTFY_PASSWORD environment variable to pass the password. This is useful if 
+you are creating users via scripts.
 `,
 `,
 		},
 		},
 		{
 		{
@@ -68,7 +74,7 @@ Example:
 			Name:      "change-pass",
 			Name:      "change-pass",
 			Aliases:   []string{"chp"},
 			Aliases:   []string{"chp"},
 			Usage:     "Changes a user's password",
 			Usage:     "Changes a user's password",
-			UsageText: "ntfy user change-pass USERNAME",
+			UsageText: "ntfy user change-pass USERNAME\nNTFY_PASSWORD=... ntfy user change-pass USERNAME",
 			Action:    execUserChangePass,
 			Action:    execUserChangePass,
 			Description: `Change the password for the given user.
 			Description: `Change the password for the given user.
 
 
@@ -76,7 +82,12 @@ The new password will be read from STDIN, and it'll be confirmed by typing
 it twice. 
 it twice. 
 
 
 Example:
 Example:
-    ntfy user change-pass phil
+  ntfy user change-pass phil
+  NTFY_PASSWORD=.. ntfy user change-pass phil
+
+You may set the NTFY_PASSWORD environment variable to pass the new password. This is 
+useful if you are updating users via scripts.
+
 `,
 `,
 		},
 		},
 		{
 		{
@@ -125,18 +136,24 @@ The command allows you to add/remove/change users in the ntfy user database, as
 passwords or roles.
 passwords or roles.
 
 
 Examples:
 Examples:
-  ntfy user list                     # Shows list of users (alias: 'ntfy access')                      
-  ntfy user add phil                 # Add regular user phil  
-  ntfy user add --role=admin phil    # Add admin user phil
-  ntfy user del phil                 # Delete user phil
-  ntfy user change-pass phil         # Change password for user phil
-  ntfy user change-role phil admin   # Make user phil an admin 
+  ntfy user list                               # Shows list of users (alias: 'ntfy access')                      
+  ntfy user add phil                           # Add regular user phil  
+  NTFY_PASSWORD=... ntfy user add phil         # As above, using env variable to set password (for scripts)
+  ntfy user add --role=admin phil              # Add admin user phil
+  ntfy user del phil                           # Delete user phil
+  ntfy user change-pass phil                   # Change password for user phil
+  NTFY_PASSWORD=.. ntfy user change-pass phil  # As above, using env variable to set password (for scripts)
+  ntfy user change-role phil admin             # Make user phil an admin 
+
+For the 'ntfy user add' and 'ntfy user change-pass' commands, you may set the NTFY_PASSWORD environment
+variable to pass the new password. This is useful if you are creating/updating users via scripts.
 `,
 `,
 }
 }
 
 
 func execUserAdd(c *cli.Context) error {
 func execUserAdd(c *cli.Context) error {
 	username := c.Args().Get(0)
 	username := c.Args().Get(0)
 	role := auth.Role(c.String("role"))
 	role := auth.Role(c.String("role"))
+	password := os.Getenv("NTFY_PASSWORD")
 	if username == "" {
 	if username == "" {
 		return errors.New("username expected, type 'ntfy user add --help' for help")
 		return errors.New("username expected, type 'ntfy user add --help' for help")
 	} else if username == userEveryone {
 	} else if username == userEveryone {
@@ -151,9 +168,13 @@ func execUserAdd(c *cli.Context) error {
 	if user, _ := manager.User(username); user != nil {
 	if user, _ := manager.User(username); user != nil {
 		return fmt.Errorf("user %s already exists", username)
 		return fmt.Errorf("user %s already exists", username)
 	}
 	}
-	password, err := readPasswordAndConfirm(c)
-	if err != nil {
-		return err
+	if password == "" {
+		p, err := readPasswordAndConfirm(c)
+		if err != nil {
+			return err
+		}
+
+		password = p
 	}
 	}
 	if err := manager.AddUser(username, password, role); err != nil {
 	if err := manager.AddUser(username, password, role); err != nil {
 		return err
 		return err
@@ -185,6 +206,7 @@ func execUserDel(c *cli.Context) error {
 
 
 func execUserChangePass(c *cli.Context) error {
 func execUserChangePass(c *cli.Context) error {
 	username := c.Args().Get(0)
 	username := c.Args().Get(0)
+	password := os.Getenv("NTFY_PASSWORD")
 	if username == "" {
 	if username == "" {
 		return errors.New("username expected, type 'ntfy user change-pass --help' for help")
 		return errors.New("username expected, type 'ntfy user change-pass --help' for help")
 	} else if username == userEveryone {
 	} else if username == userEveryone {
@@ -197,9 +219,11 @@ func execUserChangePass(c *cli.Context) error {
 	if _, err := manager.User(username); err == auth.ErrNotFound {
 	if _, err := manager.User(username); err == auth.ErrNotFound {
 		return fmt.Errorf("user %s does not exist", username)
 		return fmt.Errorf("user %s does not exist", username)
 	}
 	}
-	password, err := readPasswordAndConfirm(c)
-	if err != nil {
-		return err
+	if password == "" {
+		password, err = readPasswordAndConfirm(c)
+		if err != nil {
+			return err
+		}
 	}
 	}
 	if err := manager.ChangePassword(username, password); err != nil {
 	if err := manager.ChangePassword(username, password); err != nil {
 		return err
 		return err

+ 3 - 0
docs/releases.md

@@ -9,11 +9,14 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
 **Features:**
 **Features:**
 
 
 * Trace: Log entire HTTP request to simplify debugging (no ticket)
 * Trace: Log entire HTTP request to simplify debugging (no ticket)
+* Allow setting user password via `NTFY_PASSWORD` env variable ([#327](https://github.com/binwiederhier/ntfy/pull/327), thanks to [@Kenix3](https://github.com/Kenix3))
 
 
 **Bugs:**
 **Bugs:**
 
 
 * Return HTTP 500 for GET /_matrix/push/v1/notify when base-url is not configured (no ticket)
 * Return HTTP 500 for GET /_matrix/push/v1/notify when base-url is not configured (no ticket)
 * Disallow setting `upstream-base-url` to the same value as `base-url` ([#334](https://github.com/binwiederhier/ntfy/issues/334), thanks to [@oester](https://github.com/oester) for reporting)
 * Disallow setting `upstream-base-url` to the same value as `base-url` ([#334](https://github.com/binwiederhier/ntfy/issues/334), thanks to [@oester](https://github.com/oester) for reporting)
+* Fix `since=<id>` implementation for multiple topics ([#336](https://github.com/binwiederhier/ntfy/issues/336), thanks to [@karmanyaahm](https://github.com/karmanyaahm) for reporting)
+
 
 
 ## ntfy Android app v1.14.0 (UNRELEASED)
 ## ntfy Android app v1.14.0 (UNRELEASED)
 
 

+ 2 - 2
server/message_cache.go

@@ -49,7 +49,7 @@ const (
 		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 	`
 	`
 	pruneMessagesQuery           = `DELETE FROM messages WHERE time < ? AND published = 1`
 	pruneMessagesQuery           = `DELETE FROM messages WHERE time < ? AND published = 1`
-	selectRowIDFromMessageID     = `SELECT id FROM messages WHERE topic = ? AND mid = ?`
+	selectRowIDFromMessageID     = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
 	selectMessagesSinceTimeQuery = `
 	selectMessagesSinceTimeQuery = `
 		SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
 		SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
 		FROM messages 
 		FROM messages 
@@ -294,7 +294,7 @@ func (c *messageCache) messagesSinceTime(topic string, since sinceMarker, schedu
 }
 }
 
 
 func (c *messageCache) messagesSinceID(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
 func (c *messageCache) messagesSinceID(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
-	idrows, err := c.db.Query(selectRowIDFromMessageID, topic, since.ID())
+	idrows, err := c.db.Query(selectRowIDFromMessageID, since.ID())
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 13 - 5
server/server.go

@@ -16,6 +16,7 @@ import (
 	"path"
 	"path"
 	"path/filepath"
 	"path/filepath"
 	"regexp"
 	"regexp"
+	"sort"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -972,19 +973,26 @@ func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, schedu
 	return
 	return
 }
 }
 
 
+// sendOldMessages selects old messages from the messageCache and calls sub for each of them. It uses since as the
+// marker, returning only messages that are newer than the marker.
 func (s *Server) sendOldMessages(topics []*topic, since sinceMarker, scheduled bool, v *visitor, sub subscriber) error {
 func (s *Server) sendOldMessages(topics []*topic, since sinceMarker, scheduled bool, v *visitor, sub subscriber) error {
 	if since.IsNone() {
 	if since.IsNone() {
 		return nil
 		return nil
 	}
 	}
+	messages := make([]*message, 0)
 	for _, t := range topics {
 	for _, t := range topics {
-		messages, err := s.messageCache.Messages(t.ID, since, scheduled)
+		topicMessages, err := s.messageCache.Messages(t.ID, since, scheduled)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		for _, m := range messages {
-			if err := sub(v, m); err != nil {
-				return err
-			}
+		messages = append(messages, topicMessages...)
+	}
+	sort.Slice(messages, func(i, j int) bool {
+		return messages[i].Time < messages[j].Time
+	})
+	for _, m := range messages {
+		if err := sub(v, m); err != nil {
+			return err
 		}
 		}
 	}
 	}
 	return nil
 	return nil

+ 47 - 0
server/server_test.go

@@ -437,6 +437,53 @@ func TestServer_PublishAndPollSince(t *testing.T) {
 	require.Equal(t, 40008, toHTTPError(t, response.Body.String()).Code)
 	require.Equal(t, 40008, toHTTPError(t, response.Body.String()).Code)
 }
 }
 
 
+func newMessageWithTimestamp(topic, message string, timestamp int64) *message {
+	m := newDefaultMessage(topic, message)
+	m.Time = timestamp
+	return m
+}
+
+func TestServer_PollSinceID_MultipleTopics(t *testing.T) {
+	s := newTestServer(t, newTestConfig(t))
+
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic1", "test 1", 1655740277)))
+	markerMessage := newMessageWithTimestamp("mytopic2", "test 2", 1655740283)
+	require.Nil(t, s.messageCache.AddMessage(markerMessage))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic1", "test 3", 1655740289)))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic2", "test 4", 1655740293)))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic1", "test 5", 1655740297)))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic2", "test 6", 1655740303)))
+
+	response := request(t, s, "GET", fmt.Sprintf("/mytopic1,mytopic2/json?poll=1&since=%s", markerMessage.ID), "", nil)
+	messages := toMessages(t, response.Body.String())
+	require.Equal(t, 4, len(messages))
+	require.Equal(t, "test 3", messages[0].Message)
+	require.Equal(t, "mytopic1", messages[0].Topic)
+	require.Equal(t, "test 4", messages[1].Message)
+	require.Equal(t, "mytopic2", messages[1].Topic)
+	require.Equal(t, "test 5", messages[2].Message)
+	require.Equal(t, "mytopic1", messages[2].Topic)
+	require.Equal(t, "test 6", messages[3].Message)
+	require.Equal(t, "mytopic2", messages[3].Topic)
+}
+
+func TestServer_PollSinceID_MultipleTopics_IDDoesNotMatch(t *testing.T) {
+	s := newTestServer(t, newTestConfig(t))
+
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic1", "test 3", 1655740289)))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic2", "test 4", 1655740293)))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic1", "test 5", 1655740297)))
+	require.Nil(t, s.messageCache.AddMessage(newMessageWithTimestamp("mytopic2", "test 6", 1655740303)))
+
+	response := request(t, s, "GET", "/mytopic1,mytopic2/json?poll=1&since=NoMatchForID", "", nil)
+	messages := toMessages(t, response.Body.String())
+	require.Equal(t, 4, len(messages))
+	require.Equal(t, "test 3", messages[0].Message)
+	require.Equal(t, "test 4", messages[1].Message)
+	require.Equal(t, "test 5", messages[2].Message)
+	require.Equal(t, "test 6", messages[3].Message)
+}
+
 func TestServer_PublishViaGET(t *testing.T) {
 func TestServer_PublishViaGET(t *testing.T) {
 	s := newTestServer(t, newTestConfig(t))
 	s := newTestServer(t, newTestConfig(t))