diff --git a/client/client.go b/client/client.go index 31991c06..eaff5673 100644 --- a/client/client.go +++ b/client/client.go @@ -18,9 +18,10 @@ import ( // Event type constants const ( - MessageEvent = "message" - KeepaliveEvent = "keepalive" - OpenEvent = "open" + MessageEvent = "message" + KeepaliveEvent = "keepalive" + OpenEvent = "open" + PollRequestEvent = "poll_request" ) const ( @@ -251,6 +252,13 @@ func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicUR return err } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + b, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes)) + if err != nil { + return err + } + return errors.New(strings.TrimSpace(string(b))) + } scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { m, err := toMessage(scanner.Text(), topicURL, subscriptionID) diff --git a/cmd/access_test.go b/cmd/access_test.go new file mode 100644 index 00000000..67159d43 --- /dev/null +++ b/cmd/access_test.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "fmt" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" + "heckel.io/ntfy/server" + "heckel.io/ntfy/test" + "testing" +) + +func TestCLI_Access_Show(t *testing.T) { + s, conf, port := newTestServerWithAuth(t) + defer test.StopServer(t, s, port) + + app, _, _, stderr := newTestApp() + require.Nil(t, runAccessCommand(app, conf)) + require.Contains(t, stderr.String(), "user * (anonymous)\n- no topic-specific permissions\n- no access to any (other) topics (server config)") +} + +func TestCLI_Access_Grant_And_Publish(t *testing.T) { + s, conf, port := newTestServerWithAuth(t) + defer test.StopServer(t, s, port) + + app, stdin, _, _ := newTestApp() + stdin.WriteString("philpass\nphilpass\nbenpass\nbenpass") + require.Nil(t, runUserCommand(app, conf, "add", "--role=admin", "phil")) + require.Nil(t, runUserCommand(app, conf, "add", "ben")) + require.Nil(t, runAccessCommand(app, conf, "ben", "announcements", "rw")) + require.Nil(t, runAccessCommand(app, conf, "ben", "sometopic", "read")) + require.Nil(t, runAccessCommand(app, conf, "everyone", "announcements", "read")) + + app, _, _, stderr := newTestApp() + require.Nil(t, runAccessCommand(app, conf)) + expected := `user phil (admin) +- read-write access to all topics (admin role) +user ben (user) +- read-write access to topic announcements +- read-only access to topic sometopic +user * (anonymous) +- read-only access to topic announcements +- no access to any (other) topics (server config) +` + require.Equal(t, expected, stderr.String()) + + // See if access permissions match + app, _, _, _ = newTestApp() + require.Error(t, app.Run([]string{ + "ntfy", + "publish", + fmt.Sprintf("http://127.0.0.1:%d/announcements", port), + })) + require.Nil(t, app.Run([]string{ + "ntfy", + "publish", + "-u", "ben:benpass", + fmt.Sprintf("http://127.0.0.1:%d/announcements", port), + })) + require.Nil(t, app.Run([]string{ + "ntfy", + "publish", + "-u", "phil:philpass", + fmt.Sprintf("http://127.0.0.1:%d/announcements", port), + })) + require.Nil(t, app.Run([]string{ + "ntfy", + "subscribe", + "--poll", + fmt.Sprintf("http://127.0.0.1:%d/announcements", port), + })) + require.Error(t, app.Run([]string{ + "ntfy", + "subscribe", + "--poll", + fmt.Sprintf("http://127.0.0.1:%d/something-else", port), + })) +} + +func runAccessCommand(app *cli.App, conf *server.Config, args ...string) error { + userArgs := []string{ + "ntfy", + "access", + "--auth-file=" + conf.AuthFile, + "--auth-default-access=" + confToDefaultAccess(conf), + } + return app.Run(append(userArgs, args...)) +} diff --git a/cmd/user_test.go b/cmd/user_test.go index 46a433d2..666cb422 100644 --- a/cmd/user_test.go +++ b/cmd/user_test.go @@ -121,6 +121,16 @@ func newTestServerWithAuth(t *testing.T) (s *server.Server, conf *server.Config, } func runUserCommand(app *cli.App, conf *server.Config, args ...string) error { + userArgs := []string{ + "ntfy", + "user", + "--auth-file=" + conf.AuthFile, + "--auth-default-access=" + confToDefaultAccess(conf), + } + return app.Run(append(userArgs, args...)) +} + +func confToDefaultAccess(conf *server.Config) string { var defaultAccess string if conf.AuthDefaultRead && conf.AuthDefaultWrite { defaultAccess = "read-write" @@ -131,11 +141,5 @@ func runUserCommand(app *cli.App, conf *server.Config, args ...string) error { } else if !conf.AuthDefaultRead && !conf.AuthDefaultWrite { defaultAccess = "deny-all" } - userArgs := []string{ - "ntfy", - "user", - "--auth-file=" + conf.AuthFile, - "--auth-default-access=" + defaultAccess, - } - return app.Run(append(userArgs, args...)) + return defaultAccess } diff --git a/server/errors.go b/server/errors.go index db475b8c..893178be 100644 --- a/server/errors.go +++ b/server/errors.go @@ -40,8 +40,8 @@ var ( errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", ""} errHTTPBadRequestWebSocketsUpgradeHeaderMissing = &errHTTP{40016, http.StatusBadRequest, "invalid request: client not using the websocket protocol", ""} errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""} - errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", ""} - errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", ""} + errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"} + errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"} errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"}