Feature/auth issues 208 211 - #337
Open
Silasdotmd wants to merge 4 commits into
Open
Conversation
…col#208) Adds GET /auth/sessions plus DELETE /auth/sessions/{id} and DELETE /auth/sessions to the API contract, and surfaces them as an "Active Sessions" screen on both platforms showing device/platform/last-active time with biometric-gated "Sign out this device" / "Sign out all other devices" actions.
ethos-protocol#209) iOS's AuthStore already scheduled a refresh timer ahead of token expiry; Android's ApiClient only refreshed lazily right before the next request, so a foregrounded-but-idle app could sit on a near-expired token indefinitely. AuthViewModel now polls every 30s while signed in and refreshes once within the existing 60s isNearExpiry margin, matching iOS's lead time. Documents the shared 60s refresh margin in the API contract.
…ics (ethos-protocol#210) Registration on both platforms only handled the happy path where the device has a biometric enrolled. A device with none enrolled (or an MDM policy disabling it, or no passcode set) surfaced a generic CredentialManager/ASAuthorization failure instead of guidance. Android's register() now runs through the same CreateCredentialException mapping already used by the recovery path; iOS detects the wrapped LAError (biometryNotEnrolled/biometryNotAvailable/passcodeNotSet) and returns a dedicated PasskeyError.biometricUnavailable case. Both point the user at enrolling a biometric or falling back to a device PIN/passcode.
…ors (ethos-protocol#211) Neither platform had a test for a recovery token/proof expiring between initiate and complete, and a 401 on that call fell back to the generic "Authentication required" message with no path forward. Both clients' 401 handling now surfaces the server's human-readable error body when present (falling back to the existing generic message when it's empty, as on an ordinary rejected session token). Android's recovery flow (models, ApiClient, PasskeyService already existed but were never wired up) is connected end-to-end through AuthViewModel and the existing RecoverySheet, resetting to the "send a new code" step on a rejected token instead of leaving the user on a dead end.
|
@Silasdotmd 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses four Stellar Wave auth-hardening issues. Each is its own commit.
#208 — Add Session/Device List with Remote Sign-Out
Adds GET /auth/sessions, DELETE /auth/sessions/{id}, and DELETE /auth/sessions to the API contract. Both apps now have an "Active Sessions" screen (iOS: SettingsView → SessionsView; Android: new sessions nav route from the vault list top bar) showing device/platform/last-active time, with biometric-gated "Sign out this device" and "Sign out all other devices" actions.
#209 — Implement Silent Token Refresh Before Expiry Using /auth/refresh Proactively
Audited both clients: iOS's AuthStore already scheduled a refresh timer ahead of expiry; Android's ApiClient only refreshed lazily right before the next outgoing request, so a foregrounded-but-idle app could sit on a near-expired token indefinitely. AuthViewModel now polls every 30s while signed in and refreshes within the existing 60s isNearExpiry margin. The shared 60s refresh margin is now documented in shared/api-contract.md.
#210 — Add Passkey Registration Fallback Copy for Devices Without Biometric Hardware
Registration on both platforms only handled the happy path. Android's register() now runs through the same CreateCredentialException mapping the recovery path already had, with copy naming both the biometric-enrollment and PIN/passcode-backed-passkey options. iOS detects the wrapped LAError (biometryNotEnrolled / biometryNotAvailable / passcodeNotSet) and returns a new PasskeyError.biometricUnavailable case with the same guidance.
#211 — Add Automated Tests for /auth/recover/link Token Expiry Handling
Neither platform had a test for a recovery token/proof expiring between initiate and complete, and a 401 here fell back to a generic "Authentication required" with no path forward. Both clients now surface the server's human-readable {"error": "..."} body on a 401 instead of the generic message (ordinary rejected-session 401s are unaffected — they carry no body). Android's recovery flow (models, ApiClient, PasskeyService already existed but were never wired to the UI) is connected end-to-end through AuthViewModel and the existing RecoverySheet, resetting to the "send a new code" step on a rejected token.
Test plan
iOS: SessionsStoreTests, APIClientTests (sessions + recovery-expiry cases), AuthStoreTests (refresh + recovery-expiry cases), PasskeyServiceTests (biometric-unavailable mapping)
Android: SessionsViewModelTest, ApiClientTest (401 body-message cases), AuthViewModelTest (scheduled refresh + recovery cases), PasskeyServiceTest (recovery + no-biometric cases)
Manual: exercise the new Active Sessions screen and "Lost your device?" recovery flow on a device/simulator
Closes #208
Closes #209
Closes #210
Closes #211