From 8f1f67f1f0e5daf49db49c0cb88add01fed77230 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 18 Feb 2023 13:23:02 +0100 Subject: [PATCH] Rate limit Web Push checks No need to re-check that a Web Push subscription is valid every time a downstream connects. Mobile devices may reconnect pretty frequently. Check at most once a day. --- database/database.go | 8 ++++--- database/postgres.go | 4 ++-- database/sqlite.go | 7 ++++-- downstream.go | 53 ++++++++++++++++++++++++-------------------- server.go | 1 + 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/database/database.go b/database/database.go index c680a5f..15c04b9 100644 --- a/database/database.go +++ b/database/database.go @@ -255,9 +255,11 @@ type WebPushConfig struct { } type WebPushSubscription struct { - ID int64 - Endpoint string - Keys struct { + ID int64 + Endpoint string + CreatedAt, UpdatedAt time.Time // read-only + + Keys struct { Auth string P256DH string VAPID string diff --git a/database/postgres.go b/database/postgres.go index ee4381c..6faa695 100644 --- a/database/postgres.go +++ b/database/postgres.go @@ -849,7 +849,7 @@ func (db *PostgresDB) ListWebPushSubscriptions(ctx context.Context, userID, netw } rows, err := db.db.QueryContext(ctx, ` - SELECT id, endpoint, key_auth, key_p256dh, key_vapid + SELECT id, endpoint, created_at, updated_at, key_auth, key_p256dh, key_vapid FROM "WebPushSubscription" WHERE "user" = $1 AND network IS NOT DISTINCT FROM $2`, userID, nullNetworkID) if err != nil { @@ -860,7 +860,7 @@ func (db *PostgresDB) ListWebPushSubscriptions(ctx context.Context, userID, netw var subs []WebPushSubscription for rows.Next() { var sub WebPushSubscription - if err := rows.Scan(&sub.ID, &sub.Endpoint, &sub.Keys.Auth, &sub.Keys.P256DH, &sub.Keys.VAPID); err != nil { + if err := rows.Scan(&sub.ID, &sub.Endpoint, &sub.CreatedAt, &sub.UpdatedAt, &sub.Keys.Auth, &sub.Keys.P256DH, &sub.Keys.VAPID); err != nil { return nil, err } subs = append(subs, sub) diff --git a/database/sqlite.go b/database/sqlite.go index 932e1ee..7e1f00a 100644 --- a/database/sqlite.go +++ b/database/sqlite.go @@ -1067,7 +1067,7 @@ func (db *SqliteDB) ListWebPushSubscriptions(ctx context.Context, userID, networ } rows, err := db.db.QueryContext(ctx, ` - SELECT id, endpoint, key_auth, key_p256dh, key_vapid + SELECT id, endpoint, created_at, updated_at, key_auth, key_p256dh, key_vapid FROM WebPushSubscription WHERE user = ? AND network IS ?`, userID, nullNetworkID) if err != nil { @@ -1078,9 +1078,12 @@ func (db *SqliteDB) ListWebPushSubscriptions(ctx context.Context, userID, networ var subs []WebPushSubscription for rows.Next() { var sub WebPushSubscription - if err := rows.Scan(&sub.ID, &sub.Endpoint, &sub.Keys.Auth, &sub.Keys.P256DH, &sub.Keys.VAPID); err != nil { + var createdAt, updatedAt sqliteTime + if err := rows.Scan(&sub.ID, &sub.Endpoint, &createdAt, &updatedAt, &sub.Keys.Auth, &sub.Keys.P256DH, &sub.Keys.VAPID); err != nil { return nil, err } + sub.CreatedAt = createdAt.Time + sub.UpdatedAt = updatedAt.Time subs = append(subs, sub) } diff --git a/downstream.go b/downstream.go index 2d2acab..d7833bf 100644 --- a/downstream.go +++ b/downstream.go @@ -3230,10 +3230,13 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. }} } + updateSub := true oldSub := findWebPushSubscription(subs, endpoint) if oldSub != nil { // Update the old subscription instead of creating a new one newSub.ID = oldSub.ID + // Rate-limit subscription checks + updateSub = time.Since(oldSub.UpdatedAt) > webpushCheckSubscriptionDelay || oldSub.Keys != newSub.Keys } var networkID int64 @@ -3242,32 +3245,34 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. } // Send a test Web Push message, to make sure the endpoint is valid - err = dc.srv.sendWebPush(ctx, &webpush.Subscription{ - Endpoint: newSub.Endpoint, - Keys: webpush.Keys{ - Auth: newSub.Keys.Auth, - P256dh: newSub.Keys.P256DH, - }, - }, newSub.Keys.VAPID, &irc.Message{ - Command: "NOTE", - Params: []string{"WEBPUSH", "REGISTERED", "Push notifications enabled"}, - }) - if err != nil { - dc.logger.Printf("failed to send Web push notification to endpoint %q: %v", newSub.Endpoint, err) - return ircError{&irc.Message{ - Command: "FAIL", - Params: []string{"WEBPUSH", "INVALID_PARAMS", subcommand, "Invalid endpoint"}, - }} - } + if updateSub { + err = dc.srv.sendWebPush(ctx, &webpush.Subscription{ + Endpoint: newSub.Endpoint, + Keys: webpush.Keys{ + Auth: newSub.Keys.Auth, + P256dh: newSub.Keys.P256DH, + }, + }, newSub.Keys.VAPID, &irc.Message{ + Command: "NOTE", + Params: []string{"WEBPUSH", "REGISTERED", "Push notifications enabled"}, + }) + if err != nil { + dc.logger.Printf("failed to send Web push notification to endpoint %q: %v", newSub.Endpoint, err) + return ircError{&irc.Message{ + Command: "FAIL", + Params: []string{"WEBPUSH", "INVALID_PARAMS", subcommand, "Invalid endpoint"}, + }} + } - // TODO: limit max number of subscriptions, prune old ones + // TODO: limit max number of subscriptions, prune old ones - if err := dc.user.srv.db.StoreWebPushSubscription(ctx, dc.user.ID, networkID, &newSub); err != nil { - dc.logger.Printf("failed to store Web push subscription: %v", err) - return ircError{&irc.Message{ - Command: "FAIL", - Params: []string{"WEBPUSH", "INTERNAL_ERROR", subcommand, "Internal error"}, - }} + if err := dc.user.srv.db.StoreWebPushSubscription(ctx, dc.user.ID, networkID, &newSub); err != nil { + dc.logger.Printf("failed to store Web push subscription: %v", err) + return ircError{&irc.Message{ + Command: "FAIL", + Params: []string{"WEBPUSH", "INTERNAL_ERROR", subcommand, "Internal error"}, + }} + } } dc.SendMessage(&irc.Message{ diff --git a/server.go b/server.go index d7bf856..c29469a 100644 --- a/server.go +++ b/server.go @@ -39,6 +39,7 @@ var upstreamMessageBurst = 10 var backlogTimeout = 10 * time.Second var handleDownstreamMessageTimeout = 10 * time.Second var downstreamRegisterTimeout = 30 * time.Second +var webpushCheckSubscriptionDelay = 24 * time.Hour var chatHistoryLimit = 1000 var backlogLimit = 4000