From 3dec7efadb418bc065f6aa856e7196cd5a12b65d Mon Sep 17 00:00:00 2001 From: Kenix Date: Wed, 15 Jun 2022 11:42:22 -0400 Subject: [PATCH 1/8] Add user now supports reading password from an env var. --- cmd/user.go | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/cmd/user.go b/cmd/user.go index acc06d4c..5a5b1f9c 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -6,11 +6,12 @@ import ( "crypto/subtle" "errors" "fmt" + "strings" + "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/altsrc" "heckel.io/ntfy/auth" "heckel.io/ntfy/util" - "strings" ) func init() { @@ -40,6 +41,7 @@ var cmdUser = &cli.Command{ Action: execUserAdd, Flags: []cli.Flag{ &cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"}, + &cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"}, }, Description: `Add a new user to the ntfy user database. @@ -135,14 +137,38 @@ Examples: } func execUserAdd(c *cli.Context) error { - username := c.Args().Get(0) + var username string + var password string + userAndPass := c.String("user") role := auth.Role(c.String("role")) - if username == "" { - return errors.New("username expected, type 'ntfy user add --help' for help") - } else if username == userEveryone { - return errors.New("username not allowed") - } else if !auth.AllowedRole(role) { - return errors.New("role must be either 'user' or 'admin'") + if userAndPass != "" { + parts := strings.SplitN(userAndPass, ":", 2) + if len(parts) == 2 { + username = parts[0] + password = parts[1] + } else { + p, err := readPasswordAndConfirm(c) + if err != nil { + return err + } + username = userAndPass + password = p + } + } else { + username = c.Args().Get(0) + if username == "" { + return errors.New("username expected, type 'ntfy user add --help' for help") + } else if username == userEveryone { + return errors.New("username not allowed") + } else if !auth.AllowedRole(role) { + return errors.New("role must be either 'user' or 'admin'") + } + + p, err := readPasswordAndConfirm(c) + if err != nil { + return err + } + password = p } manager, err := createAuthManager(c) if err != nil { @@ -151,10 +177,6 @@ func execUserAdd(c *cli.Context) error { if user, _ := manager.User(username); user != nil { return fmt.Errorf("user %s already exists", username) } - password, err := readPasswordAndConfirm(c) - if err != nil { - return err - } if err := manager.AddUser(username, password, role); err != nil { return err } From d05211648d4cd7115dc9be1b733102f30bddf675 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Mon, 20 Jun 2022 12:11:52 -0400 Subject: [PATCH 2/8] Fix `since=` implementation for multiple topics, closes #336 --- docs/releases.md | 1 + server/message_cache.go | 4 ++-- server/server.go | 18 +++++++++++----- server/server_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/docs/releases.md b/docs/releases.md index 3f5a7b6c..935f5950 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -14,6 +14,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release * 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) +* Fix `since=` 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) diff --git a/server/message_cache.go b/server/message_cache.go index 77aa4f78..afd4bf17 100644 --- a/server/message_cache.go +++ b/server/message_cache.go @@ -49,7 +49,7 @@ const ( VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` 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 = ` SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding 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) { - idrows, err := c.db.Query(selectRowIDFromMessageID, topic, since.ID()) + idrows, err := c.db.Query(selectRowIDFromMessageID, since.ID()) if err != nil { return nil, err } diff --git a/server/server.go b/server/server.go index dacfa743..4d028d91 100644 --- a/server/server.go +++ b/server/server.go @@ -16,6 +16,7 @@ import ( "path" "path/filepath" "regexp" + "sort" "strconv" "strings" "sync" @@ -972,19 +973,26 @@ func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, schedu 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 { if since.IsNone() { return nil } + messages := make([]*message, 0) 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 { 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 diff --git a/server/server_test.go b/server/server_test.go index 66ad6e1b..9fc9aa88 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -437,6 +437,53 @@ func TestServer_PublishAndPollSince(t *testing.T) { 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) { s := newTestServer(t, newTestConfig(t)) From 1265e69eee40818e96a89fe4d5011ffb442cb089 Mon Sep 17 00:00:00 2001 From: Kenix Date: Mon, 20 Jun 2022 13:19:54 -0400 Subject: [PATCH 3/8] Changes user add to use a NTFY_PASSWORD env var rather than NTFY_USER. --- cmd/user.go | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/cmd/user.go b/cmd/user.go index 5a5b1f9c..08605198 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -41,7 +41,7 @@ var cmdUser = &cli.Command{ Action: execUserAdd, Flags: []cli.Flag{ &cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"}, - &cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"}, + &cli.StringFlag{Name: "password", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PASSWORD"}, Usage: "user password"}, }, Description: `Add a new user to the ntfy user database. @@ -137,39 +137,27 @@ Examples: } func execUserAdd(c *cli.Context) error { - var username string - var password string - userAndPass := c.String("user") + password := c.String("user") role := auth.Role(c.String("role")) - if userAndPass != "" { - parts := strings.SplitN(userAndPass, ":", 2) - if len(parts) == 2 { - username = parts[0] - password = parts[1] - } else { - p, err := readPasswordAndConfirm(c) - if err != nil { - return err - } - username = userAndPass - password = p - } - } else { - username = c.Args().Get(0) - if username == "" { - return errors.New("username expected, type 'ntfy user add --help' for help") - } else if username == userEveryone { - return errors.New("username not allowed") - } else if !auth.AllowedRole(role) { - return errors.New("role must be either 'user' or 'admin'") - } + username = c.Args().Get(0) + if username == "" { + return errors.New("username expected, type 'ntfy user add --help' for help") + } else if username == userEveryone { + return errors.New("username not allowed") + } else if !auth.AllowedRole(role) { + return errors.New("role must be either 'user' or 'admin'") + } + // If the password env var was not set, read it from stdin + if password == "" { p, err := readPasswordAndConfirm(c) if err != nil { return err } + password = p } + manager, err := createAuthManager(c) if err != nil { return err From 50cd50cfdf1857596cdb9d23ba703695966a0c84 Mon Sep 17 00:00:00 2001 From: Kenix Date: Mon, 20 Jun 2022 13:24:42 -0400 Subject: [PATCH 4/8] Moves password stdin down to the original location. --- cmd/user.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/user.go b/cmd/user.go index 08605198..e9bc7b2e 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -147,7 +147,13 @@ func execUserAdd(c *cli.Context) error { } else if !auth.AllowedRole(role) { return errors.New("role must be either 'user' or 'admin'") } - + manager, err := createAuthManager(c) + if err != nil { + return err + } + if user, _ := manager.User(username); user != nil { + return fmt.Errorf("user %s already exists", username) + } // If the password env var was not set, read it from stdin if password == "" { p, err := readPasswordAndConfirm(c) @@ -157,14 +163,6 @@ func execUserAdd(c *cli.Context) error { password = p } - - manager, err := createAuthManager(c) - if err != nil { - return err - } - if user, _ := manager.User(username); user != nil { - return fmt.Errorf("user %s already exists", username) - } if err := manager.AddUser(username, password, role); err != nil { return err } From 727c6268b91a3deaf20c284a55701b9fedb312e6 Mon Sep 17 00:00:00 2001 From: Kenix Date: Mon, 20 Jun 2022 13:25:31 -0400 Subject: [PATCH 5/8] Updating order of variables ntfy user add command. --- cmd/user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/user.go b/cmd/user.go index e9bc7b2e..07970fde 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -137,9 +137,9 @@ Examples: } func execUserAdd(c *cli.Context) error { - password := c.String("user") - role := auth.Role(c.String("role")) username = c.Args().Get(0) + role := auth.Role(c.String("role")) + password := c.String("user") if username == "" { return errors.New("username expected, type 'ntfy user add --help' for help") } else if username == userEveryone { From 7de7e0de1246752d421c977a57147ea1f3f9e474 Mon Sep 17 00:00:00 2001 From: Kenix Date: Mon, 20 Jun 2022 13:26:13 -0400 Subject: [PATCH 6/8] Adds missing colon assignment for username variable in ntfy user add command. --- cmd/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/user.go b/cmd/user.go index 07970fde..d4d543fe 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -137,7 +137,7 @@ Examples: } func execUserAdd(c *cli.Context) error { - username = c.Args().Get(0) + username := c.Args().Get(0) role := auth.Role(c.String("role")) password := c.String("user") if username == "" { From f3e59618921e02ad35e046e2ac86e1561dd85564 Mon Sep 17 00:00:00 2001 From: Kenix3 Date: Mon, 20 Jun 2022 14:21:30 -0400 Subject: [PATCH 7/8] Fixes envvar fetch in ntfy user add for password --- cmd/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/user.go b/cmd/user.go index d4d543fe..f94f6250 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -139,7 +139,7 @@ Examples: func execUserAdd(c *cli.Context) error { username := c.Args().Get(0) role := auth.Role(c.String("role")) - password := c.String("user") + password := c.String("password") if username == "" { return errors.New("username expected, type 'ntfy user add --help' for help") } else if username == userEveryone { From a7d8e69dfd40536fc8abaa6125b290c6779bb3d6 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Mon, 20 Jun 2022 16:03:39 -0400 Subject: [PATCH 8/8] Refine NTFY_PASSWORD logic --- cmd/user.go | 50 ++++++++++++++++++++++++++++++++---------------- docs/releases.md | 2 ++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/cmd/user.go b/cmd/user.go index f94f6250..da88d459 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -6,6 +6,7 @@ import ( "crypto/subtle" "errors" "fmt" + "os" "strings" "github.com/urfave/cli/v2" @@ -37,11 +38,10 @@ var cmdUser = &cli.Command{ Name: "add", Aliases: []string{"a"}, 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, Flags: []cli.Flag{ &cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"}, - &cli.StringFlag{Name: "password", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PASSWORD"}, Usage: "user password"}, }, Description: `Add a new user to the ntfy user database. @@ -50,8 +50,12 @@ granted otherwise by the auth-default-access setting). An admin user has read an topics. 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. `, }, { @@ -70,7 +74,7 @@ Example: Name: "change-pass", Aliases: []string{"chp"}, 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, Description: `Change the password for the given user. @@ -78,7 +82,12 @@ The new password will be read from STDIN, and it'll be confirmed by typing it twice. 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,19 +136,24 @@ The command allows you to add/remove/change users in the ntfy user database, as passwords or roles. 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 { username := c.Args().Get(0) role := auth.Role(c.String("role")) - password := c.String("password") + password := os.Getenv("NTFY_PASSWORD") if username == "" { return errors.New("username expected, type 'ntfy user add --help' for help") } else if username == userEveryone { @@ -154,7 +168,6 @@ func execUserAdd(c *cli.Context) error { if user, _ := manager.User(username); user != nil { return fmt.Errorf("user %s already exists", username) } - // If the password env var was not set, read it from stdin if password == "" { p, err := readPasswordAndConfirm(c) if err != nil { @@ -193,6 +206,7 @@ func execUserDel(c *cli.Context) error { func execUserChangePass(c *cli.Context) error { username := c.Args().Get(0) + password := os.Getenv("NTFY_PASSWORD") if username == "" { return errors.New("username expected, type 'ntfy user change-pass --help' for help") } else if username == userEveryone { @@ -205,9 +219,11 @@ func execUserChangePass(c *cli.Context) error { if _, err := manager.User(username); err == auth.ErrNotFound { 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 { return err diff --git a/docs/releases.md b/docs/releases.md index 935f5950..63fff818 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -9,6 +9,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release **Features:** * 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:** @@ -16,6 +17,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release * 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=` 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) **Features:**