mirror of
				https://github.com/binwiederhier/ntfy.git
				synced 2025-10-31 13:02:24 +01:00 
			
		
		
		
	Merge branch 'main' into done
This commit is contained in:
		
						commit
						c40338c146
					
				
					 5 changed files with 107 additions and 25 deletions
				
			
		
							
								
								
									
										34
									
								
								cmd/user.go
									
										
									
									
									
								
							
							
						
						
									
										34
									
								
								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"}, | ||||||
|  | @ -50,6 +52,10 @@ topics. | ||||||
| Examples: | Examples: | ||||||
|   ntfy user add phil                     # Add regular user phil   |   ntfy user add phil                     # Add regular user phil   | ||||||
|   ntfy user add --role=admin phil        # Add admin 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. | ||||||
| 
 | 
 | ||||||
|  | @ -77,6 +83,11 @@ 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. | ||||||
|  | 
 | ||||||
| `, | `, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | @ -127,16 +138,22 @@ passwords or roles. | ||||||
| Examples: | Examples: | ||||||
|   ntfy user list                               # Shows list of users (alias: 'ntfy access')                       |   ntfy user list                               # Shows list of users (alias: 'ntfy access')                       | ||||||
|   ntfy user add phil                           # Add regular user phil   |   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 add --role=admin phil              # Add admin user phil | ||||||
|   ntfy user del phil                           # Delete user phil |   ntfy user del phil                           # Delete user phil | ||||||
|   ntfy user change-pass phil                   # Change password for 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  |   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,10 +168,14 @@ 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 password == "" { | ||||||
|  | 		p, err := readPasswordAndConfirm(c) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			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,10 +219,12 @@ 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 password == "" { | ||||||
|  | 		password, err = readPasswordAndConfirm(c) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	if err := manager.ChangePassword(username, password); err != nil { | 	if err := manager.ChangePassword(username, password); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ import ( | ||||||
| 	"path" | 	"path" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
|  | @ -972,21 +973,28 @@ 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 | ||||||
| 		} | 		} | ||||||
|  | 		messages = append(messages, topicMessages...) | ||||||
|  | 	} | ||||||
|  | 	sort.Slice(messages, func(i, j int) bool { | ||||||
|  | 		return messages[i].Time < messages[j].Time | ||||||
|  | 	}) | ||||||
| 	for _, m := range messages { | 	for _, m := range messages { | ||||||
| 		if err := sub(v, m); err != nil { | 		if err := sub(v, m); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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)) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Philipp Heckel
						Philipp Heckel