Repository navigation
feat(notifications): retire an Android push installation without a session - #2834
Conversation
…ssion
An Android phone that removes a server or signs out sends one
session-authenticated unregisterPushDevice and then clears its login. When
that request does not land, the app cannot resend it and the server keeps
pushing FCM to the phone.
POST /api/v2/notifications/push/devices/{device_id}/retire
(retirePushDevice) takes the installation key and generation the app still
holds and needs no login session. Under the installation lock, a newer
generation applies an ordered removal on behalf of the stored owner: it
deletes the installation's Android push rows (queued attempts cascade) and
records the owner's removal intent at that generation. A replay at a stored
removal's generation answers removed without writing, an older generation or
the generation of a registration gets 409, a wrong key gets 403, and an
installation with no ordered record answers removed without writing. A later
registration with a higher generation and the same key resumes through the
existing ordered path.
The route is public and spends a new android_push_retire per-IP rate-limit
budget.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
visible, evidence incomplete
Suggested fix: add the matching API request/response excerpts under Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness. Posted via Macroscope — Visible change evidence |
|
Automated check: Macroscope check run agent (gpt-6-luna). No validation was performed. Posted via Macroscope — v1 validation impact |
07cd1e9
into
fix/apple-push-retire-installation
…2826) * feat(notifications): retire an Apple push installation without a session An iPhone that removes a server makes one session-authenticated attempt to turn push off and then deletes its session. When that attempt fails, nothing can stop the server from pushing to the phone, which shows the pushes as generic banners. POST /api/v2/devices/push/apple/retire (retireApplePushInstallation) takes the installation key and generation the app still holds and needs no login session. Under the installation lock, a newer generation deletes the installation's Apple push rows (queued attempts cascade) and records a retirement intent that keeps the key hash and owner. An exact replay answers the same receipt without writing, an older or conflicting generation gets 409, a wrong key gets 403, and an installation with no ordered record answers removed without writing. A later registration with a higher generation and the same key resumes through the existing ordered path. The route is public and spends a new apple_push_retire per-IP rate-limit budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(notifications): retire an Android push installation without a session (#2834) An Android phone that removes a server or signs out sends one session-authenticated unregisterPushDevice and then clears its login. When that request does not land, the app cannot resend it and the server keeps pushing FCM to the phone. POST /api/v2/notifications/push/devices/{device_id}/retire (retirePushDevice) takes the installation key and generation the app still holds and needs no login session. Under the installation lock, a newer generation applies an ordered removal on behalf of the stored owner: it deletes the installation's Android push rows (queued attempts cascade) and records the owner's removal intent at that generation. A replay at a stored removal's generation answers removed without writing, an older generation or the generation of a registration gets 409, a wrong key gets 403, and an installation with no ordered record answers removed without writing. A later registration with a higher generation and the same key resumes through the existing ordered path. The route is public and spends a new android_push_retire per-IP rate-limit budget. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Related issue: N/A
Validation tasks: changes #1234 C2
An Android phone that removes a Silo server, or signs out of it, sends one logged-in
unregisterPushDeviceand then clears its login. If that request doesn't land, the app can't resend it, and the server keeps sending FCM pushes to the phone. This is the Android side of the gap #2826 closes for Apple.This adds a way for the app to stop those pushes without a login.
Approach
New endpoint:
POST /api/v2/notifications/push/devices/{device_id}/retire(retirePushDevice). It takesX-Push-Installation-KeyandX-Push-Generation, the same headers as ordered Android registration, and no body. The server already stores the key's hash, so the key authorizes this one call.push_devicesrows, and their queued attempts go with them. It records the stored owner's removal at that generation. The key hash and owner stay, so a later registration at a higher generation goes through the normal ordered path.removed: trueand nothing is written.android_push_retire, at 20/min with a burst of 10. The 256-bit key is the real guard.This is stacked on #2826, which adds the Apple endpoint and the shared generation parser. Retarget to
mainafter #2826 merges. The Android client change that calls this is Silo-Server/silo-android#657.Validation
verify-apiv2-openapi,verify-apiv2-web-types,verify-route-inventory: pass.verify-apiv2-contract: 2 changes, none breaking.make test-goand the web gate. CI covers them.Evidence
Evidence: https://evidence.siloserver.org/r/silo-server/pr-2834/
Risks
Checklist
AI Disclosure
🤖 Generated with Claude Code
Note
Add session-less Android push device retirement to notifications API
POST /api/v2/notifications/push/devices/{device_id}/retire, which takes an installation-key header and generation, and returns a receipt with the generation and a removed flag. The route is registered in notification_android_push.go and wired through the router when a push-device service is configured.PushDeviceRepository.RetireAndroidPushin android_push_ordering.go performs the retirement transactionally under the installation lock. It requires a matching installation key, rejects stale generations, replays an existing removal at the same generation, deletes Android push rows and queued delivery attempts, and writes a removal tombstone. Unknown and bridge-only installations return a removed receipt without changes.android_push_retireper-IP rate limit of 20 requests per minute with a burst of 10 in ratelimit.go.RetireAndroidPushgeneration ordering.Macroscope summarized e74e963.