From d3ac976d05e8052fea2f4c8d7d2ad3c995db4534 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Thu, 8 Jun 2023 14:30:19 -0400 Subject: [PATCH] Remove web-push-(enabled|duration*), change endpoint, other cosmetic changes --- cmd/serve.go | 13 +------- cmd/web_push.go | 25 ++++------------ cmd/web_push_test.go | 6 ++-- docs/config.md | 48 ++++++++++++------------------ server/config.go | 2 -- server/server.go | 10 +++---- server/server.yml | 5 +--- server/server_manager.go | 2 +- server/server_middleware.go | 2 +- server/server_test.go | 3 +- server/server_web_push.go | 2 +- server/server_web_push_test.go | 14 ++++----- server/web_push.go | 10 +++---- web/src/app/Api.js | 4 +-- web/src/app/Notifier.js | 7 ++--- web/src/app/SubscriptionManager.js | 1 - web/src/app/utils.js | 2 +- 17 files changed, 55 insertions(+), 101 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index a77227e6..6d1b111a 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -94,13 +94,10 @@ var flagsServe = append( altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-metrics", Aliases: []string{"enable_metrics"}, EnvVars: []string{"NTFY_ENABLE_METRICS"}, Value: false, Usage: "if set, Prometheus metrics are exposed via the /metrics endpoint"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "metrics-listen-http", Aliases: []string{"metrics_listen_http"}, EnvVars: []string{"NTFY_METRICS_LISTEN_HTTP"}, Usage: "ip:port used to expose the metrics endpoint (implicitly enables metrics)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "profile-listen-http", Aliases: []string{"profile_listen_http"}, EnvVars: []string{"NTFY_PROFILE_LISTEN_HTTP"}, Usage: "ip:port used to expose the profiling endpoints (implicitly enables profiling)"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "web-push-enabled", Aliases: []string{"web_push_enabled"}, EnvVars: []string{"NTFY_WEB_PUSH_ENABLED"}, Usage: "enable web push (requires public and private key)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-public-key", Aliases: []string{"web_push_public_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PUBLIC_KEY"}, Usage: "public key used for web push notifications"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-private-key", Aliases: []string{"web_push_private_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PRIVATE_KEY"}, Usage: "private key used for web push notifications"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-subscriptions-file", Aliases: []string{"web_push_subscriptions_file"}, EnvVars: []string{"NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE"}, Usage: "file used to store web push subscriptions"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-email-address", Aliases: []string{"web_push_email_address"}, EnvVars: []string{"NTFY_WEB_PUSH_EMAIL_ADDRESS"}, Usage: "e-mail address of sender, required to use browser push services"}), - altsrc.NewDurationFlag(&cli.DurationFlag{Name: "web-push-expiry-warning-duration", Aliases: []string{"web_push_expiry_warning_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION"}, Value: server.DefaultWebPushExpiryWarningDuration, Usage: "duration after last update to send a warning notification"}), - altsrc.NewDurationFlag(&cli.DurationFlag{Name: "web-push-expiry-duration", Aliases: []string{"web_push_expiry_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_DURATION"}, Value: server.DefaultWebPushExpiryDuration, Usage: "duration after last update to expire subscription"}), ) var cmdServe = &cli.Command{ @@ -136,12 +133,9 @@ func execServe(c *cli.Context) error { keyFile := c.String("key-file") certFile := c.String("cert-file") firebaseKeyFile := c.String("firebase-key-file") - webPushEnabled := c.Bool("web-push-enabled") webPushPrivateKey := c.String("web-push-private-key") webPushPublicKey := c.String("web-push-public-key") webPushSubscriptionsFile := c.String("web-push-subscriptions-file") - webPushExpiryWarningDuration := c.Duration("web-push-expiry-warning-duration") - webPushExpiryDuration := c.Duration("web-push-expiry-duration") webPushEmailAddress := c.String("web-push-email-address") cacheFile := c.String("cache-file") cacheDuration := c.Duration("cache-duration") @@ -197,12 +191,10 @@ func execServe(c *cli.Context) error { // Check values if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) { return errors.New("if set, FCM key file must exist") - } else if webPushEnabled && (webPushPrivateKey == "" || webPushPublicKey == "" || webPushSubscriptionsFile == "" || webPushEmailAddress == "" || baseURL == "") { + } else if webPushPublicKey != "" && (webPushPrivateKey == "" || webPushSubscriptionsFile == "" || webPushEmailAddress == "" || baseURL == "") { return errors.New("if web push is enabled, web-push-private-key, web-push-public-key, web-push-subscriptions-file, web-push-email-address, and base-url should be set. run 'ntfy web-push generate-keys' to generate keys") } else if keepaliveInterval < 5*time.Second { return errors.New("keepalive interval cannot be lower than five seconds") - } else if webPushEnabled && (webPushExpiryWarningDuration < 24*time.Hour || (webPushExpiryDuration-webPushExpiryWarningDuration < 24*time.Hour)) { - return errors.New("web push expiry warning duration must be at least 1 day, expire duration must be at least 1 day later") } else if managerInterval < 5*time.Second { return errors.New("manager interval cannot be lower than five seconds") } else if cacheDuration > 0 && cacheDuration < managerInterval { @@ -365,13 +357,10 @@ func execServe(c *cli.Context) error { conf.MetricsListenHTTP = metricsListenHTTP conf.ProfileListenHTTP = profileListenHTTP conf.Version = c.App.Version - conf.WebPushEnabled = webPushEnabled conf.WebPushPrivateKey = webPushPrivateKey conf.WebPushPublicKey = webPushPublicKey conf.WebPushSubscriptionsFile = webPushSubscriptionsFile conf.WebPushEmailAddress = webPushEmailAddress - conf.WebPushExpiryDuration = webPushExpiryDuration - conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration // Set up hot-reloading of config go sigHandlerConfigReload(config) diff --git a/cmd/web_push.go b/cmd/web_push.go index 8b09762c..94f45c77 100644 --- a/cmd/web_push.go +++ b/cmd/web_push.go @@ -14,7 +14,7 @@ func init() { } var cmdWebPush = &cli.Command{ - Name: "web-push", + Name: "webpush", Usage: "Generate keys, in the future manage web push subscriptions", UsageText: "ntfy web-push [generate-keys]", Category: categoryServer, @@ -22,7 +22,7 @@ var cmdWebPush = &cli.Command{ Subcommands: []*cli.Command{ { Action: generateWebPushKeys, - Name: "generate-keys", + Name: "keys", Usage: "Generate VAPID keys to enable browser background push notifications", UsageText: "ntfy web-push generate-keys", Category: categoryServer, @@ -36,28 +36,15 @@ func generateWebPushKeys(c *cli.Context) error { return err } - fmt.Fprintf(c.App.ErrWriter, `Keys generated. + fmt.Fprintf(c.App.ErrWriter, `Web Push keys generated. Add the following lines to your config file: -VAPID Public Key: -%s - -VAPID Private Key: -%s - ---- - -Add the following lines to your config file: - -web-push-enabled: true web-push-public-key: %s web-push-private-key: %s -web-push-subscriptions-file: +web-push-subscriptions-file: /var/cache/ntfy/webpush.db # or similar web-push-email-address: -Look at the docs for other methods (e.g. command line flags & environment variables). - -You will also need to set a base-url. -`, publicKey, privateKey, publicKey, privateKey) +See https://ntfy.sh/docs/config/#web-push for details. +`, publicKey, privateKey) return nil } diff --git a/cmd/web_push_test.go b/cmd/web_push_test.go index 3241ea43..1b364701 100644 --- a/cmd/web_push_test.go +++ b/cmd/web_push_test.go @@ -10,15 +10,15 @@ import ( func TestCLI_WebPush_GenerateKeys(t *testing.T) { app, _, _, stderr := newTestApp() - require.Nil(t, runWebPushCommand(app, server.NewConfig(), "generate-keys")) - require.Contains(t, stderr.String(), "Keys generated.") + require.Nil(t, runWebPushCommand(app, server.NewConfig(), "keys")) + require.Contains(t, stderr.String(), "Web Push keys generated.") } func runWebPushCommand(app *cli.App, conf *server.Config, args ...string) error { webPushArgs := []string{ "ntfy", "--log-level=ERROR", - "web-push", + "webpush", } return app.Run(append(webPushArgs, args...)) } diff --git a/docs/config.md b/docs/config.md index cfd14034..b15b8022 100644 --- a/docs/config.md +++ b/docs/config.md @@ -791,9 +791,18 @@ it'll show `New message` as a popup. ## Web Push notifications [Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) ([RFC8030](https://datatracker.ietf.org/doc/html/rfc8030)) -is supported, but needs a little configuration to enable it. Since there is no central server (other than the browser's push endpoint), -you have to configure your own [VAPID](https://datatracker.ietf.org/doc/html/draft-thomson-webpush-vapid) keys. These identify the publisher, -and are used to encrypt the messages before sending them to the push endpoint. +allows ntfy to receive push notifications, even when the ntfy web app (or even the browser, depending on the platform) is closed. +When enabled, the user can enable **background notifications** for their topics in the wep app under Settings. Once enabled by the +user, ntfy will forward published messages to the push endpoint (browser-provided, e.g. fcm.googleapis.com), which will then +forward it to the browser. + +To configure Web Push, you need to generate and configure a [VAPID](https://datatracker.ietf.org/doc/html/draft-thomson-webpush-vapid) keypair (via `ntfy webpush keys`), +a database to keep track of the browser's subscriptions, and an admin email address (you): + +- `web-push-public-key` is the generated VAPID public key, e.g. AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890 +- `web-push-private-key` is the generated VAPID private key, e.g. AA2BB1234567890abcdefzxcvbnm1234567890 +- `web-push-subscriptions-file` is a database file to keep track of browser subscription endpoints, e.g. `/var/cache/ntfy/webpush.db` +- `web-push-email-address` is the admin email address send to the push provider, e.g. `sysadmin@example.com` Limitations: @@ -806,32 +815,17 @@ Limitations: To configure VAPID keys, first generate them: ```sh -$ ntfy web-push generate-keys -Keys generated. - -VAPID Public Key: -AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890 - -VAPID Private Key: -AA2BB1234567890abcdefzxcvbnm1234567890 +$ ntfy webpush keys +Web Push keys generated. ``` Then copy the generated values into your `server.yml` or use the corresponding environment variables or command line arguments: ```yaml -web-push-enabled: true web-push-public-key: AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890 web-push-private-key: AA2BB1234567890abcdefzxcvbnm1234567890 -web-push-subscriptions-file: /var/cache/ntfy/subscriptions.db +web-push-subscriptions-file: /var/cache/ntfy/webpush.db web-push-email-address: sysadmin@example.com - -# don't forget to set the required base-url for web push notifications -base-url: https://ntfy.example.com - -# you can also set custom expiry and warning durations -# the minimum is 1 day for warning, and 1 day after warning for expiry -web-push-expiry-warning-duration: 168h -web-push-expiry-duration: 192h ``` The `web-push-subscriptions-file` is used to store the push subscriptions. Subscriptions do not ever expire automatically, unless the push @@ -840,7 +834,7 @@ gateway returns an error (e.g. 410 Gone when a user has unsubscribed). The web app refreshes subscriptions on start and regularly on an interval, but this file should be persisted across restarts. If the subscription file is deleted or lost, any web apps that aren't open will not receive new web push notifications until you open then. -Changing your public/private keypair is NOT recommended. Browsers only allow one server identity (public key) per origin, and +Changing your public/private keypair is **not recommended**. Browsers only allow one server identity (public key) per origin, and if you change them the clients will not be able to subscribe via web push until the user manually clears the notification permission. ## Tiers @@ -1340,12 +1334,10 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe | | `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact | | `web-push-enabled` | `NTFY_WEB_PUSH_ENABLED` | *boolean* (`true` or `false`) | - | Web Push: Enable/disable (requires private and public key below). | -| `web-push-public-key` | `NTFY_WEB_PUSH_PUBLIC_KEY` | *string* | - | Web Push: Public Key. Run `ntfy web-push generate-keys` to generate | -| `web-push-private-key` | `NTFY_WEB_PUSH_PRIVATE_KEY` | *string* | - | Web Push: Private Key. Run `ntfy web-push generate-keys` to generate | -| `web-push-subscriptions-file` | `NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE` | *string* | - | Web Push: Subscriptions file | +| `web-push-public-key` | `NTFY_WEB_PUSH_PUBLIC_KEY` | *string* | - | Web Push: Public Key. Run `ntfy webpush generate-keys` to generate | +| `web-push-private-key` | `NTFY_WEB_PUSH_PRIVATE_KEY` | *string* | - | Web Push: Private Key. Run `ntfy webpush generate-keys` to generate | +| `web-push-subscriptions-file` | `NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE` | *string* | - | Web Push: Subscriptions file | | `web-push-email-address` | `NTFY_WEB_PUSH_EMAIL_ADDRESS` | *string* | - | Web Push: Sender email address | -| `web-push-expiry-warning-duration` | `NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION` | *duration* | 1 week | Web Push: Time before expiry warning is sent (min 1 day) | -| `web-push-expiry-duration` | `NTFY_WEB_PUSH_EXPIRY_DURATION` | *duration* | 1 week + 1 day | Web Push: Time before subscription is expired (min 1 day after warning) | The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. The format for a *size* is: `(GMK)`, e.g. 1G, 200M or 4000k. @@ -1443,8 +1435,6 @@ OPTIONS: --web-push-private-key value, --web_push_private_key value private key used for web push notifications [$NTFY_WEB_PUSH_PRIVATE_KEY] --web-push-subscriptions-file value, --web_push_subscriptions_file value file used to store web push subscriptions [$NTFY_WEB_PUSH_SUBSCRIPTIONS_FILE] --web-push-email-address value, --web_push_email_address value e-mail address of sender, required to use browser push services [$NTFY_WEB_PUSH_EMAIL_ADDRESS] - --web-push-expiry-warning-duration value, --web_push_expiry_warning_duration value duration after last update to send a warning notification (default: 168h0m0s) [$NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION] - --web-push-expiry-duration value, --web_push_expiry_duration value duration after last update to expire subscription (default: 192h0m0s) [$NTFY_WEB_PUSH_EXPIRY_DURATION] --help, -h show help ``` diff --git a/server/config.go b/server/config.go index e26ee0dd..3d779fba 100644 --- a/server/config.go +++ b/server/config.go @@ -153,7 +153,6 @@ type Config struct { EnableMetrics bool AccessControlAllowOrigin string // CORS header field to restrict access from web clients Version string // injected by App - WebPushEnabled bool WebPushPrivateKey string WebPushPublicKey string WebPushSubscriptionsFile string @@ -241,7 +240,6 @@ func NewConfig() *Config { EnableReservations: false, AccessControlAllowOrigin: "*", Version: "", - WebPushEnabled: false, WebPushPrivateKey: "", WebPushPublicKey: "", WebPushSubscriptionsFile: "", diff --git a/server/server.go b/server/server.go index a3170817..c9b95ba6 100644 --- a/server/server.go +++ b/server/server.go @@ -94,7 +94,7 @@ var ( apiAccountSettingsPath = "/v1/account/settings" apiAccountSubscriptionPath = "/v1/account/subscription" apiAccountReservationPath = "/v1/account/reservation" - apiAccountWebPushPath = "/v1/account/web-push" + apiAccountWebPushPath = "/v1/account/webpush" apiAccountPhonePath = "/v1/account/phone" apiAccountPhoneVerifyPath = "/v1/account/phone/verify" apiAccountBillingPortalPath = "/v1/account/billing/portal" @@ -157,7 +157,7 @@ func New(conf *Config) (*Server, error) { return nil, err } var webPush *webPushStore - if conf.WebPushEnabled { + if conf.WebPushPublicKey != "" { webPush, err = newWebPushStore(conf.WebPushSubscriptionsFile) if err != nil { return nil, err @@ -574,7 +574,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi EnableCalls: s.config.TwilioAccount != "", EnableEmails: s.config.SMTPSenderFrom != "", EnableReservations: s.config.EnableReservations, - EnableWebPush: s.config.WebPushEnabled, + EnableWebPush: s.config.WebPushPublicKey != "", BillingContact: s.config.BillingContact, WebPushPublicKey: s.config.WebPushPublicKey, DisallowedTopics: s.config.DisallowedTopics, @@ -792,7 +792,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream go s.forwardPollRequest(v, m) } - if s.config.WebPushEnabled { + if s.config.WebPushPublicKey != "" { go s.publishToWebPushEndpoints(v, m) } } else { @@ -1724,7 +1724,7 @@ func (s *Server) sendDelayedMessage(v *visitor, m *message) error { if s.config.UpstreamBaseURL != "" { go s.forwardPollRequest(v, m) } - if s.config.WebPushEnabled { + if s.config.WebPushPublicKey != "" { go s.publishToWebPushEndpoints(v, m) } if err := s.messageCache.MarkPublished(m); err != nil { diff --git a/server/server.yml b/server/server.yml index e59c8336..0afd4b43 100644 --- a/server/server.yml +++ b/server/server.yml @@ -40,15 +40,12 @@ # Enable web push # -# Run "ntfy web-push generate-keys" to generate the keys +# Run "ntfy webpush keys" to generate the keys # -# web-push-enabled: false # web-push-public-key: # web-push-private-key: # web-push-subscriptions-file: # web-push-email-address: -# web-push-expiry-warning-duration: 168h -# web-push-expiry-duration: 192h # If "cache-file" is set, messages are cached in a local SQLite database instead of only in-memory. # This allows for service restarts without losing messages in support of the since= parameter. diff --git a/server/server_manager.go b/server/server_manager.go index a8626bd5..97572a55 100644 --- a/server/server_manager.go +++ b/server/server_manager.go @@ -15,7 +15,7 @@ func (s *Server) execManager() { s.pruneTokens() s.pruneAttachments() s.pruneMessages() - if s.config.WebPushEnabled { + if s.config.WebPushPublicKey != "" { s.expireOrNotifyOldSubscriptions() } diff --git a/server/server_middleware.go b/server/server_middleware.go index 41c2706c..b9d1bb88 100644 --- a/server/server_middleware.go +++ b/server/server_middleware.go @@ -60,7 +60,7 @@ func (s *Server) ensureWebEnabled(next handleFunc) handleFunc { func (s *Server) ensureWebPushEnabled(next handleFunc) handleFunc { return func(w http.ResponseWriter, r *http.Request, v *visitor) error { - if !s.config.WebPushEnabled { + if s.config.WebPushPublicKey == "" { return errHTTPNotFound } return next(w, r, v) diff --git a/server/server_test.go b/server/server_test.go index 76f83eea..2c361e3b 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -2622,8 +2622,7 @@ func newTestConfigWithWebPush(t *testing.T) *Config { conf := newTestConfig(t) privateKey, publicKey, err := webpush.GenerateVAPIDKeys() require.Nil(t, err) - conf.WebPushEnabled = true - conf.WebPushSubscriptionsFile = filepath.Join(t.TempDir(), "subscriptions.db") + conf.WebPushSubscriptionsFile = filepath.Join(t.TempDir(), "webpush.db") conf.WebPushEmailAddress = "testing@example.com" conf.WebPushPrivateKey = privateKey conf.WebPushPublicKey = publicKey diff --git a/server/server_web_push.go b/server/server_web_push.go index 6b3e4adc..20bd4e7c 100644 --- a/server/server_web_push.go +++ b/server/server_web_push.go @@ -76,7 +76,7 @@ func (s *Server) publishToWebPushEndpoints(v *visitor, m *message) { } // TODO this should return error -// TODO the updated_at field is not actually updated ever +// TODO rate limiting func (s *Server) expireOrNotifyOldSubscriptions() { subscriptions, err := s.webPush.ExpireAndGetExpiringSubscriptions(s.config.WebPushExpiryWarningDuration, s.config.WebPushExpiryDuration) diff --git a/server/server_web_push_test.go b/server/server_web_push_test.go index 29d91f7e..57c52a0d 100644 --- a/server/server_web_push_test.go +++ b/server/server_web_push_test.go @@ -23,7 +23,7 @@ const ( func TestServer_WebPush_TopicAdd(t *testing.T) { s := newTestServer(t, newTestConfigWithWebPush(t)) - response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil) + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil) require.Equal(t, 200, response.Code) require.Equal(t, `{"success":true}`+"\n", response.Body.String()) @@ -40,7 +40,7 @@ func TestServer_WebPush_TopicAdd(t *testing.T) { func TestServer_WebPush_TopicAdd_InvalidEndpoint(t *testing.T) { s := newTestServer(t, newTestConfigWithWebPush(t)) - response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, "https://ddos-target.example.com/webpush"), nil) + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, "https://ddos-target.example.com/webpush"), nil) require.Equal(t, 400, response.Code) require.Equal(t, `{"code":40039,"http":400,"error":"invalid request: web push endpoint unknown"}`+"\n", response.Body.String()) } @@ -53,7 +53,7 @@ func TestServer_WebPush_TopicAdd_TooManyTopics(t *testing.T) { topicList[i] = util.RandomString(5) } - response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, topicList, defaultEndpoint), nil) + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, topicList, defaultEndpoint), nil) require.Equal(t, 400, response.Code) require.Equal(t, `{"code":40040,"http":400,"error":"invalid request: too many web push topic subscriptions"}`+"\n", response.Body.String()) } @@ -64,7 +64,7 @@ func TestServer_WebPush_TopicUnsubscribe(t *testing.T) { addSubscription(t, s, "test-topic", defaultEndpoint) requireSubscriptionCount(t, s, "test-topic", 1) - response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{}, defaultEndpoint), nil) + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{}, defaultEndpoint), nil) require.Equal(t, 200, response.Code) require.Equal(t, `{"success":true}`+"\n", response.Body.String()) @@ -79,7 +79,7 @@ func TestServer_WebPush_TopicSubscribeProtected_Allowed(t *testing.T) { 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"}, defaultEndpoint), map[string]string{ + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), map[string]string{ "Authorization": util.BasicAuth("ben", "ben"), }) require.Equal(t, 200, response.Code) @@ -96,7 +96,7 @@ func TestServer_WebPush_TopicSubscribeProtected_Denied(t *testing.T) { config.AuthDefault = user.PermissionDenyAll s := newTestServer(t, config) - response := request(t, s, "PUT", "/v1/account/web-push", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil) + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), nil) require.Equal(t, 403, response.Code) requireSubscriptionCount(t, s, "test-topic", 0) @@ -109,7 +109,7 @@ func TestServer_WebPush_DeleteAccountUnsubscribe(t *testing.T) { 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"}, defaultEndpoint), map[string]string{ + response := request(t, s, "PUT", "/v1/account/webpush", payloadForTopics(t, []string{"test-topic"}, defaultEndpoint), map[string]string{ "Authorization": util.BasicAuth("ben", "ben"), }) diff --git a/server/web_push.go b/server/web_push.go index 0bea0857..6a6b5ee3 100644 --- a/server/web_push.go +++ b/server/web_push.go @@ -31,10 +31,9 @@ const ( INSERT OR REPLACE INTO subscriptions (topic, user_id, endpoint, key_auth, key_p256dh) VALUES (?, ?, ?, ?, ?) ` - deleteWebPushSubscriptionByEndpointQuery = `DELETE FROM subscriptions WHERE endpoint = ?` - deleteWebPushSubscriptionByUserIDQuery = `DELETE FROM subscriptions WHERE user_id = ?` - deleteWebPushSubscriptionByTopicAndEndpointQuery = `DELETE FROM subscriptions WHERE topic = ? AND endpoint = ?` - deleteWebPushSubscriptionsByAgeQuery = `DELETE FROM subscriptions WHERE warning_sent = 1 AND updated_at <= datetime('now', ?)` + deleteWebPushSubscriptionByEndpointQuery = `DELETE FROM subscriptions WHERE endpoint = ?` + deleteWebPushSubscriptionByUserIDQuery = `DELETE FROM subscriptions WHERE user_id = ?` + deleteWebPushSubscriptionsByAgeQuery = `DELETE FROM subscriptions WHERE warning_sent = 1 AND updated_at <= datetime('now', ?)` selectWebPushSubscriptionsForTopicQuery = `SELECT endpoint, key_auth, key_p256dh, user_id FROM subscriptions WHERE topic = ?` selectWebPushSubscriptionsExpiringSoonQuery = `SELECT DISTINCT endpoint, key_auth, key_p256dh FROM subscriptions WHERE warning_sent = 0 AND updated_at <= datetime('now', ?)` @@ -169,8 +168,7 @@ func (c *webPushStore) ExpireAndGetExpiringSubscriptions(warningDuration time.Du return nil, err } - err = tx.Commit() - if err != nil { + if err = tx.Commit(); err != nil { return nil, err } diff --git a/web/src/app/Api.js b/web/src/app/Api.js index b763346b..d4281d33 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -6,7 +6,7 @@ import { topicUrlAuth, topicUrlJsonPoll, topicUrlJsonPollWithSince, - webPushSubscriptionsUrl, + accountWebPushUrl, } from "./utils"; import userManager from "./UserManager"; import { fetchOrThrow } from "./errors"; @@ -117,7 +117,7 @@ class Api { async updateWebPushSubscriptions(topics, browserSubscription) { const user = await userManager.get(config.base_url); - const url = webPushSubscriptionsUrl(config.base_url); + const url = accountWebPushUrl(config.base_url); console.log(`[Api] Sending Web Push Subscriptions`, { url, topics, endpoint: browserSubscription.endpoint }); const response = await fetch(url, { diff --git a/web/src/app/Notifier.js b/web/src/app/Notifier.js index b157ef46..8ef1a1ae 100644 --- a/web/src/app/Notifier.js +++ b/web/src/app/Notifier.js @@ -52,17 +52,14 @@ class Notifier { if (!this.pushPossible()) { throw new Error("Unsupported or denied"); } - const pushManager = await this.pushManager(); - const existingSubscription = await pushManager.getSubscription(); - if (existingSubscription) { return existingSubscription; } - // create a new subscription only if web push is enabled - // it is possible that web push was previously enabled and then disabled again + // Create a new subscription only if web push is enabled. + // It is possible that web push was previously enabled and then disabled again // in which case there would be an existingSubscription. // but if it was _not_ enabled previously, we reach here, and only create a new // subscription if it is now enabled. diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index 74488bc0..592db6f9 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -113,7 +113,6 @@ class SubscriptionManager { async refreshWebPushSubscriptions(presetTopics) { const topics = presetTopics ?? (await this.webPushTopics()); - const browserSubscription = await notifier.getBrowserSubscription(); if (!browserSubscription) { diff --git a/web/src/app/utils.js b/web/src/app/utils.js index d5b3e976..8a1026d7 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -21,7 +21,6 @@ export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, top export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`; export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`; export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic)); -export const webPushSubscriptionsUrl = (baseUrl) => `${baseUrl}/v1/account/web-push`; export const accountUrl = (baseUrl) => `${baseUrl}/v1/account`; export const accountPasswordUrl = (baseUrl) => `${baseUrl}/v1/account/password`; export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`; @@ -33,6 +32,7 @@ export const accountBillingSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account export const accountBillingPortalUrl = (baseUrl) => `${baseUrl}/v1/account/billing/portal`; export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`; export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`; +export const accountWebPushUrl = (baseUrl) => `${baseUrl}/v1/account/webpush`; export const validUrl = (url) => url.match(/^https?:\/\/.+/);