Description
Author: Alex Ruzenhack [email protected]
Reviewers: André Carneiro, Pedro Ferreira
Motivation
Add an extra level of security for the user by protecting the wallet in case of inactivity, avoiding a malicious agent to operate the wallet in the absence of its owner.
Introduction
What inactivity means for our purpose? A person can be using the computer but not using the wallet; a person can be out of the computer and leave the wallet open; a person can have the wallet open as the primary window, but just looking the screen without interacting with the wallet.
"Activity" presumes the action of interacting with the wallet, if the interaction ceases then the wallet is not being used and it can be marked as inactive. However, how many times do we need to mark the wallet as inactive?
The time span of no interaction that characterizes inactivity in the wallet has an inverse relation with the protection of the wallet. We can say the smaller the time span, the more protected the wallet will be. On the other hand, the time span also has a direct relation with convenience. The smaller the time span, the smaller the convenience for the user.
The time span to lock the wallet should provide a new layer of protection for the user at their discretion of the desired convenience.
Proposed solution
Taking activity by interaction, we should monitor each interaction with the wallet: click
, touch
, roll
, type
, tab navigation
; establish an elapsed time for the last interaction and verify periodically if the elapsed time has reached the time span threshold for inactivity, then lock the wallet if reached.
Guide-level explanation
Global interaction event listener
We can set an event listener for each targeted interaction in the application element level, which means any event bubbling up will be captured if not halted by stopPropagation
. Every time an event is processed the interaction time is updated.
Last interaction state
We should create a state called lastInteractionAt
to hold the timestamp of the last interaction with the wallet in Redux.
Inactivity threshold state
We should create a state called inactivityThreshold
to hold the time in seconds that characterizes the inactivity in the wallet. This state can be populated with a default value of 300 seconds (5min).
Lock function
A function to lock the wallet is to be run as a callback for setTimeout, which should set timeout as inactivityThreshold
value. For every interaction, the timeout is canceled and set again with the current inactivityThreshold
value.
Hardware wallet lock
The lock function in this context will redirect the user to the screen wallet type selection.
Jitter protection
To avoid flooding the event processing a jitter must be placed in the event listeners. Or maybe not, to avoid over-engineering.
Reference-level Explanation
Global interaction state
We can add an event listener in the Root element by the component reference and use the hook useEffect
to add the listener and program its removal once the component is unmounted.
Last interaction state
We can initialize the state lastInteractionAt
in the initial state of Redux with the value of the timestamp in UTC as new Date().getTime()
.
const initialState = {
// last user interaction with the wallet
lastInteractionAt: new Date().getTime(),
...
}
Inactivity threshold state
We can initialize the state inactivityThreshold
with a default value that can be a configured constant as DEFAULT_INACTIVITY_THRESHOLD
with the value of 300
seconds.
const initialState = {
// last user interaction with the wallet
inactivityThreshold: DEFAULT_INACTIVITY_THRESHOLD,
...
}
Lock function
Software wallet
Should call the lock function from HathorLib.
Hardware wallet
Should navigate to the page "wallet type selection".
Interaction listener
The interaction listener should update lastInteractionAt
state and set the timeout for the lock function. However, for the lock function, it must be set only if the wallet status is ready
.
Hardware wallet
When the wallet is used with a device like Ledger, the wallet also listens to the device's events, therefore these events also need to be hooked to update lastInteractionAt
and schedule or reschedule the lock function.
Close event
When the Ledger device is inactive with Hathor App for 30 sec, an event of ledger:closed
(see ledger.js:124) is emitted in the electron. This event is captured on a listener defined at App.js
(see App.js:81), which updates the state ledgerWasClosed
.
- What is the meaning of
ledgerWasClosed: true
for the wallet renderization state? (see WalletType.js:92) - Does it impact on redirection to "wallet type selection"?
Assess the impact of Multiple Wallets PR
See feat: Add support for multiple wallets.
Software wallet
When the wallet is loaded the function hathorLib.wallet.markWalletAsStarted()
is called. Therefore there is no real change here because we already consider triggering the setTimeout
with the lock function once the wallet is ready/started.
See LoadWallet.js:104.
Hardware wallet
When initializing the wallet, in the handlePublicKeyData
logic there is a call to hathorLib.wallet.unlock()
, we may use it to trigger the first interaction with the wallet and set the setTimeout
with the lock function.
See StartHardwareWallet.js:129.
When adding a wallet
In this situation, the setTimout
for the lock function should be canceled, to avoid a lock call in the middle of a new wallet load.
See ChooseWallet.js:69(addWallet).
When going to hardware wallet
As this type of wallet if more ephemeral for the application, the setTimeout
in this situation also needs to be canceled.
See ChooseWallet.js:79(goToHardwareWallet).
Task Breakdown
- Implement a global event handler to capture user interaction with the wallet, update
lasInteractionAt
state, and schedule/reschedule the lock function. 1 dev day - Implement a selector for options of inactive threshold values in the settings page, and update the state
inactivityThreshold
. 1 dev day - Add documentation for feature usage from the user's perspective. 0.5 dev day
Future work
- Implement reactions for power-monitor events, like capturing the screen lock event to lock the wallet as well without the need to wait the inactivity time threshold.
- Should we implement the configurable
inactiveThreshold
? If so, let the user choose a larger time for inactivity depending on battery conditions.
Alternative solutions
If the meaning of “inactivity“ is other than “interaction” we could think of it as “visible activity” and use window information to handle inactivity.
Active window
Native solution through visibility of window
As documented in “Page visibility”, the visibility property is not consistent, therefore it will be tricky to implement any feature relying on it. I think it is better to avoid it.
Read at: BrowserWindow | Electron (electronjs.org)
External solution through **electron-active-window
package
The project https://github.com/nullxx/electron-active-window has few stars, and downloads, and the last maintenance was 15 months ago. Despite these facts, there is a testimony very interesting about compatibility with macOS and privacy settings.
We should consider this path only if we intend to give maintenance to the package.
Read at: Comparison with active-win
lib · Issue #3 · nullxx/electron-active-window (github.com)