server_web_push.go 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package server
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "github.com/SherClockHolmes/webpush-go"
  7. "heckel.io/ntfy/log"
  8. "heckel.io/ntfy/user"
  9. )
  10. func (s *Server) handleWebPushUpdate(w http.ResponseWriter, r *http.Request, v *visitor) error {
  11. payload, err := readJSONWithLimit[webPushSubscriptionPayload](r.Body, jsonBodyBytesLimit, false)
  12. if err != nil || payload.BrowserSubscription.Endpoint == "" || payload.BrowserSubscription.Keys.P256dh == "" || payload.BrowserSubscription.Keys.Auth == "" {
  13. return errHTTPBadRequestWebPushSubscriptionInvalid
  14. }
  15. u := v.User()
  16. topics, err := s.topicsFromIDs(payload.Topics...)
  17. if err != nil {
  18. return err
  19. }
  20. if s.userManager != nil {
  21. for _, t := range topics {
  22. if err := s.userManager.Authorize(u, t.ID, user.PermissionRead); err != nil {
  23. logvr(v, r).With(t).Err(err).Debug("Access to topic %s not authorized", t.ID)
  24. return errHTTPForbidden.With(t)
  25. }
  26. }
  27. }
  28. if err := s.webPush.UpdateSubscriptions(payload.Topics, v.MaybeUserID(), payload.BrowserSubscription); err != nil {
  29. return err
  30. }
  31. return s.writeJSON(w, newSuccessResponse())
  32. }
  33. func (s *Server) publishToWebPushEndpoints(v *visitor, m *message) {
  34. subscriptions, err := s.webPush.SubscriptionsForTopic(m.Topic)
  35. if err != nil {
  36. logvm(v, m).Err(err).Warn("Unable to publish web push messages")
  37. return
  38. }
  39. for i, xi := range subscriptions {
  40. go func(i int, sub webPushSubscription) {
  41. ctx := log.Context{"endpoint": sub.BrowserSubscription.Endpoint, "username": sub.UserID, "topic": m.Topic, "message_id": m.ID}
  42. payload := &webPushPayload{
  43. SubscriptionID: fmt.Sprintf("%s/%s", s.config.BaseURL, m.Topic),
  44. Message: *m,
  45. }
  46. jsonPayload, err := json.Marshal(payload)
  47. if err != nil {
  48. logvm(v, m).Err(err).Fields(ctx).Debug("Unable to publish web push message")
  49. return
  50. }
  51. resp, err := webpush.SendNotification(jsonPayload, &sub.BrowserSubscription, &webpush.Options{
  52. Subscriber: s.config.WebPushEmailAddress,
  53. VAPIDPublicKey: s.config.WebPushPublicKey,
  54. VAPIDPrivateKey: s.config.WebPushPrivateKey,
  55. // Deliverability on iOS isn't great with lower urgency values,
  56. // and thus we can't really map lower ntfy priorities to lower urgency values
  57. Urgency: webpush.UrgencyHigh,
  58. })
  59. if err != nil {
  60. logvm(v, m).Err(err).Fields(ctx).Debug("Unable to publish web push message")
  61. if err := s.webPush.RemoveByEndpoint(sub.BrowserSubscription.Endpoint); err != nil {
  62. logvm(v, m).Err(err).Fields(ctx).Warn("Unable to expire subscription")
  63. }
  64. return
  65. }
  66. // May want to handle at least 429 differently, but for now treat all errors the same
  67. if !(200 <= resp.StatusCode && resp.StatusCode <= 299) {
  68. logvm(v, m).Fields(ctx).Field("response", resp).Debug("Unable to publish web push message")
  69. if err := s.webPush.RemoveByEndpoint(sub.BrowserSubscription.Endpoint); err != nil {
  70. logvm(v, m).Err(err).Fields(ctx).Warn("Unable to expire subscription")
  71. }
  72. return
  73. }
  74. }(i, xi)
  75. }
  76. }