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.
This commit is contained in:
Simon Ser 2023-02-18 13:23:02 +01:00
parent b6c0841291
commit 8f1f67f1f0
5 changed files with 42 additions and 31 deletions

View File

@ -255,9 +255,11 @@ type WebPushConfig struct {
} }
type WebPushSubscription struct { type WebPushSubscription struct {
ID int64 ID int64
Endpoint string Endpoint string
Keys struct { CreatedAt, UpdatedAt time.Time // read-only
Keys struct {
Auth string Auth string
P256DH string P256DH string
VAPID string VAPID string

View File

@ -849,7 +849,7 @@ func (db *PostgresDB) ListWebPushSubscriptions(ctx context.Context, userID, netw
} }
rows, err := db.db.QueryContext(ctx, ` 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" FROM "WebPushSubscription"
WHERE "user" = $1 AND network IS NOT DISTINCT FROM $2`, userID, nullNetworkID) WHERE "user" = $1 AND network IS NOT DISTINCT FROM $2`, userID, nullNetworkID)
if err != nil { if err != nil {
@ -860,7 +860,7 @@ func (db *PostgresDB) ListWebPushSubscriptions(ctx context.Context, userID, netw
var subs []WebPushSubscription var subs []WebPushSubscription
for rows.Next() { for rows.Next() {
var sub WebPushSubscription 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 return nil, err
} }
subs = append(subs, sub) subs = append(subs, sub)

View File

@ -1067,7 +1067,7 @@ func (db *SqliteDB) ListWebPushSubscriptions(ctx context.Context, userID, networ
} }
rows, err := db.db.QueryContext(ctx, ` 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 FROM WebPushSubscription
WHERE user = ? AND network IS ?`, userID, nullNetworkID) WHERE user = ? AND network IS ?`, userID, nullNetworkID)
if err != nil { if err != nil {
@ -1078,9 +1078,12 @@ func (db *SqliteDB) ListWebPushSubscriptions(ctx context.Context, userID, networ
var subs []WebPushSubscription var subs []WebPushSubscription
for rows.Next() { for rows.Next() {
var sub WebPushSubscription 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 return nil, err
} }
sub.CreatedAt = createdAt.Time
sub.UpdatedAt = updatedAt.Time
subs = append(subs, sub) subs = append(subs, sub)
} }

View File

@ -3230,10 +3230,13 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
}} }}
} }
updateSub := true
oldSub := findWebPushSubscription(subs, endpoint) oldSub := findWebPushSubscription(subs, endpoint)
if oldSub != nil { if oldSub != nil {
// Update the old subscription instead of creating a new one // Update the old subscription instead of creating a new one
newSub.ID = oldSub.ID newSub.ID = oldSub.ID
// Rate-limit subscription checks
updateSub = time.Since(oldSub.UpdatedAt) > webpushCheckSubscriptionDelay || oldSub.Keys != newSub.Keys
} }
var networkID int64 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 // Send a test Web Push message, to make sure the endpoint is valid
err = dc.srv.sendWebPush(ctx, &webpush.Subscription{ if updateSub {
Endpoint: newSub.Endpoint, err = dc.srv.sendWebPush(ctx, &webpush.Subscription{
Keys: webpush.Keys{ Endpoint: newSub.Endpoint,
Auth: newSub.Keys.Auth, Keys: webpush.Keys{
P256dh: newSub.Keys.P256DH, Auth: newSub.Keys.Auth,
}, P256dh: newSub.Keys.P256DH,
}, newSub.Keys.VAPID, &irc.Message{ },
Command: "NOTE", }, newSub.Keys.VAPID, &irc.Message{
Params: []string{"WEBPUSH", "REGISTERED", "Push notifications enabled"}, 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) if err != nil {
return ircError{&irc.Message{ dc.logger.Printf("failed to send Web push notification to endpoint %q: %v", newSub.Endpoint, err)
Command: "FAIL", return ircError{&irc.Message{
Params: []string{"WEBPUSH", "INVALID_PARAMS", subcommand, "Invalid endpoint"}, 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 { 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) dc.logger.Printf("failed to store Web push subscription: %v", err)
return ircError{&irc.Message{ return ircError{&irc.Message{
Command: "FAIL", Command: "FAIL",
Params: []string{"WEBPUSH", "INTERNAL_ERROR", subcommand, "Internal error"}, Params: []string{"WEBPUSH", "INTERNAL_ERROR", subcommand, "Internal error"},
}} }}
}
} }
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{

View File

@ -39,6 +39,7 @@ var upstreamMessageBurst = 10
var backlogTimeout = 10 * time.Second var backlogTimeout = 10 * time.Second
var handleDownstreamMessageTimeout = 10 * time.Second var handleDownstreamMessageTimeout = 10 * time.Second
var downstreamRegisterTimeout = 30 * time.Second var downstreamRegisterTimeout = 30 * time.Second
var webpushCheckSubscriptionDelay = 24 * time.Hour
var chatHistoryLimit = 1000 var chatHistoryLimit = 1000
var backlogLimit = 4000 var backlogLimit = 4000