QA-8627 Don't Write Connect Data After Signing Out Of PersonalID (1/2: gate the write sites) - #3863
Conversation
Signing out of PersonalId could leave the nav drawer showing "Logged out of PersonalId / please configure your PersonalId account again". An in-flight Connect request (notification retrieval, release toggles) landed after signing out had deleted the Connect DB and its passphrase, so ConnectDatabaseHelper.getHandle() failed to open the DB, flagged it as broken and raised PERSONALID_GENERIC_ERROR. That error persists for 24h, and because GlobalErrorUtil.triggerGlobalError throws LoginInvalidatedException, reaching the uncaught handler with it also wiped the account and exited the process. Connect storage may only be accessed while signed in, so the write sites now gate on the sign-in status: - Release toggles are no longer stored when the user is not signed in. The response is still parsed and returned, it just isn't persisted. - The messaging-channel key sync worker bails out instead of reading channels from a deleted DB. - Retrieved notifications are not stored, and no acknowledgement is sent. For those checks to mean anything, forgetUser has to flip the status before it deletes the DB rather than after, and the status field has to be volatile so background threads observe the change. A notification click-gating test previously relied on loading notifications while registering; it now loads signed in and drops the login only for the click it actually covers. Note that a status check is check-then-act: a caller already past its check when sign-out lands still reaches the deleted DB. Closing that interleaving is a follow-up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughPersonalID sign-out now resets login state before clearing stored user data and makes status visibility thread-safe. Release-toggle parsing, messaging key synchronization, and push-notification handling skip database or synchronization work when sign-out occurs during an in-flight operation. Tests and QA scenarios cover signed-in persistence, signed-out behavior, and notification navigation. Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant PersonalIdManager
participant PushNotificationApiHelper
participant ConnectReleaseTogglesParser
participant MessagingChannelsKeySyncWorker
User->>PersonalIdManager: Sign out
PersonalIdManager->>PersonalIdManager: Set status to NotIntroduced
PushNotificationApiHelper->>PersonalIdManager: Check login state
ConnectReleaseTogglesParser->>PersonalIdManager: Check login state
MessagingChannelsKeySyncWorker->>PersonalIdManager: Check login state
PushNotificationApiHelper-->>PushNotificationApiHelper: Skip response processing when signed out
ConnectReleaseTogglesParser-->>ConnectReleaseTogglesParser: Skip toggle persistence when signed out
MessagingChannelsKeySyncWorker-->>MessagingChannelsKeySyncWorker: Skip channel synchronization when signed out
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## commcare_2.64 #3863 +/- ##
===================================================
- Coverage 27.37% 27.36% -0.01%
- Complexity 4789 4790 +1
===================================================
Files 988 988
Lines 58947 58982 +35
Branches 7029 7036 +7
===================================================
+ Hits 16135 16140 +5
- Misses 40864 40892 +28
- Partials 1948 1950 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Trigger on Unlock: Every successful biometric unlock enqueues the Notification and Connect Toggle workers. The PeriodicWorkRequest is built with no initial delay, making it eligible to execute immediately upon enqueueing during the biometric unlock step. When the user navigates to "Manage Profile" (after biometric unlock) and triggers "Forgot Personal ID", the app attempts to cancel these workers. However, NotificationsSyncWorker is a CoroutineWorker executing blocking I/O calls (withContext(Dispatchers.IO)). Because coroutine cancellation is cooperative, the blocking network/DB operations do not observe cancellation immediately and complete in-flight tasks anyway. Reproducible: If the user presses "Forgot Personal ID" immediately after biometric unlock (in-flight worker task will cause this issue). Non-Reproducible: If the user pauses long enough, on the PersonalId Profile screen, to allow in-flight calls to finish before pressing "Forgot Personal ID". I think changing the ExistingPeriodicWorkPolicy should work here. to Also, needs to be corrected for Connect's Heartbeat CC @shubham1g5 |
|
@Jignesh-dimagi Agree that seems like a good change to make on this PR. Seems like we should also modify |
REPLACE cancels and re-enqueues, so each biometric unlock made the notification retrieval and Connect heartbeat workers eligible to run immediately. A run started that way can still be in flight when the user signs out moments later, and lands on a deleted Connect DB. UPDATE keeps the existing schedule while still applying changes to the request. Applied to the release toggle worker too (was KEEP), so all three periodic Connect workers behave consistently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Done in e58f3ce
Added a QA note to |
|
With these changes, I'm now seeing a new issue where forgetting PersonalID incorrectly drops me on a CC app home page. Will share a video with you privately to avoid leaking my email address |
Ah, good find! I think this surfaces a new issue that we'll want to address, although I'm not sure whether to include it in this PR. All in all, I think the issue is that forgetting PersonalID never currently forces any app navigation but sometimes it should. In the case of App Home, we should probably logout of the app and navigate to Login. But automatically logging out of the app (the App Home case) could be risky, for instance if the user still has un-synced forms. I wonder if we should consider disabling the Forget button while in an app? Or we could leave it enabled and show the user a message telling them to logout of their app first if they try to forget their account while on App Home (thinking the disabled button could otherwise be confusing). @shubham1g5 I'm thinking you probably have good insight here. Thinking a bit more, although maybe this gets fancier than we need... If we're on App Home for a non-Connect app at the time the user forgets PersonalID, we don't need to logout of the app. Finally, flagging that this is a separate issue unrelated to and currently unaffected by this PR, so we could consider merging these changes and tackling the additional issue in a new ticket. |
conroy-ricketts
left a comment
There was a problem hiding this comment.
Thanks for the context, and agreed, this fix is time-sensitive
|
We may want to provide some of that context in the ticket as well when this gets merged so that QA is not caught off guard |
Ahh think that's alright, an app can have un-synced forms even where user is logged out. And technically it'd be possible for user to recover that data if they sign back into PersonalID account on the same app installation. It'd be nice for us to warn user when they click Forget but think that's most we should do here. |
QA-8627
First of two PRs. This one gates the Connect write sites on the sign-in status; #3860 stacks the storage-layer hardening on top.
Product Description
Signing out of PersonalID could leave the nav drawer showing "Logged out of PersonalID — A problem occurred, please configure your PersonalID account again", instead of the sign-in and register options. The error persisted for 24 hours or until dismissed. Sign-out now leaves the drawer in the correct signed-out state.
Technical Summary
The trigger is a stale write: an in-flight Connect request (notification retrieval, release toggles) lands after signing out has deleted the Connect DB and its passphrase.
ConnectDatabaseHelper.getHandle()then can't open the DB, flagsdbBrokenand raisesPERSONALID_GENERIC_ERROR, which the exception handler persists as aGlobalErrorRecordfor the drawer to display.Connect storage is only valid while signed in, so the callers violating that contract now check before they write:
PushNotificationApiHelper.callPushNotificationApiresumes with an empty list instead of storing retrieved notifications and acknowledging them.ConnectReleaseTogglesParserstill parses the response and returns it, it just doesn't persist it.MessagingChannelsKeySyncWorkerread Connect storage with no check at all, and now bails out.Two supporting changes are what make those checks mean anything:
forgetUserflips the status toNotIntroducedbefore deleting the DB rather than after. Previously a callback landing between the delete and the status change would pass its check and hit a deleted DB. Nothing between the two reads the DB, but the ordering is load-bearing.personalIdSatusis nowvolatile, since every one of these checks runs on a worker thread while sign-out writes the field from the UI thread.Safety Assurance
Safety story
What gives me confidence:
PushNotificationActivityTestsuite passes against the new gate.Risks to review:
forgetUser.isloggedIn()gate was preferred over tracking that case — but it is a real behavior change.PushNotificationActivityTest's click-gating test had to be adjusted for it: it previously loaded notifications while registering, and now loads signed in and drops the login only for the click it actually covers.NotificationsSyncWorkeris guarded in the follow-up. Not guarded anywhere:ConnectOpportunitiesParser(reachable from the sameNotificationsSyncWorkerviasyncOpportunities) and the remainingconnectIdresponse parsers. Those can still land post-sign-out.ConnectOpportunitiesParserwas left out because its tests assertstoreJobsis always called and would all need updating; worth deciding whether to widen this or track it separately. Note that the follow-up PR makes the consequence non-fatal for all of them, guarded or not.Automated test coverage
ConnectReleaseTogglesParserTestgains two cases: toggles are stored while signed in, and are parsed but not persisted once signed out — the stale-write path this PR fixes.The race window itself is not covered; the failure needs a response to arrive inside a narrow window after sign-out, which the existing test harness cannot schedule. The follow-up PR covers the interleaving with a latch-based concurrency test.