Skip to content

Commit

Permalink
Allow setting custom auto-lock timer (#3477)
Browse files Browse the repository at this point in the history
Closes #3471

Adds a dropdown on settings to allow changing the maximum amount of time
the wallet can remain idle before locking signing. This new setting is
updated through an async thunk which triggers an event to also handle
the change on the keyring service. The data is persisted on preferences
db and exposed through redux to the UI.

## Testing Env

```
SUPPORT_CUSTOM_AUTOLOCK=true
```

## To Test
- [x] Import an account
- [x] Lock and unlock your wallet
- [x] Head to settings, change auto-lock time
- [x] Change your system's date or change the options
[here](https://github.com/tahowallet/extension/blob/be6fae78bd7949cd5e4d8f37c882152d00e8141d/ui/pages/Settings.tsx#L308),
test that different time options work correctly
- [x] If the wallet has been idle for 10 minutes and the current auto
lock setting changes to from 15 minutes to 5, the wallet should lock
itself.

Latest build:
[extension-builds-3477](https://github.com/tahowallet/extension/suites/13795656037/artifacts/764974743)
(as of Thu, 22 Jun 2023 16:11:15 GMT).
  • Loading branch information
jagodarybacka authored Jun 23, 2023
2 parents c05f338 + 86dd6d8 commit 2c1816b
Show file tree
Hide file tree
Showing 14 changed files with 310 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ SUPPORT_SWAP_QUOTE_REFRESH=false
SUPPORT_ACHIEVEMENTS_BANNER=false
SUPPORT_NFT_SEND=false
USE_MAINNET_FORK=false
ENABLE_UPDATED_DAPP_CONNECTIONS=false
ENABLE_UPDATED_DAPP_CONNECTIONS=false
16 changes: 15 additions & 1 deletion background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import {
toggleCollectAnalytics,
setShowAnalyticsNotification,
setSelectedNetwork,
setAutoLockInterval,
setShownDismissableItems,
dismissableItemMarkedAsShown,
} from "./redux-slices/ui"
Expand Down Expand Up @@ -302,7 +303,8 @@ export default class Main extends BaseService<never> {

static create: ServiceCreatorFunction<never, Main, []> = async () => {
const preferenceService = PreferenceService.create()
const internalSignerService = InternalSignerService.create()
const internalSignerService =
InternalSignerService.create(preferenceService)
const chainService = ChainService.create(
preferenceService,
internalSignerService
Expand Down Expand Up @@ -1502,6 +1504,14 @@ export default class Main extends BaseService<never> {
}
)

this.preferenceService.emitter.on(
"updateAutoLockInterval",
async (newTimerValue) => {
await this.internalSignerService.updateAutoLockInterval()
this.store.dispatch(setAutoLockInterval(newTimerValue))
}
)

this.preferenceService.emitter.on(
"initializeShownDismissableItems",
async (dismissableItems) => {
Expand Down Expand Up @@ -1785,6 +1795,10 @@ export default class Main extends BaseService<never> {
this.analyticsService.sendAnalyticsEvent(event)
}
})

uiSliceEmitter.on("updateAutoLockInterval", async (newTimerValue) => {
await this.preferenceService.updateAutoLockInterval(newTimerValue)
})
}

async updateAssetMetadata(
Expand Down
4 changes: 3 additions & 1 deletion background/redux-slices/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import to29 from "./to-29"
import to30 from "./to-30"
import to31 from "./to-31"
import to32 from "./to-32"
import to33 from "./to-33"

/**
* The version of persisted Redux state the extension is expecting. Any previous
* state without this version, or with a lower version, ought to be migrated.
*/
export const REDUX_STATE_VERSION = 32
export const REDUX_STATE_VERSION = 33

/**
* Common type for all migration functions.
Expand Down Expand Up @@ -76,6 +77,7 @@ const allMigrations: { [targetVersion: string]: Migration } = {
30: to30,
31: to31,
32: to32,
33: to33,
}

/**
Expand Down
39 changes: 39 additions & 0 deletions background/redux-slices/migrations/to-33.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { MINUTE } from "../../constants"

const DEFAULT_AUTOLOCK_INTERVAL = 60 * MINUTE

type OldState = {
ui: {
settings: {
[settingsKey: string]: unknown
}
[sliceKey: string]: unknown
}
[otherSlice: string]: unknown
}

type NewState = {
ui: {
settings: {
[settingsKey: string]: unknown
autoLockInterval: number
}
[sliceKey: string]: unknown
}
[otherSlice: string]: unknown
}

export default (prevState: Record<string, unknown>): NewState => {
const typedPrevState = prevState as OldState

return {
...prevState,
ui: {
...typedPrevState.ui,
settings: {
...typedPrevState.ui.settings,
autoLockInterval: DEFAULT_AUTOLOCK_INTERVAL,
},
},
}
}
30 changes: 30 additions & 0 deletions background/redux-slices/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { AccountSignerWithId } from "../signing"
import { AccountSignerSettings } from "../ui"
import { AccountState, addAddressNetwork } from "./accounts"
import { createBackgroundAsyncThunk } from "./utils"
import { UNIXTime } from "../types"
import { DEFAULT_AUTOLOCK_INTERVAL } from "../services/preferences/defaults"

export const defaultSettings = {
hideDust: false,
Expand All @@ -19,6 +21,7 @@ export const defaultSettings = {
showAnalyticsNotification: false,
showUnverifiedAssets: false,
hideBanners: false,
autoLockInterval: DEFAULT_AUTOLOCK_INTERVAL,
}

export type UIState = {
Expand All @@ -35,6 +38,7 @@ export type UIState = {
showAnalyticsNotification: boolean
showUnverifiedAssets: boolean
hideBanners: boolean
autoLockInterval: UNIXTime
}
snackbarMessage: string
routeHistoryEntries?: Partial<Location>[]
Expand All @@ -54,6 +58,7 @@ export type Events = {
newSelectedNetwork: EVMNetwork
updateAnalyticsPreferences: Partial<AnalyticsPreferences>
addCustomNetworkResponse: [string, boolean]
updateAutoLockInterval: number
}

export const emitter = new Emittery<Events>()
Expand Down Expand Up @@ -201,6 +206,12 @@ const uiSlice = createSlice({
) => {
return { ...state, accountSignerSettings: payload }
},
setAutoLockInterval: (state, { payload }: { payload: number }) => {
return {
...state,
settings: { ...state.settings, autoLockInterval: payload },
}
},
},
})

Expand All @@ -222,6 +233,7 @@ export const {
setRouteHistoryEntries,
setSlippageTolerance,
setAccountsSignerSettings,
setAutoLockInterval,
} = uiSlice.actions

export default uiSlice.reducer
Expand Down Expand Up @@ -295,6 +307,19 @@ export const addNetworkUserResponse = createBackgroundAsyncThunk(
}
)

export const updateAutoLockInterval = createBackgroundAsyncThunk(
"ui/updateAutoLockInterval",
async (newValue: string) => {
const parsedValue = parseInt(newValue, 10)

if (Number.isNaN(parsedValue) || parsedValue <= 1) {
throw new Error("Invalid value for auto lock timer")
}

emitter.emit("updateAutoLockInterval", parsedValue)
}
)

export const userActivityEncountered = createBackgroundAsyncThunk(
"ui/userActivityEncountered",
async (addressNetwork: AddressOnNetwork) => {
Expand Down Expand Up @@ -348,6 +373,11 @@ export const selectHideDust = createSelector(
(settings) => settings?.hideDust
)

export const selectAutoLockTimer = createSelector(
selectSettings,
(settings) => settings.autoLockInterval
)

export const selectSnackbarMessage = createSelector(
selectUI,
(ui) => ui.snackbarMessage
Expand Down
37 changes: 26 additions & 11 deletions background/services/internal-signer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ import { HexString, EIP712TypedData, UNIXTime } from "../../types"
import { SignedTransaction, TransactionRequestWithNonce } from "../../networks"

import BaseService from "../base"
import { FORK, MINUTE } from "../../constants"
import { FORK } from "../../constants"
import { ethersTransactionFromTransactionRequest } from "../chain/utils"
import { FeatureFlags, isEnabled } from "../../features"
import { AddressOnNetwork } from "../../accounts"
import logger from "../../lib/logger"

export const MAX_INTERNAL_SIGNERS_IDLE_TIME = 60 * MINUTE
export const MAX_OUTSIDE_IDLE_TIME = 60 * MINUTE
import PreferenceService from "../preferences"
import { DEFAULT_AUTOLOCK_INTERVAL } from "../preferences/defaults"

export enum SignerInternalTypes {
mnemonicBIP39S128 = "mnemonic#bip39:128",
Expand Down Expand Up @@ -170,12 +169,17 @@ export default class InternalSignerService extends BaseService<Events> {
*/
lastOutsideActivity: UNIXTime | undefined

static create: ServiceCreatorFunction<Events, InternalSignerService, []> =
async () => {
return new this()
}
#internalAutoLockInterval: UNIXTime = DEFAULT_AUTOLOCK_INTERVAL

private constructor() {
static create: ServiceCreatorFunction<
Events,
InternalSignerService,
[Promise<PreferenceService>]
> = async (preferenceService) => {
return new this(await preferenceService)
}

private constructor(private preferenceService: PreferenceService) {
super({
autolock: {
schedule: {
Expand All @@ -193,6 +197,10 @@ export default class InternalSignerService extends BaseService<Events> {
// goal is to have external viewers synced to internal state no matter what
// it is. Don't emit if there are no vaults to unlock.
await super.internalStartService()

this.#internalAutoLockInterval =
await this.preferenceService.getAutoLockInterval()

if ((await getEncryptedVaults()).vaults.length > 0) {
this.emitter.emit("locked", this.locked())
}
Expand All @@ -204,6 +212,13 @@ export default class InternalSignerService extends BaseService<Events> {
await super.internalStopService()
}

async updateAutoLockInterval(): Promise<void> {
this.#internalAutoLockInterval =
await this.preferenceService.getAutoLockInterval()

await this.autolockIfNeeded()
}

/**
* @return True if the service is locked, false if it is unlocked.
*/
Expand Down Expand Up @@ -351,9 +366,9 @@ export default class InternalSignerService extends BaseService<Events> {
const timeSinceLastActivity = now - this.lastActivity
const timeSinceLastOutsideActivity = now - this.lastOutsideActivity

if (timeSinceLastActivity >= MAX_INTERNAL_SIGNERS_IDLE_TIME) {
if (timeSinceLastActivity >= this.#internalAutoLockInterval) {
this.lock()
} else if (timeSinceLastOutsideActivity >= MAX_OUTSIDE_IDLE_TIME) {
} else if (timeSinceLastOutsideActivity >= this.#internalAutoLockInterval) {
this.lock()
}
}
Expand Down
Loading

0 comments on commit 2c1816b

Please sign in to comment.