Skip to content

Fix/issues 200 201 202 203 - #335

Open
yvl4life wants to merge 4 commits into
ethos-protocol:mainfrom
yvl4life:fix/issues-200-201-202-203
Open

Fix/issues 200 201 202 203#335
yvl4life wants to merge 4 commits into
ethos-protocol:mainfrom
yvl4life:fix/issues-200-201-202-203

Conversation

@yvl4life

Copy link
Copy Markdown

Summary
Closes four Stellar Wave parity/hardening issues, each in its own commit:

#201 — Persist OTP rate-limiter state across process death (iOS)
OTPRateLimiter kept failureCount/cooldownSecondsRemaining in-memory only, so an app relaunch mid-cooldown reset a user's failed-attempt count — letting an attacker bypass the throttle. Now persists the failure count and an absolute cooldown deadline (not a countdown) to UserDefaults and rehydrates on init, mirroring Android's SavedStateHandle-backed TwoFactorViewModel (#172).

#202 — Restore in-flight 2FA verify screen state (iOS)
TwoFactorSetupView lost all progress on process death mid-verification, forcing users back through method selection and a brand-new code send. Added PendingTwoFactorSessionStore, which persists only the selected method and a "code sent" flag (never the OTP or TOTP secret/provisioning URI) keyed by vault ID, with a 10-minute expiry so stale sessions aren't restored.

#203 — Regression guard for the duplicate check-in queue (iOS)
Commit 8d8d59d reconciled two duplicate, conflicting check-in queue implementations that had zero test coverage. Added behavioral tests for PendingCheckInStore/CheckInSyncTask, plus an assertion that CheckInSyncTask's default store is PendingCheckInStore.shared. Documented the canonical queue path in README's Offline Support section.

#200 — Cross-device backup/restore for Android
iOS syncs vault-ID → passkey-credential-ID mappings across devices via ICloudSyncService (NSUbiquitousKeyValueStore); Android had no equivalent. Added VaultAssociationStore, a dedicated SharedPreferences file included in Android's Auto Backup for Apps (data_extraction_rules.xml / backup_rules.xml), so it restores transparently on a new device signed into the same Google account — no new cloud API or consent flow needed. Only the non-secret mapping is ever backed up; auth tokens (EncryptedSharedPreferences) and the offline queue database are explicitly excluded. Cleared on sign-out, leaving the synced copy for other devices intact.

Notes / follow-ups
#200: wiring VaultAssociationStore.save() into the actual vault-creation flow (mirroring iOS's per-vault save(vaultID:credentialID:) calls) needs a persisted account-level credential ID on Android, which doesn't currently exist outside the passkey registration ceremony. Left as a natural follow-up rather than adding new persistence to the auth/token layer in this PR.
Test plan
iOS: run OTPRateLimiterTests, PendingTwoFactorSessionStoreTests, PendingCheckInStoreTests, CheckInSyncTaskTests
Android: run AuthViewModelTest, VaultAssociationStoreTest
Manually verify OTP cooldown survives a force-quit mid-cooldown
Manually verify 2FA setup resumes at the verify step after a force-quit post-code-send
Verify Android backup: adb shell bmgr backupnow com.ethosprotocol, reinstall, confirm vault_associations_sync restores while auth state does not
Closes #200
Closes #201
Closes #202
Closes #203

OTPRateLimiter previously kept failureCount and cooldownSecondsRemaining
as in-memory-only @published properties, so an app relaunch mid-cooldown
reset a user's failed-attempt count and let an attacker bypass the
throttle. Persist the failure count and an absolute cooldown deadline
(not a remaining-seconds counter) to UserDefaults and rehydrate/resume
the cooldown on init, mirroring Android's SavedStateHandle-backed
TwoFactorViewModel (ethos-protocol#172).

Closes ethos-protocol#201
TwoFactorSetupView lost all progress on process death mid-verification,
forcing the user back through method selection and a brand-new code
send even though a code had already been sent. Persist just the method
and a "code sent" flag (never the OTP or the TOTP secret/provisioning
URI) keyed by vault ID, with a 10-minute expiry so a stale session
isn't restored once the underlying code has almost certainly expired
server-side.

Closes ethos-protocol#202
8d8d59d reconciled two duplicate, conflicting offline check-in queue
systems (CheckInQueue/CheckInSyncService vs. PendingCheckInStore/
CheckInSyncTask) after the dead one shipped silently with zero test
coverage of either. Add behavioral tests for PendingCheckInStore and
CheckInSyncTask, plus an assertion that CheckInSyncTask's default store
is PendingCheckInStore.shared, so a reintroduced second queue would be
caught. Document the canonical queue path in README's Offline Support
section.

Closes ethos-protocol#203
…ssociations

PARITY.md marked cross-device sync as an Android gap: iOS syncs vault
ID -> passkey credential ID mappings across devices via ICloudSyncService
(NSUbiquitousKeyValueStore), Android had no equivalent. Add
VaultAssociationStore, a SharedPreferences-backed store for the same
non-secret mapping, and include only that file in Android's Auto Backup
for Apps (data_extraction_rules.xml / backup_rules.xml) so it restores
transparently on a new device signed into the same Google account —
auth tokens (EncryptedSharedPreferences) and the offline queue database
are explicitly excluded. Clear local associations on sign-out, mirroring
iOS's behavior of leaving the synced copy for other devices intact.

Closes ethos-protocol#200
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@yvl4life Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant