mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Auto logout after specific time (#6558)
* Add i18n strings * Finish Auto timeout * Fix linter * Fix copies * Add unit test to Advanced Tab component * Add back actions and container * Add basic test to ensure container completeness * No zero, fix linters * restrict negative in input
This commit is contained in:
parent
0497d209b2
commit
56ed189aeb
@ -154,6 +154,12 @@
|
||||
"attributions": {
|
||||
"message": "Attributions"
|
||||
},
|
||||
"autoLogoutTimeLimit": {
|
||||
"message": "Auto-Logout Timer (minutes)"
|
||||
},
|
||||
"autoLogoutTimeLimitDescription": {
|
||||
"message": "Set the number of idle time in minutes before Metamask automatically log out"
|
||||
},
|
||||
"available": {
|
||||
"message": "Available"
|
||||
},
|
||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -32939,6 +32939,11 @@
|
||||
"react-icon-base": "2.1.0"
|
||||
}
|
||||
},
|
||||
"react-idle-timer": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.2.5.tgz",
|
||||
"integrity": "sha512-8B/OwjG8E/DTx1fHYKTpZ4cnCbL9+LOc5I9t8SYe8tbEkP14KChiYg0xPIuyRpO33wUZHcgmQl93CVePaDhmRA=="
|
||||
},
|
||||
"react-input-autosize": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz",
|
||||
|
@ -155,6 +155,7 @@
|
||||
"react-dnd-html5-backend": "^7.4.4",
|
||||
"react-dom": "^15.6.2",
|
||||
"react-hyperscript": "^3.0.0",
|
||||
"react-idle-timer": "^4.2.5",
|
||||
"react-inspector": "^2.3.0",
|
||||
"react-markdown": "^3.0.0",
|
||||
"react-media": "^1.8.0",
|
||||
|
@ -3,9 +3,10 @@ import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
|
||||
import { compose } from 'recompose'
|
||||
import actions from '../../store/actions'
|
||||
import actions, {hideSidebar, hideWarning, lockMetamask} from '../../store/actions'
|
||||
import log from 'loglevel'
|
||||
import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors'
|
||||
import IdleTimer from 'react-idle-timer'
|
||||
import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors'
|
||||
|
||||
// init
|
||||
import FirstTimeFlow from '../first-time-flow'
|
||||
@ -98,7 +99,9 @@ class Routes extends Component {
|
||||
}
|
||||
|
||||
renderRoutes () {
|
||||
return (
|
||||
const { autoLogoutTimeLimit, lockMetamask } = this.props
|
||||
|
||||
const routes = (
|
||||
<Switch>
|
||||
<Route path={LOCK_ROUTE} component={Lock} exact />
|
||||
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
|
||||
@ -116,6 +119,19 @@ class Routes extends Component {
|
||||
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
|
||||
</Switch>
|
||||
)
|
||||
|
||||
if (autoLogoutTimeLimit > 0) {
|
||||
return (
|
||||
<IdleTimer
|
||||
onIdle={lockMetamask}
|
||||
timeout={autoLogoutTimeLimit * 1000 * 60}
|
||||
>
|
||||
{routes}
|
||||
</IdleTimer>
|
||||
)
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
onInitializationUnlockPage () {
|
||||
@ -322,6 +338,7 @@ Routes.propTypes = {
|
||||
networkDropdownOpen: PropTypes.bool,
|
||||
showNetworkDropdown: PropTypes.func,
|
||||
hideNetworkDropdown: PropTypes.func,
|
||||
lockMetamask: PropTypes.func,
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
dispatch: PropTypes.func,
|
||||
@ -344,6 +361,7 @@ Routes.propTypes = {
|
||||
t: PropTypes.func,
|
||||
providerId: PropTypes.string,
|
||||
providerRequests: PropTypes.array,
|
||||
autoLogoutTimeLimit: PropTypes.number,
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
@ -358,6 +376,7 @@ function mapStateToProps (state) {
|
||||
} = appState
|
||||
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)
|
||||
|
||||
const {
|
||||
identities,
|
||||
@ -409,6 +428,7 @@ function mapStateToProps (state) {
|
||||
Qr: state.appState.Qr,
|
||||
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
|
||||
providerId: getNetworkIdentifier(state),
|
||||
autoLogoutTimeLimit,
|
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
identities,
|
||||
@ -427,6 +447,11 @@ function mapDispatchToProps (dispatch, ownProps) {
|
||||
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
|
||||
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
|
||||
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
|
||||
lockMetamask: () => {
|
||||
dispatch(lockMetamask())
|
||||
dispatch(hideWarning())
|
||||
dispatch(hideSidebar())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,8 @@ export default class AdvancedTab extends PureComponent {
|
||||
setAdvancedInlineGasFeatureFlag: PropTypes.func,
|
||||
advancedInlineGas: PropTypes.bool,
|
||||
showFiatInTestnets: PropTypes.bool,
|
||||
autoLogoutTimeLimit: PropTypes.number,
|
||||
setAutoLogoutTimeLimit: PropTypes.func.isRequired,
|
||||
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent {
|
||||
)
|
||||
}
|
||||
|
||||
renderAutoLogoutTimeLimit () {
|
||||
const { t } = this.context
|
||||
const {
|
||||
autoLogoutTimeLimit,
|
||||
setAutoLogoutTimeLimit,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{ t('autoLogoutTimeLimit') }</span>
|
||||
<div className="settings-page__content-description">
|
||||
{ t('autoLogoutTimeLimitDescription') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-page__content-item">
|
||||
<div className="settings-page__content-item-col">
|
||||
<TextField
|
||||
type="number"
|
||||
id="autoTimeout"
|
||||
placeholder="5"
|
||||
value={this.state.autoLogoutTimeLimit}
|
||||
defaultValue={autoLogoutTimeLimit}
|
||||
onChange={e => this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })}
|
||||
fullWidth
|
||||
margin="dense"
|
||||
min={0}
|
||||
/>
|
||||
<button
|
||||
className="button btn-primary settings-tab__rpc-save-button"
|
||||
onClick={() => {
|
||||
setAutoLogoutTimeLimit(this.state.autoLogoutTimeLimit)
|
||||
}}
|
||||
>
|
||||
{ t('save') }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const { warning } = this.props
|
||||
|
||||
@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
{ this.renderAdvancedGasInputInline() }
|
||||
{ this.renderHexDataOptIn() }
|
||||
{ this.renderShowConversionInTestnets() }
|
||||
{ this.renderAutoLogoutTimeLimit() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -8,10 +8,11 @@ import {
|
||||
setFeatureFlag,
|
||||
showModal,
|
||||
setShowFiatConversionOnTestnetsPreference,
|
||||
setAutoLogoutTimeLimit,
|
||||
} from '../../../store/actions'
|
||||
import {preferencesSelector} from '../../../selectors/selectors'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
export const mapStateToProps = state => {
|
||||
const { appState: { warning }, metamask } = state
|
||||
const {
|
||||
featureFlags: {
|
||||
@ -19,17 +20,18 @@ const mapStateToProps = state => {
|
||||
advancedInlineGas,
|
||||
} = {},
|
||||
} = metamask
|
||||
const { showFiatInTestnets } = preferencesSelector(state)
|
||||
const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
|
||||
|
||||
return {
|
||||
warning,
|
||||
sendHexData,
|
||||
advancedInlineGas,
|
||||
showFiatInTestnets,
|
||||
autoLogoutTimeLimit,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
export const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
||||
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
|
||||
@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => {
|
||||
setShowFiatConversionOnTestnetsPreference: value => {
|
||||
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
|
||||
},
|
||||
setAutoLogoutTimeLimit: value => {
|
||||
return dispatch(setAutoLogoutTimeLimit(value))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import sinon from 'sinon'
|
||||
import { shallow } from 'enzyme'
|
||||
import AdvancedTab from '../advanced-tab.component'
|
||||
import TextField from '../../../../components/ui/text-field'
|
||||
|
||||
describe('AdvancedTab Component', () => {
|
||||
it('should render correctly', () => {
|
||||
const root = shallow(
|
||||
<AdvancedTab />,
|
||||
{
|
||||
context: {
|
||||
t: s => `_${s}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(root.find('.settings-page__content-row').length, 8)
|
||||
})
|
||||
|
||||
it('should update autoLogoutTimeLimit', () => {
|
||||
const setAutoLogoutTimeLimitSpy = sinon.spy()
|
||||
const root = shallow(
|
||||
<AdvancedTab
|
||||
setAutoLogoutTimeLimit={setAutoLogoutTimeLimitSpy}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
t: s => `_${s}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const autoTimeout = root.find('.settings-page__content-row').last()
|
||||
const textField = autoTimeout.find(TextField)
|
||||
|
||||
textField.props().onChange({ target: { value: 1440 } })
|
||||
assert.equal(root.state().autoLogoutTimeLimit, 1440)
|
||||
|
||||
autoTimeout.find('button').simulate('click')
|
||||
assert.equal(setAutoLogoutTimeLimitSpy.args[0][0], 1440)
|
||||
})
|
||||
})
|
@ -0,0 +1,46 @@
|
||||
import assert from 'assert'
|
||||
import { mapStateToProps, mapDispatchToProps } from '../advanced-tab.container'
|
||||
|
||||
const defaultState = {
|
||||
appState: {
|
||||
warning: null,
|
||||
},
|
||||
metamask: {
|
||||
featureFlags: {
|
||||
sendHexData: false,
|
||||
advancedInlineGas: false,
|
||||
},
|
||||
preferences: {
|
||||
autoLogoutTimeLimit: 0,
|
||||
showFiatInTestnets: false,
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('AdvancedTab Container', () => {
|
||||
it('should map state to props correctly', () => {
|
||||
const props = mapStateToProps(defaultState)
|
||||
const expected = {
|
||||
warning: null,
|
||||
sendHexData: false,
|
||||
advancedInlineGas: false,
|
||||
showFiatInTestnets: false,
|
||||
autoLogoutTimeLimit: 0,
|
||||
}
|
||||
|
||||
assert.deepEqual(props, expected)
|
||||
})
|
||||
|
||||
it('should map dispatch to props correctly', () => {
|
||||
const props = mapDispatchToProps(() => 'mockDispatch')
|
||||
|
||||
assert.ok(typeof props.setHexDataFeatureFlag === 'function')
|
||||
assert.ok(typeof props.setRpcTarget === 'function')
|
||||
assert.ok(typeof props.displayWarning === 'function')
|
||||
assert.ok(typeof props.showResetAccountConfirmationModal === 'function')
|
||||
assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function')
|
||||
assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function')
|
||||
assert.ok(typeof props.setAutoLogoutTimeLimit === 'function')
|
||||
})
|
||||
})
|
@ -316,6 +316,7 @@ var actions = {
|
||||
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
|
||||
setUseNativeCurrencyAsPrimaryCurrencyPreference,
|
||||
setShowFiatConversionOnTestnetsPreference,
|
||||
setAutoLogoutTimeLimit,
|
||||
|
||||
// Migration of users to new UI
|
||||
setCompletedUiMigration,
|
||||
@ -2439,6 +2440,10 @@ function setShowFiatConversionOnTestnetsPreference (value) {
|
||||
return setPreference('showFiatInTestnets', value)
|
||||
}
|
||||
|
||||
function setAutoLogoutTimeLimit (value) {
|
||||
return setPreference('autoLogoutTimeLimit', value)
|
||||
}
|
||||
|
||||
function setCompletedOnboarding () {
|
||||
return async dispatch => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
Loading…
Reference in New Issue
Block a user