feat(notifications): cap push subscriptions per user across channels (LC-147) #180
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lc-147-cap-push-subscriptions"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Bounds the number of active push subscriptions a single user can hold per channel, closing the unbounded-token-registration gap raised in the LC-91 review (LC-147).
Why
An authenticated user could POST many distinct tokens to
/push/subscribe(Web Push),/push/apns, or/push/fcm, growingpush_subscriptions/apns_subscriptions/fcm_subscriptionswithout bound. The upsert dedups by token, so a real device re-registering never grew the table; the abuse vector is a logged-in client minting distinct fake tokens (storage abuse + slowerfor_userfan-out lookups over time). It predates mobile push (Web Push had the same gap), so the fix is applied uniformly to all three channels.How
MAX_PUSH_SUBSCRIPTIONS_PER_USER = 20indb/mod.rs(per-user, per-channel; generous for real multi-device use, far below abuse).insert_or_replacenow calls a privateevict_over_capafter the upsert:DELETEthe user's rows whose id is not among thecapmost-recently-seen (ORDER BY last_seen_at DESC, id DESC LIMIT cap). The row just upserted haslast_seen_at = now, so it always survives; theid DESCtie-break makes a burst of same-second registrations evict deterministically (oldest id first).Acceptance criteria
Out of scope
Test
just check,just test,just test-saasgreen (theroutes_uploadsupload-pipeline flake under concurrent-binary load is pre-existing and passes in isolation). Six new tests (two per channel). No schema change.🤖 Generated with Claude Code