Merge branch 'main' into done
commit
c40338c146
60
cmd/user.go
60
cmd/user.go
|
@ -6,11 +6,13 @@ import (
|
|||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"heckel.io/ntfy/auth"
|
||||
"heckel.io/ntfy/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -36,7 +38,7 @@ 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"},
|
||||
|
@ -48,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.
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -68,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.
|
||||
|
||||
|
@ -76,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.
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -125,18 +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 := os.Getenv("NTFY_PASSWORD")
|
||||
if username == "" {
|
||||
return errors.New("username expected, type 'ntfy user add --help' for help")
|
||||
} else if username == userEveryone {
|
||||
|
@ -151,9 +168,13 @@ 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 password == "" {
|
||||
p, err := readPasswordAndConfirm(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password = p
|
||||
}
|
||||
if err := manager.AddUser(username, password, role); err != nil {
|
||||
return err
|
||||
|
@ -185,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 {
|
||||
|
@ -197,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
|
||||
|
|
|
@ -9,11 +9,14 @@ 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:**
|
||||
|
||||
* 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=<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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
Loading…
Reference in New Issue