From 0c2cc6b0814e23c6f2bd21aab0b5602a53a899f5 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Tue, 4 Aug 2026 14:51:34 +0200 Subject: [PATCH] Fix ntfy bearer token usage --- README.md | 5 ++--- backend/ntfy.go | 22 +++++++--------------- compose.dev.yml | 1 - compose.yml | 1 - 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 85ca6d3..4d6c5da 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ A web app that visualizes DMR repeaters on an interactive map. Filter by band an | [BrandMeister WebSocket](https://wiki.brandmeister.network/index.php/API/Last_Heard) | Real-time Last Heard feed for heatmap via socket.io | None | | [Nominatim](https://nominatim.openstreetmap.org) | Address geocoding and autocomplete (client-side) | None | | [OSRM](https://project-osrm.org) | Driving route calculation (client-side) | None | -| [ntfy](https://ntfy.sh) | Push notification when a user report is submitted (optional) | Access token (bearer or basic auth) | +| [ntfy](https://ntfy.sh) | Push notification when a user report is submitted (optional) | Access token (bearer) | The BrandMeister talk group registry is fetched once at Docker build time and bundled as `frontend/static/talkgroups.json`. It is used for talk group name resolution and autocomplete in CPS Studio. @@ -79,8 +79,7 @@ docker compose -f compose.test.yml run --rm test | `ADMIN_TOKEN` | *(disabled)* | Set to enable the admin interface at `/admin/` | | `NTFY_URL` | *(disabled)* | ntfy server base URL (e.g. `https://ntfy.example.com`). Together with `NTFY_TOPIC` it enables push notifications for new user reports | | `NTFY_TOPIC` | *(disabled)* | ntfy topic that report notifications are published to | -| `NTFY_USER` | *(none)* | ntfy username. When set, the token is sent as HTTP basic auth (`user:token`) | -| `NTFY_TOKEN` | *(none)* | ntfy access token. Sent as `Authorization: Bearer` when no username is set | +| `NTFY_TOKEN` | *(none)* | ntfy access token, sent as `Authorization: Bearer`. Required for protected topics | | `PUBLIC_BASE_URL` | *(none)* | Public base URL of the site (e.g. `https://dmrmap.de`). Used to add a click-through link to the admin view in ntfy notifications | Both `NTFY_URL` and `NTFY_TOPIC` must be set for notifications; otherwise reports are stored but nothing is pushed. On startup the app logs either `ntfy notifications enabled: (auth: ...)` or `ntfy notifications disabled`. The **Test push** button in the admin Reports view publishes a test notification and shows the upstream error if it fails. diff --git a/backend/ntfy.go b/backend/ntfy.go index d4e08c3..cd64f21 100644 --- a/backend/ntfy.go +++ b/backend/ntfy.go @@ -1,7 +1,6 @@ package main import ( - "encoding/base64" "fmt" "log" "net/http" @@ -14,14 +13,13 @@ import ( // required environment variables are not set, in which case notify is a no-op. type ntfyNotifier struct { url string // full topic URL, e.g. https://ntfy.example.com/dmrmap - user string token string clickBase string httpClient *http.Client } // newNtfyNotifierFromEnv builds a notifier from NTFY_URL / NTFY_TOPIC / -// NTFY_USER / NTFY_TOKEN. Returns nil when notifications are not configured. +// NTFY_TOKEN. Returns nil when notifications are not configured. func newNtfyNotifierFromEnv() *ntfyNotifier { base := strings.TrimSpace(os.Getenv("NTFY_URL")) topic := strings.TrimSpace(os.Getenv("NTFY_TOPIC")) @@ -32,17 +30,14 @@ func newNtfyNotifierFromEnv() *ntfyNotifier { n := &ntfyNotifier{ url: strings.TrimRight(base, "/") + "/" + strings.TrimLeft(topic, "/"), - user: strings.TrimSpace(os.Getenv("NTFY_USER")), token: strings.TrimSpace(os.Getenv("NTFY_TOKEN")), clickBase: strings.TrimRight(strings.TrimSpace(os.Getenv("PUBLIC_BASE_URL")), "/"), httpClient: &http.Client{Timeout: 10 * time.Second}, } auth := "none" - if n.user != "" && n.token != "" { - auth = "basic (" + n.user + ")" - } else if n.token != "" { - auth = "bearer token" + if n.token != "" { + auth = "access token" } log.Printf("ntfy notifications enabled: %s (auth: %s)", n.url, auth) @@ -110,14 +105,11 @@ func (n *ntfyNotifier) send(rep UserReport) error { return nil } -// setAuth uses basic auth when a username is configured (ntfy accepts an -// access token as the password), and bearer auth for a token on its own. +// setAuth applies the ntfy access token. It is a bearer credential: ntfy does +// NOT accept it as the password half of a username:password pair (that yields +// HTTP 401), only as a bearer token or as basic auth with an empty username. func (n *ntfyNotifier) setAuth(req *http.Request) { - switch { - case n.user != "" && n.token != "": - creds := base64.StdEncoding.EncodeToString([]byte(n.user + ":" + n.token)) - req.Header.Set("Authorization", "Basic "+creds) - case n.token != "": + if n.token != "" { req.Header.Set("Authorization", "Bearer "+n.token) } } diff --git a/compose.dev.yml b/compose.dev.yml index 5d252e2..a90d2e6 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -33,7 +33,6 @@ services: - MIGRATIONS_DIR=/app/backend/migrations - NTFY_URL=${NTFY_URL:-} - NTFY_TOPIC=${NTFY_TOPIC:-} - - NTFY_USER=${NTFY_USER:-} - NTFY_TOKEN=${NTFY_TOKEN:-} - PUBLIC_BASE_URL=${PUBLIC_BASE_URL:-http://localhost:8080} diff --git a/compose.yml b/compose.yml index 3d29453..90da62e 100644 --- a/compose.yml +++ b/compose.yml @@ -22,7 +22,6 @@ services: - ADMIN_TOKEN=${ADMIN_TOKEN:-} - NTFY_URL=${NTFY_URL:-} - NTFY_TOPIC=${NTFY_TOPIC:-} - - NTFY_USER=${NTFY_USER:-} - NTFY_TOKEN=${NTFY_TOKEN:-} - PUBLIC_BASE_URL=${PUBLIC_BASE_URL:-} restart: unless-stopped