diff --git a/client/client.yml b/client/client.yml index 1b81b80d..ebf4c281 100644 --- a/client/client.yml +++ b/client/client.yml @@ -7,7 +7,10 @@ # Default credentials will be used with "ntfy publish" and "ntfy subscribe" if no other credentials are provided. # You can set a default token to use or a default user:password combination, but not both. For an empty password, -# use empty double-quotes ("") +# use empty double-quotes (""). +# +# To override the default user:password combination or default token for a particular subscription (e.g., to send +# no Authorization header), set the user:pass/token for the subscription to empty double-quotes (""). # default-token: diff --git a/client/config.go b/client/config.go index d4337d47..bc46ab89 100644 --- a/client/config.go +++ b/client/config.go @@ -23,9 +23,9 @@ type Config struct { // Subscribe is the struct for a Subscription within Config type Subscribe struct { Topic string `yaml:"topic"` - User string `yaml:"user"` + User *string `yaml:"user"` Password *string `yaml:"password"` - Token string `yaml:"token"` + Token *string `yaml:"token"` Command string `yaml:"command"` If map[string]string `yaml:"if"` } diff --git a/client/config_test.go b/client/config_test.go index f22e6b20..c85d3d49 100644 --- a/client/config_test.go +++ b/client/config_test.go @@ -37,7 +37,7 @@ subscribe: require.Equal(t, 4, len(conf.Subscribe)) require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic) require.Equal(t, "", conf.Subscribe[0].Command) - require.Equal(t, "phil", conf.Subscribe[0].User) + require.Equal(t, "phil", *conf.Subscribe[0].User) require.Equal(t, "mypass", *conf.Subscribe[0].Password) require.Equal(t, "echo-this", conf.Subscribe[1].Topic) require.Equal(t, `echo "Message received: $message"`, conf.Subscribe[1].Command) @@ -67,7 +67,7 @@ subscribe: require.Equal(t, 1, len(conf.Subscribe)) require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic) require.Equal(t, "", conf.Subscribe[0].Command) - require.Equal(t, "phil", conf.Subscribe[0].User) + require.Equal(t, "phil", *conf.Subscribe[0].User) require.Equal(t, "", *conf.Subscribe[0].Password) } @@ -91,7 +91,7 @@ subscribe: require.Equal(t, 1, len(conf.Subscribe)) require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic) require.Equal(t, "", conf.Subscribe[0].Command) - require.Equal(t, "phil", conf.Subscribe[0].User) + require.Equal(t, "phil", *conf.Subscribe[0].User) require.Nil(t, conf.Subscribe[0].Password) } @@ -113,7 +113,7 @@ subscribe: require.Equal(t, 1, len(conf.Subscribe)) require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic) require.Equal(t, "", conf.Subscribe[0].Command) - require.Equal(t, "phil", conf.Subscribe[0].User) + require.Equal(t, "phil", *conf.Subscribe[0].User) require.Nil(t, conf.Subscribe[0].Password) } @@ -134,7 +134,7 @@ subscribe: require.Equal(t, "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", conf.DefaultToken) require.Equal(t, 1, len(conf.Subscribe)) require.Equal(t, "mytopic", conf.Subscribe[0].Topic) - require.Equal(t, "", conf.Subscribe[0].User) + require.Nil(t, conf.Subscribe[0].User) require.Nil(t, conf.Subscribe[0].Password) - require.Equal(t, "", conf.Subscribe[0].Token) + require.Nil(t, conf.Subscribe[0].Token) } diff --git a/client/options.go b/client/options.go index 7f6232f8..630f1554 100644 --- a/client/options.go +++ b/client/options.go @@ -97,6 +97,11 @@ func WithBearerAuth(token string) PublishOption { return WithHeader("Authorization", fmt.Sprintf("Bearer %s", token)) } +// WithEmptyAuth clears the Authorization header +func WithEmptyAuth() PublishOption { + return RemoveHeader("Authorization") +} + // WithNoCache instructs the server not to cache the message server-side func WithNoCache() PublishOption { return WithHeader("X-Cache", "no") @@ -187,3 +192,13 @@ func WithQueryParam(param, value string) RequestOption { return nil } } + +// RemoveHeader is a generic option to remove a header from a request +func RemoveHeader(header string) RequestOption { + return func(r *http.Request) error { + if header != "" { + delete(r.Header, header) + } + return nil + } +} diff --git a/cmd/subscribe.go b/cmd/subscribe.go index c85c4686..77a1b5f1 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -225,12 +225,17 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, } func maybeAddAuthHeader(s client.Subscribe, conf *client.Config) client.SubscribeOption { - // check for subscription token then subscription user:pass - if s.Token != "" { - return client.WithBearerAuth(s.Token) + // if an explicit empty token or empty user:pass is given, exit without auth + if (s.Token != nil && *s.Token == "") || (s.User != nil && *s.User == "" && s.Password != nil && *s.Password == "") { + return client.WithEmptyAuth() } - if s.User != "" && s.Password != nil { - return client.WithBasicAuth(s.User, *s.Password) + + // check for subscription token then subscription user:pass + if s.Token != nil && *s.Token != "" { + return client.WithBearerAuth(*s.Token) + } + if s.User != nil && *s.User != "" && s.Password != nil { + return client.WithBasicAuth(*s.User, *s.Password) } // if no subscription token nor subscription user:pass, check for default token then default user:pass diff --git a/cmd/subscribe_test.go b/cmd/subscribe_test.go index 0b3a0a47..08dbbf5d 100644 --- a/cmd/subscribe_test.go +++ b/cmd/subscribe_test.go @@ -330,7 +330,7 @@ default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"})) + require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "mytopic"})) require.Equal(t, message, strings.TrimSpace(stdout.String())) } @@ -355,7 +355,63 @@ default-password: mypass app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"})) + require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "mytopic"})) + + require.Equal(t, message, strings.TrimSpace(stdout.String())) +} + +func TestCLI_Subscribe_Override_Default_UserPass_With_Empty_UserPass(t *testing.T) { + message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/mytopic/json", r.URL.Path) + require.Equal(t, "", r.Header.Get("Authorization")) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(message)) + })) + defer server.Close() + + filename := filepath.Join(t.TempDir(), "client.yml") + require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` +default-host: %s +default-user: philipp +default-password: mypass +subscribe: + - topic: mytopic + user: "" + password: "" +`, server.URL)), 0600)) + + app, _, stdout, _ := newTestApp() + + require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) + + require.Equal(t, message, strings.TrimSpace(stdout.String())) +} + +func TestCLI_Subscribe_Override_Default_Token_With_Empty_Token(t *testing.T) { + message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/mytopic/json", r.URL.Path) + require.Equal(t, "", r.Header.Get("Authorization")) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(message)) + })) + defer server.Close() + + filename := filepath.Join(t.TempDir(), "client.yml") + require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` +default-host: %s +default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 +subscribe: + - topic: mytopic + token: "" +`, server.URL)), 0600)) + + app, _, stdout, _ := newTestApp() + + require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) require.Equal(t, message, strings.TrimSpace(stdout.String())) } diff --git a/docs/releases.md b/docs/releases.md index 6fc0f510..a2ee1586 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1262,6 +1262,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release * Fix issues with date/time with different locales ([#700](https://github.com/binwiederhier/ntfy/issues/700), thanks to [@nimbleghost](https://github.com/nimbleghost)) * Re-init i18n on each service worker message to avoid missing translations ([#817](https://github.com/binwiederhier/ntfy/pull/817), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves)) +* You can now unset the default user:pass/token in `client.yml` for an individual subscription to remove the Authorization header ([#829](https://github.com/binwiederhier/ntfy/issues/829), thanks to [tomeon](https://github.com/tomeon) for reporting and to [@wunter8](https://github.com/wunter8) for fixing) ### ntfy Android app v1.16.1 (UNRELEASED)