diff --git a/docs/examples.md b/docs/examples.md
index 0c892e64..59796372 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -572,4 +572,27 @@ Example `template.html`:
Add notification on Rundeck (attachment type must be: `Attached as file to email`):
![Rundeck](static/img/rundeck.png)
+## Traccar
+This will only work on selfhosted [traccar](https://www.traccar.org/) ([Github](https://github.com/traccar/traccar)) instances, as you need to be able to set `sms.http.*` keys, which is not possible through the UI attributes
+The easiest way to integrate traccar with ntfy, is to configure ntfy as the SMS provider for your instance. You then can set your ntfy topic as your account's phone number in traccar. Sending the email notifications to ntfy will not work, as ntfy does not support HTML emails.
+
+**Caution:** JSON publishing is only possible, when POST-ing to the root URL of the ntfy instance. (see [documentation](publish.md#publish-as-json))
+```xml
+ https://ntfy.sh
+
+ {
+ "topic": "{phone}",
+ "message": "{message}"
+ }
+
+```
+If [access control](config.md#access-control) is enabled, and the target topic does not support anonymous writes, you'll also have to provide an authorization header, for example in form of a privileged token
+```xml
+ Bearer tk_JhbsnoMrgy2FcfHeofv97Pi5uXaZZ
+```
+or by simply providing traccar with a valid username/password combination.
+```xml
+ phil
+ mypass
+```
diff --git a/docs/releases.md b/docs/releases.md
index 2354f8ee..f3275544 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -2,7 +2,11 @@
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
-## ntfy server v2.0.2 (UNRELEASED)
+## ntfy server v2.1.0 (UNRELEASED)
+
+This release now supports sending emails to protected topics, and it ships code to support annual billing cycles (not live yet).
+Most importantly, it fixes an issue with UnifiedPush (mostly Mastodon servers) that send an `Authorization` header, which ntfy
+rejects with an HTTP 401.
**Features:**
@@ -13,6 +17,11 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Bug fixes + maintenance:**
* Web: Do not disable "Reserve topic" checkbox for admins (no ticket, thanks to @xenrox for reporting)
+* UnifiedPush: Treat non-Basic/Bearer `Authorization` header like header was not sent ([#629](https://github.com/binwiederhier/ntfy/issues/629), thanks to [@Boebbele](https://github.com/Boebbele) and [@S1m](https://github.com/S1m) for reporting)
+
+**Documentation:**
+
+* Added example for [Traccar](https://ntfy.sh/docs/examples/#traccar) ([#631](https://github.com/binwiederhier/ntfy/pull/631), thanks to [tamcore](https://github.com/tamcore))
**Additional languages:**
diff --git a/server/server.go b/server/server.go
index 6ce60c03..57f084f6 100644
--- a/server/server.go
+++ b/server/server.go
@@ -1528,7 +1528,8 @@ func (s *Server) autorizeTopic(next handleFunc, perm user.Permission) handleFunc
// maybeAuthenticate reads the "Authorization" header and will try to authenticate the user
// if it is set.
//
-// - If the header is not set, an IP-based visitor is returned
+// - If the header is not set or not supported (anything non-Basic and non-Bearer),
+// an IP-based visitor is returned
// - If the header is set, authenticate will be called to check the username/password (Basic auth),
// or the token (Bearer auth), and read the user from the database
//
@@ -1541,7 +1542,7 @@ func (s *Server) maybeAuthenticate(r *http.Request) (*visitor, error) {
header, err := readAuthHeader(r)
if err != nil {
return vip, err
- } else if header == "" {
+ } else if !supportedAuthHeader(header) {
return vip, nil
} else if s.userManager == nil {
return vip, errHTTPUnauthorized
@@ -1586,6 +1587,14 @@ func readAuthHeader(r *http.Request) (string, error) {
return value, nil
}
+// supportedAuthHeader returns true only if the Authorization header value starts
+// with "Basic" or "Bearer". In particular, an empty value is not supported, and neither
+// are things like "WebPush", or "vapid" (see #629).
+func supportedAuthHeader(value string) bool {
+ value = strings.ToLower(value)
+ return strings.HasPrefix(value, "basic ") || strings.HasPrefix(value, "bearer ")
+}
+
func (s *Server) authenticateBasicAuth(r *http.Request, value string) (user *user.User, err error) {
r.Header.Set("Authorization", value)
username, password, ok := r.BasicAuth()
diff --git a/server/server_test.go b/server/server_test.go
index 09e711e7..97762545 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -796,6 +796,25 @@ func TestServer_Auth_ViaQuery(t *testing.T) {
require.Equal(t, 401, response.Code)
}
+func TestServer_Auth_NonBasicHeader(t *testing.T) {
+ s := newTestServer(t, newTestConfigWithAuthFile(t))
+
+ response := request(t, s, "PUT", "/mytopic", "test", map[string]string{
+ "Authorization": "WebPush not-supported",
+ })
+ require.Equal(t, 200, response.Code)
+
+ response = request(t, s, "PUT", "/mytopic", "test", map[string]string{
+ "Authorization": "Bearer supported",
+ })
+ require.Equal(t, 401, response.Code)
+
+ response = request(t, s, "PUT", "/mytopic", "test", map[string]string{
+ "Authorization": "basic supported",
+ })
+ require.Equal(t, 401, response.Code)
+}
+
func TestServer_StatsResetter(t *testing.T) {
// This tests the stats resetter for
// - an anonymous user