App Configurable Auto lock - #905
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 723206ec5c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-905f7aa90c0219e6106c (SDF collaborators only — install instructions in the release description) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b7d43caee
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 996b9a9c67
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 820f7dbbe3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ed state mid-transaction
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 14fcdc9f42
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba81a3c341
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44f10d969a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5cf10fd718
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98de964087
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8281249b68
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e781e130ca
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fa28e85d0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
Pull request overview
Adds a configurable auto-lock feature to Freighter Mobile, including a “soft lock” overlay that preserves in-progress navigation state, plus native privacy shielding to prevent wallet content from appearing in the OS app switcher while backgrounded.
Changes:
- Introduces secure-storage-backed auto-lock timer persistence + background timestamp tracking, and integrates timer evaluation into auth checks.
- Adds a soft-lock overlay (keeps navigation tree mounted) and suppresses non-lock toasts while soft-locked; hardens signing paths against mid-await locking.
- Adds Settings UI for selecting the auto-lock timer, i18n strings, and extensive Jest coverage across the new behaviors.
Reviewed changes
Copilot reviewed 46 out of 48 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/services/autoLock.ts | New service for secure auto-lock settings, background timestamps, and hash-key TTL anchoring. |
| src/providers/ToastProvider.tsx | Suppresses most toasts while soft-locked (allows only lock-related IDs). |
| src/navigators/SettingsNavigator.tsx | Adds AutoLockTimerScreen route to Settings stack. |
| src/navigators/RootNavigator.tsx | Keeps authenticated stack mounted under soft lock; hides underlying tree from accessibility while soft-locked; hydrates auto-lock timer on init. |
| src/i18n/locales/en/translations.json | Adds English strings for Security auto-lock entry + timer screen. |
| src/i18n/locales/pt/translations.json | Adds Portuguese strings for Security auto-lock entry + timer screen. |
| src/hooks/useTokenFiatConverter/index.ts | Records user activity on keyboard input to avoid idle-lock during amount entry. |
| src/hooks/useManageTokens.ts | Re-checks unlocked state before signing after async tx build. |
| src/hooks/useGetActiveAccount.ts | Adds isWalletUnlocked guard and blocks signing when soft-locked. |
| src/hooks/useAuthCheck.ts | Adds background/foreground auto-lock evaluation, activity tracking, and privacy-shield coordination. |
| src/hooks/useAppOpenBiometricsLogin.ts | Removes prior app-open biometric flow (superseded by lock screen logic). |
| src/helpers/userActivity.ts | Adds a global bridge for recording user activity from non-PanResponder inputs. |
| src/helpers/privacyShield.ts | Adds JS wrapper for native privacy shield dismissal + visibility tracking. |
| src/ducks/preferences.ts | Adds autoLockTimer state + secure-mirror hydration/persist with rollback behavior. |
| src/ducks/auth.ts | Implements soft-lock state machine, timer-aware auth checks, and timer-aware hash-key TTL behavior. |
| src/config/routes.ts | Adds settings route type for AutoLockTimerScreen. |
| src/config/constants.ts | Adds auto-lock enums/durations/toast IDs and updates hash-key expiration semantics. |
| src/components/screens/SwapScreen/hooks/useSwapTransaction.ts | Blocks signing if a lock engaged after swap was prepared. |
| src/components/screens/SettingsScreen/SecurityScreen/SecurityScreen.tsx | Adds Security list item to navigate to auto-lock timer settings. |
| src/components/screens/SettingsScreen/SecurityScreen/AutoLockTimerScreen/index.ts | New barrel export for AutoLockTimerScreen. |
| src/components/screens/SettingsScreen/SecurityScreen/AutoLockTimerScreen/AutoLockTimerScreen.tsx | New UI for selecting auto-lock timer preset. |
| src/components/screens/SendScreen/screens/TransactionAmountScreen.tsx | Prevents false validation errors during rehydration; adds wallet-unlocked guard before signing. |
| src/components/screens/SendScreen/screens/SendCollectibleReview.tsx | Adds wallet-unlocked guard before signing collectible send. |
| src/components/screens/LockScreen.tsx | Refactors into reusable LockScreenContent; implements privacy-shield-aware biometric auto-prompt logic. |
| src/components/Modal.tsx | Auto-dismisses RN Modals on soft lock so they can’t cover the lock overlay. |
| src/components/LockScreenOverlay.tsx | New full-screen overlay for in-process soft lock. |
| src/components/App.tsx | Renders the soft-lock overlay above providers so it covers sheets/navigation. |
| jest.setup.js | Mocks isWalletUnlocked and adds keyboard-controller Jest mock. |
| jest.config.js | Adds react-native-keyboard-controller to transform ignore patterns. |
| ios/PrivacyShield.swift | Adds iOS native module used to dismiss privacy shield from JS. |
| ios/PrivacyShield.m | Objective-C bridge for the PrivacyShield Swift module. |
| ios/PrivacyShield.podspec | Defines local pod for the iOS PrivacyShield module. |
| ios/Podfile | Adds PrivacyShield local pod dependency. |
| ios/Podfile.lock | Locks PrivacyShield pod in iOS dependencies. |
| ios/freighter-mobile/AppDelegate.swift | Implements iOS privacy window shield and notification-driven dismissal. |
| android/app/src/main/java/org/stellar/freighterwallet/PrivacyShieldPackage.kt | Adds Android ReactPackage for the PrivacyShield module. |
| android/app/src/main/java/org/stellar/freighterwallet/PrivacyShieldModule.kt | Adds Android native module for hiding the privacy shield. |
| android/app/src/main/java/com/freightermobile/MainApplication.kt | Registers PrivacyShieldPackage. |
| android/app/src/main/java/com/freightermobile/MainActivity.kt | Implements Android privacy overlay + fallback timer; enables FLAG_SECURE. |
| tests/services/autoLock.test.ts | Unit tests for autoLock service behavior. |
| tests/providers/ToastProvider.test.tsx | Tests toast suppression behavior while soft-locked. |
| tests/hooks/useGetActiveAccountSigningGuard.test.tsx | Tests signing is blocked while soft-locked / not authenticated. |
| tests/hooks/useAuthCheck.test.tsx | Tests background/idle timer handling and interaction signals. |
| tests/ducks/preferences.test.ts | Tests secure-mirror hydration/persist/rollback for auto-lock timer setting. |
| tests/ducks/auth.test.ts | Extends auth duck tests to cover timer behavior and soft-lock funneling. |
| tests/components/screens/SettingsScreen/AutoLockTimerScreen.test.tsx | Tests timer options render + selection updates store state. |
| tests/components/screens/LockScreen.test.tsx | Tests biometric auto-prompt behavior and privacy-shield gating. |
| tests/components/LockScreenOverlay.test.tsx | Tests overlay mounts/unmounts based on soft-lock state. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The configurable auto-lock feature added a 24h soft-lock preset while the pre-existing 24h hard-expiry backstop (HASH_KEY_EXPIRATION_MS) was silently widened to 7 days so that preset's fast-unlock path stays reachable (the hard expiry is anchored at sign-in and checked before the soft timer, so a <=24h backstop fires first and the 24h preset could never fast-unlock). 7 days materially weakens the forced full re-auth window and matches nothing on the extension (which has no persisted-session hard expiry at all). 48h is the tight bound that still clears the 24h preset with headroom, restoring the backstop close to its long-standing 24h behavior. NONE still opts out via NEVER_EXPIRE_HASH_KEY_MS. Follow-up (separate PR): re-anchor the hard expiry on background/last-activity instead of sign-in so the two timers are commensurable and no fixed value is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
The Send, Collectible, and Swap confirmation guards previously threw
`new Error("Wallet is locked")` when an auto-lock engaged between opening
the review sheet and signing. That had two problems flagged in review:
- Send/Collectible: the throw was caught after `setIsProcessing(true)` and
the sheet was dismissed, but the catch never reset `isProcessing`, so the
user was stranded on the TransactionProcessingScreen "sending" spinner
after unlocking.
- Swap: the hard-coded English string became the error-toast title (the
catch surfaces `error.message`), i.e. non-localized UI text.
Being locked isn't a transaction failure, so all three now abort cleanly:
reset `isProcessing` and return, skipping the failure toast/analytics path.
The user simply lands back on the review screen after unlocking. In swap the
early return (vs throw) also keeps the fire-and-forget executeSwap() from
rejecting unhandled.
Addresses review comments on TransactionAmountScreen, SendCollectibleReview,
and useSwapTransaction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Aligns the mobile auto-lock preset set with the extension's VALID_AUTO_LOCK_TIMEOUT_MINUTES so both platforms offer identical options: 1 min, 5 min, 15 min, 30 min, 1 hr, 6 hr, 12 hr, 24 hr (default 12h). - Removes IMMEDIATELY (no extension equivalent; it was the only lock-on- background option) and its dedicated logic in useAuthCheck (background soft-lock branch) and getAuthStatus (the 0ms special case). - Removes NONE (no extension equivalent) and, with it, the whole timer-aware hash-key TTL machinery it existed to support: NEVER_EXPIRE_HASH_KEY_MS, getHashKeyExpirationMs, and applyAutoLockTimerToHashKey. All presets now share the single 48h hard-expiry backstop, stamped directly at sign-in / generateHashKey, so setAutoLockTimer just persists the preference (+ rollback on failure). - Adds FIVE_MINUTES and SIX_HOURS presets + en/pt translations. Simplifies the now-unconditional timer guards in getAuthStatus and useAuthCheck (all durations are positive) and updates the affected tests. Addresses the reviewer comments on the auto-lock option set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
- Forgot-password unreachable while soft-locked: Modal auto-dismisses on soft-lock, but the lock overlay's own ForgotPasswordWarningModal used that same behavior, so tapping "Forgot password?" from the overlay instantly dismissed the warning. Added an opt-out (`dismissOnSoftLock`) and set it on the forgot-password modal so the reset flow stays usable while locked. - Android accessibility leak: the overlay only set `accessibilityViewIsModal` (iOS). Wrapped the app tree in a SoftLockAccessibilityShield that also sets `accessibilityElementsHidden` (iOS) and `importantForAccessibility` (Android) while soft-locked, so TalkBack/VoiceOver can't traverse the wallet underneath the overlay. - Private key restored under lock: signIn's background getActiveAccount().then set `account` without re-checking isSoftLocked, so a lock engaging during the slow key derivation would repopulate the private key into a LOCKED store. Now drops the stale load when isSoftLocked. Addresses review comments on Modal/LockScreenOverlay and the post-unlock account-load race. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
- Toast above the lock overlay: showToast already blocked NEW toasts while soft-locked, but a toast already visible when auto-lock fired stayed rendered (the absolute ToastContainer sits above the in-tree overlay). ToastProvider now tracks isSoftLocked reactively and drops non-allowlisted toasts from the render, so nothing lingers over the lock screen. - setAutoLockTimer write races: rapid taps fired overlapping fire-and-forget mirror writes that could land out of order (mirror disagreeing with the latest selection), and a failed earlier write's rollback could clobber a newer selection. Writes are now serialized (each awaits the previous) and tagged, so rollback only applies when the selection is still current. - Stale backgrounded-at write: on a fast background->foreground bounce, recordBackgroundedAt could resolve after getAuthStatus already cleared the timestamp, leaving a stale one that could later lock. The background handler now clears it if the app has returned to the foreground by the time the write lands. Addresses the remaining lower-severity race comments (ToastProvider, preferences, useAuthCheck). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
The 48h hard-expiry backstop used `Date.now() > expiresAt`, with no defense against a backward clock change — rolling the device clock back below the key's expiry kept the cached derived-key material usable past the intended bound (the soft-timer path already guards this via getBackgroundedAt's future-timestamp check; the hard-expiry did not, an asymmetry). Stamp `generatedAt` when a hash key is created/refreshed and treat a key whose generatedAt is in the future as expired, so a clock rolled back below the key's creation time forces a full re-auth. generatedAt is optional, so keys persisted before this change fall back to the plain expiry check. Unlock still requires the password on every path, so this is defense-in-depth hardening, not an auth-bypass fix. Also normalizes a pre-existing single-quote lint error surfaced in the same test file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
The Test job failed on the single AboutScreen suite with "_reactNativeDeviceInfo.getBundleId is not a function". AboutScreen's import graph reaches helpers/isEnv, which calls getBundleId() at module load, but the test's local react-native-device-info mock only stubbed getVersion / getBuildNumber / hasNotch. Add getBundleId so the suite can load. Pre-existing test-infra gap (main has the same incomplete mock); surfaced on this branch after main was merged in. Unrelated to the auto-lock changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
At 48h (2x the 24h max preset), a loosely-active user on the 24h auto-lock preset — one whose usage gaps stay just under 24h, so the soft-lock never fires and never resets the sign-in-anchored hard-expiry — could hit a full password re-auth roughly every ~2 days. 72h (3x the max preset) widens the headroom so that pattern must persist a full three days to trip it, making a surprise re-auth unlikely for normal 24h-preset use. Still a stopgap for the sign-in-anchored design; the complete fix is to re-anchor the hard-expiry on activity/foreground (tracked as a follow-up), after which no fixed backstop value is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Under sign-in anchoring (#905), LOCKED deliberately outranked the expiry check so routine lock-screen expiry kept the fast unlock reachable. With the TTL re-anchored on activity, an expired key under a persisted soft lock means 72h of genuine idleness — the exact case AC2 requires to force the full password re-auth. getAuthStatus now reports it as HASH_KEY_EXPIRED (consuming the stale LOCKED marker), and the signIn fast path refuses to re-stamp an expired key, falling through to the full rebuild. Raised by Copilot on #993. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #627
Adds a configurable Auto-Lock to Settings → Security, so users decide how quickly the wallet re-locks itself: 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 6 hours, 12 hours, or 24 hours (default 12h). This is the exact same option set and default as the Freighter extension (
VALID_AUTO_LOCK_TIMEOUT_MINUTES,DEFAULT_AUTO_LOCK_TIMEOUT_MINUTES=720), so the two platforms behave identically. After the chosen time the user is asked for their password (or biometrics) againIt locks on inactive time, either on background or in the foreground without any actions. Tracking is done via gestures, presses and navigation.
The lock itself is a soft lock: the screen you were on stays mounted underneath a lock overlay, so after unlocking you land back exactly where you were instead of getting bounced to a reloaded home screen.
There are two independent timers. The soft auto-lock above governs how soon the wallet re-locks (fast unlock — the persisted key is reused, so you resume where you were). The hash-key hard-expiry (
HASH_KEY_EXPIRATION_MS) is a separate, coarser security backstop that caps how long derived key material may live in secure storage before it's discarded and you get a full re-authentication from your password — regardless of the soft-lock preset. This backstop is set to 72h: because it's anchored at sign-in and checked before the soft timer, it must exceed the largest preset (24h) or that preset's fast-unlock path could never run. 72h (3x the max preset) gives enough headroom that even a loosely-active user on the 24h preset — whose gaps stay just under 24h, so the soft-lock never fires and never resets this clock — is unlikely to hit a surprise full re-auth. (Resolves the reviewer comment onHASH_KEY_EXPIRATION_MS; earlier iterations were 24h, then briefly 7 days.)The hard-expiry is also hardened against clock rollback: each hash key records a
generatedAttimestamp, and a key whosegeneratedAtis in the future (device clock moved backward below the key's creation time) is treated as expired. This prevents a rolled-back clock from keeping cached key material usable past the backstop — mirroring the soft-timer's existing future-timestamp guard. (Unlock still verifies the password on every path, so this is defense-in-depth.)Follow-up (separate PR): re-anchor the hard expiry on background/last-activity instead of sign-in, so the two timers are commensurable and no fixed backstop value is needed.
biometric prompt only triggers when coming from background. signing is blocked until unlocked , toasts wait for loading+state to be rehydrated before deciding on showing or not to avoid false positive errors
for Added privacy, on the native layer it also adds a Privacy shield -> When app is in background, only the splash screen is shown, and on the native app switcher carousel menu, it does not show any information. When the app goes to background, it waits for the native JS layer to be ready (auth state and storage hydrated) to show again the lock screen or the home screen, avoiding exposing any info during this in-between state
📱 Tested iOS Build 1.19.26 (1781721439)
Android smoke tests:
Screen.Recording.2026-06-17.at.12.20.45.mov
Screen.Recording.2026-06-17.at.12.24.16.mov
Ios Tests:
Send flow / login with biometric / background / state preservation on reunlock / demonstration with timers
ScreenRecording_06-17-2026.14-07-55_1.mov
no timer / more lock and unlock / privacy shield preservation
ScreenRecording_06-17-2026.16-55-18_1.mov
no biometrics tests with swap and collectible flows
ScreenRecording_06-17-2026.16-59-12_1.MP4
One small addition post last build to reset the default timers on sing up / reimport
Screen.Recording.2026-06-17.at.18.06.29.mov
Checklist
PR structure
Testing
Release