server_web_push_test.go 6.6 KB

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