server_web_push_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package server
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "sync/atomic"
  10. "testing"
  11. "github.com/SherClockHolmes/webpush-go"
  12. "github.com/stretchr/testify/require"
  13. "heckel.io/ntfy/user"
  14. "heckel.io/ntfy/util"
  15. )
  16. func TestServer_WebPush_TopicAdd(t *testing.T) {
  17. s := newTestServer(t, newTestConfigWithWebPush(t))
  18. response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), nil)
  19. require.Equal(t, 200, response.Code)
  20. require.Equal(t, `{"success":true}`+"\n", response.Body.String())
  21. subs, err := s.webPush.SubscriptionsForTopic("test-topic")
  22. require.Nil(t, err)
  23. require.Len(t, subs, 1)
  24. require.Equal(t, subs[0].BrowserSubscription.Endpoint, "https://example.com/webpush")
  25. require.Equal(t, subs[0].BrowserSubscription.Keys.P256dh, "p256dh-key")
  26. require.Equal(t, subs[0].BrowserSubscription.Keys.Auth, "auth-key")
  27. require.Equal(t, subs[0].UserID, "")
  28. }
  29. func TestServer_WebPush_TopicUnsubscribe(t *testing.T) {
  30. s := newTestServer(t, newTestConfigWithWebPush(t))
  31. addSubscription(t, s, "test-topic", "https://example.com/webpush")
  32. requireSubscriptionCount(t, s, "test-topic", 1)
  33. response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{}), nil)
  34. require.Equal(t, 200, response.Code)
  35. require.Equal(t, `{"success":true}`+"\n", response.Body.String())
  36. requireSubscriptionCount(t, s, "test-topic", 0)
  37. }
  38. func TestServer_WebPush_TopicSubscribeProtected_Allowed(t *testing.T) {
  39. config := configureAuth(t, newTestConfigWithWebPush(t))
  40. config.AuthDefault = user.PermissionDenyAll
  41. s := newTestServer(t, config)
  42. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
  43. require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
  44. response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), map[string]string{
  45. "Authorization": util.BasicAuth("ben", "ben"),
  46. })
  47. require.Equal(t, 200, response.Code)
  48. require.Equal(t, `{"success":true}`+"\n", response.Body.String())
  49. subs, err := s.webPush.SubscriptionsForTopic("test-topic")
  50. require.Nil(t, err)
  51. require.Len(t, subs, 1)
  52. require.True(t, strings.HasPrefix(subs[0].UserID, "u_"))
  53. }
  54. func TestServer_WebPush_TopicSubscribeProtected_Denied(t *testing.T) {
  55. config := configureAuth(t, newTestConfigWithWebPush(t))
  56. config.AuthDefault = user.PermissionDenyAll
  57. s := newTestServer(t, config)
  58. response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), nil)
  59. require.Equal(t, 403, response.Code)
  60. requireSubscriptionCount(t, s, "test-topic", 0)
  61. }
  62. func TestServer_WebPush_DeleteAccountUnsubscribe(t *testing.T) {
  63. config := configureAuth(t, newTestConfigWithWebPush(t))
  64. s := newTestServer(t, config)
  65. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
  66. require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
  67. response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}), map[string]string{
  68. "Authorization": util.BasicAuth("ben", "ben"),
  69. })
  70. require.Equal(t, 200, response.Code)
  71. require.Equal(t, `{"success":true}`+"\n", response.Body.String())
  72. requireSubscriptionCount(t, s, "test-topic", 1)
  73. request(t, s, "DELETE", "/v1/account", `{"password":"ben"}`, map[string]string{
  74. "Authorization": util.BasicAuth("ben", "ben"),
  75. })
  76. // should've been deleted with the account
  77. requireSubscriptionCount(t, s, "test-topic", 0)
  78. }
  79. func TestServer_WebPush_Publish(t *testing.T) {
  80. s := newTestServer(t, newTestConfigWithWebPush(t))
  81. var received atomic.Bool
  82. upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  83. _, err := io.ReadAll(r.Body)
  84. require.Nil(t, err)
  85. require.Equal(t, "/push-receive", r.URL.Path)
  86. require.Equal(t, "high", r.Header.Get("Urgency"))
  87. require.Equal(t, "", r.Header.Get("Topic"))
  88. received.Store(true)
  89. }))
  90. defer upstreamServer.Close()
  91. addSubscription(t, s, "test-topic", upstreamServer.URL+"/push-receive")
  92. request(t, s, "PUT", "/test-topic", "web push test", nil)
  93. waitFor(t, func() bool {
  94. return received.Load()
  95. })
  96. }
  97. func TestServer_WebPush_PublishExpire(t *testing.T) {
  98. s := newTestServer(t, newTestConfigWithWebPush(t))
  99. var received atomic.Bool
  100. upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  101. _, err := io.ReadAll(r.Body)
  102. require.Nil(t, err)
  103. // Gone
  104. w.WriteHeader(410)
  105. w.Write([]byte(``))
  106. received.Store(true)
  107. }))
  108. defer upstreamServer.Close()
  109. addSubscription(t, s, "test-topic", upstreamServer.URL+"/push-receive")
  110. addSubscription(t, s, "test-topic-abc", upstreamServer.URL+"/push-receive")
  111. requireSubscriptionCount(t, s, "test-topic", 1)
  112. requireSubscriptionCount(t, s, "test-topic-abc", 1)
  113. request(t, s, "PUT", "/test-topic", "web push test", nil)
  114. waitFor(t, func() bool {
  115. return received.Load()
  116. })
  117. // Receiving the 410 should've caused the publisher to expire all subscriptions on the endpoint
  118. requireSubscriptionCount(t, s, "test-topic", 0)
  119. requireSubscriptionCount(t, s, "test-topic-abc", 0)
  120. }
  121. func payloadForTopics(t *testing.T, topics []string) string {
  122. topicsJson, err := json.Marshal(topics)
  123. require.Nil(t, err)
  124. return fmt.Sprintf(`{
  125. "topics": %s,
  126. "browser_subscription":{
  127. "endpoint": "https://example.com/webpush",
  128. "keys": {
  129. "p256dh": "p256dh-key",
  130. "auth": "auth-key"
  131. }
  132. }
  133. }`, topicsJson)
  134. }
  135. func addSubscription(t *testing.T, s *Server, topic string, url string) {
  136. err := s.webPush.AddSubscription(topic, "", webpush.Subscription{
  137. Endpoint: url,
  138. Keys: webpush.Keys{
  139. // connected to a local test VAPID key, not a leak!
  140. Auth: "kSC3T8aN1JCQxxPdrFLrZg",
  141. P256dh: "BMKKbxdUU_xLS7G1Wh5AN8PvWOjCzkCuKZYb8apcqYrDxjOF_2piggBnoJLQYx9IeSD70fNuwawI3e9Y8m3S3PE",
  142. },
  143. })
  144. require.Nil(t, err)
  145. }
  146. func requireSubscriptionCount(t *testing.T, s *Server, topic string, expectedLength int) {
  147. subs, err := s.webPush.SubscriptionsForTopic("test-topic")
  148. require.Nil(t, err)
  149. require.Len(t, subs, expectedLength)
  150. }