ntfy/server/server_web_push_test.go

228 lines
6.9 KiB
Go
Raw Normal View History

2023-05-29 17:57:21 +02:00
package server
import (
"encoding/json"
"fmt"
2023-05-29 17:57:21 +02:00
"io"
"net/http"
"net/http/httptest"
2023-05-30 20:23:03 +02:00
"strings"
2023-05-29 17:57:21 +02:00
"sync/atomic"
"testing"
"github.com/SherClockHolmes/webpush-go"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
)
func TestServer_WebPush_TopicAdd(t *testing.T) {
2023-05-29 17:57:21 +02:00
s := newTestServer(t, newTestConfigWithWebPush(t))
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), nil)
2023-05-29 17:57:21 +02:00
require.Equal(t, 200, response.Code)
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
2023-05-30 20:23:03 +02:00
subs, err := s.webPush.SubscriptionsForTopic("test-topic")
require.Nil(t, err)
2023-05-29 17:57:21 +02:00
require.Len(t, subs, 1)
require.Equal(t, subs[0].BrowserSubscription.Endpoint, "https://example.com/webpush")
require.Equal(t, subs[0].BrowserSubscription.Keys.P256dh, "p256dh-key")
require.Equal(t, subs[0].BrowserSubscription.Keys.Auth, "auth-key")
2023-05-30 20:23:03 +02:00
require.Equal(t, subs[0].UserID, "")
2023-05-29 17:57:21 +02:00
}
func TestServer_WebPush_TopicUnsubscribe(t *testing.T) {
s := newTestServer(t, newTestConfigWithWebPush(t))
addSubscription(t, s, "test-topic", "https://example.com/webpush")
requireSubscriptionCount(t, s, "test-topic", 1)
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{}), nil)
require.Equal(t, 200, response.Code)
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
requireSubscriptionCount(t, s, "test-topic", 0)
}
2023-05-29 17:57:21 +02:00
func TestServer_WebPush_TopicSubscribeProtected_Allowed(t *testing.T) {
config := configureAuth(t, newTestConfigWithWebPush(t))
config.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, config)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), map[string]string{
2023-05-29 17:57:21 +02:00
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, response.Code)
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
2023-05-30 20:23:03 +02:00
subs, err := s.webPush.SubscriptionsForTopic("test-topic")
require.Nil(t, err)
2023-05-29 17:57:21 +02:00
require.Len(t, subs, 1)
2023-05-30 20:23:03 +02:00
require.True(t, strings.HasPrefix(subs[0].UserID, "u_"))
2023-05-29 17:57:21 +02:00
}
func TestServer_WebPush_TopicSubscribeProtected_Denied(t *testing.T) {
config := configureAuth(t, newTestConfigWithWebPush(t))
config.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, config)
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), nil)
2023-05-29 17:57:21 +02:00
require.Equal(t, 403, response.Code)
requireSubscriptionCount(t, s, "test-topic", 0)
}
func TestServer_WebPush_DeleteAccountUnsubscribe(t *testing.T) {
config := configureAuth(t, newTestConfigWithWebPush(t))
s := newTestServer(t, config)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), map[string]string{
2023-05-29 17:57:21 +02:00
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, response.Code)
require.Equal(t, `{"success":true}`+"\n", response.Body.String())
requireSubscriptionCount(t, s, "test-topic", 1)
request(t, s, "DELETE", "/v1/account", `{"password":"ben"}`, map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
// should've been deleted with the account
requireSubscriptionCount(t, s, "test-topic", 0)
}
func TestServer_WebPush_Publish(t *testing.T) {
s := newTestServer(t, newTestConfigWithWebPush(t))
var received atomic.Bool
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := io.ReadAll(r.Body)
require.Nil(t, err)
require.Equal(t, "/push-receive", r.URL.Path)
require.Equal(t, "high", r.Header.Get("Urgency"))
require.Equal(t, "", r.Header.Get("Topic"))
received.Store(true)
}))
defer upstreamServer.Close()
addSubscription(t, s, "test-topic", upstreamServer.URL+"/push-receive")
request(t, s, "PUT", "/test-topic", "web push test", nil)
waitFor(t, func() bool {
return received.Load()
})
}
func TestServer_WebPush_PublishExpire(t *testing.T) {
s := newTestServer(t, newTestConfigWithWebPush(t))
var received atomic.Bool
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := io.ReadAll(r.Body)
require.Nil(t, err)
// Gone
w.WriteHeader(410)
w.Write([]byte(``))
received.Store(true)
}))
defer upstreamServer.Close()
addSubscription(t, s, "test-topic", upstreamServer.URL+"/push-receive")
addSubscription(t, s, "test-topic-abc", upstreamServer.URL+"/push-receive")
requireSubscriptionCount(t, s, "test-topic", 1)
requireSubscriptionCount(t, s, "test-topic-abc", 1)
request(t, s, "PUT", "/test-topic", "web push test", nil)
waitFor(t, func() bool {
return received.Load()
})
// Receiving the 410 should've caused the publisher to expire all subscriptions on the endpoint
requireSubscriptionCount(t, s, "test-topic", 0)
requireSubscriptionCount(t, s, "test-topic-abc", 0)
}
2023-06-02 14:45:05 +02:00
func TestServer_WebPush_Expiry(t *testing.T) {
s := newTestServer(t, newTestConfigWithWebPush(t))
var received atomic.Bool
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := io.ReadAll(r.Body)
require.Nil(t, err)
w.WriteHeader(200)
w.Write([]byte(``))
received.Store(true)
}))
defer upstreamServer.Close()
addSubscription(t, s, "test-topic", upstreamServer.URL+"/push-receive")
requireSubscriptionCount(t, s, "test-topic", 1)
_, err := s.webPush.db.Exec("UPDATE subscriptions SET updated_at = datetime('now', '-7 days')")
require.Nil(t, err)
s.expireOrNotifyOldSubscriptions()
requireSubscriptionCount(t, s, "test-topic", 1)
waitFor(t, func() bool {
return received.Load()
})
_, err = s.webPush.db.Exec("UPDATE subscriptions SET updated_at = datetime('now', '-8 days')")
require.Nil(t, err)
s.expireOrNotifyOldSubscriptions()
requireSubscriptionCount(t, s, "test-topic", 0)
}
func payloadForTopics(t *testing.T, topics []string) string {
2023-06-02 14:45:05 +02:00
topicsJSON, err := json.Marshal(topics)
require.Nil(t, err)
return fmt.Sprintf(`{
"topics": %s,
"browser_subscription":{
"endpoint": "https://example.com/webpush",
"keys": {
"p256dh": "p256dh-key",
"auth": "auth-key"
}
}
2023-06-02 14:45:05 +02:00
}`, topicsJSON)
}
2023-05-29 17:57:21 +02:00
func addSubscription(t *testing.T, s *Server, topic string, url string) {
err := s.webPush.AddSubscription(topic, "", webpush.Subscription{
Endpoint: url,
Keys: webpush.Keys{
// connected to a local test VAPID key, not a leak!
Auth: "kSC3T8aN1JCQxxPdrFLrZg",
P256dh: "BMKKbxdUU_xLS7G1Wh5AN8PvWOjCzkCuKZYb8apcqYrDxjOF_2piggBnoJLQYx9IeSD70fNuwawI3e9Y8m3S3PE",
2023-05-29 17:57:21 +02:00
},
})
require.Nil(t, err)
2023-05-29 17:57:21 +02:00
}
func requireSubscriptionCount(t *testing.T, s *Server, topic string, expectedLength int) {
2023-05-30 20:23:03 +02:00
subs, err := s.webPush.SubscriptionsForTopic("test-topic")
require.Nil(t, err)
2023-05-29 17:57:21 +02:00
require.Len(t, subs, expectedLength)
}