mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +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": {
|
"attributions": {
|
||||||
"message": "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": {
|
"available": {
|
||||||
"message": "Available"
|
"message": "Available"
|
||||||
},
|
},
|
||||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -32939,6 +32939,11 @@
|
|||||||
"react-icon-base": "2.1.0"
|
"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": {
|
"react-input-autosize": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz",
|
"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-dnd-html5-backend": "^7.4.4",
|
||||||
"react-dom": "^15.6.2",
|
"react-dom": "^15.6.2",
|
||||||
"react-hyperscript": "^3.0.0",
|
"react-hyperscript": "^3.0.0",
|
||||||
|
"react-idle-timer": "^4.2.5",
|
||||||
"react-inspector": "^2.3.0",
|
"react-inspector": "^2.3.0",
|
||||||
"react-markdown": "^3.0.0",
|
"react-markdown": "^3.0.0",
|
||||||
"react-media": "^1.8.0",
|
"react-media": "^1.8.0",
|
||||||
|
@ -3,9 +3,10 @@ import PropTypes from 'prop-types'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
|
import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
|
||||||
import { compose } from 'recompose'
|
import { compose } from 'recompose'
|
||||||
import actions from '../../store/actions'
|
import actions, {hideSidebar, hideWarning, lockMetamask} from '../../store/actions'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors'
|
import IdleTimer from 'react-idle-timer'
|
||||||
|
import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors'
|
||||||
|
|
||||||
// init
|
// init
|
||||||
import FirstTimeFlow from '../first-time-flow'
|
import FirstTimeFlow from '../first-time-flow'
|
||||||
@ -98,7 +99,9 @@ class Routes extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRoutes () {
|
renderRoutes () {
|
||||||
return (
|
const { autoLogoutTimeLimit, lockMetamask } = this.props
|
||||||
|
|
||||||
|
const routes = (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={LOCK_ROUTE} component={Lock} exact />
|
<Route path={LOCK_ROUTE} component={Lock} exact />
|
||||||
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
|
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
|
||||||
@ -116,6 +119,19 @@ class Routes extends Component {
|
|||||||
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
|
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (autoLogoutTimeLimit > 0) {
|
||||||
|
return (
|
||||||
|
<IdleTimer
|
||||||
|
onIdle={lockMetamask}
|
||||||
|
timeout={autoLogoutTimeLimit * 1000 * 60}
|
||||||
|
>
|
||||||
|
{routes}
|
||||||
|
</IdleTimer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
onInitializationUnlockPage () {
|
onInitializationUnlockPage () {
|
||||||
@ -322,6 +338,7 @@ Routes.propTypes = {
|
|||||||
networkDropdownOpen: PropTypes.bool,
|
networkDropdownOpen: PropTypes.bool,
|
||||||
showNetworkDropdown: PropTypes.func,
|
showNetworkDropdown: PropTypes.func,
|
||||||
hideNetworkDropdown: PropTypes.func,
|
hideNetworkDropdown: PropTypes.func,
|
||||||
|
lockMetamask: PropTypes.func,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
@ -344,6 +361,7 @@ Routes.propTypes = {
|
|||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
providerId: PropTypes.string,
|
providerId: PropTypes.string,
|
||||||
providerRequests: PropTypes.array,
|
providerRequests: PropTypes.array,
|
||||||
|
autoLogoutTimeLimit: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
@ -358,6 +376,7 @@ function mapStateToProps (state) {
|
|||||||
} = appState
|
} = appState
|
||||||
|
|
||||||
const accounts = getMetaMaskAccounts(state)
|
const accounts = getMetaMaskAccounts(state)
|
||||||
|
const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
identities,
|
identities,
|
||||||
@ -409,6 +428,7 @@ function mapStateToProps (state) {
|
|||||||
Qr: state.appState.Qr,
|
Qr: state.appState.Qr,
|
||||||
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
|
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
|
||||||
providerId: getNetworkIdentifier(state),
|
providerId: getNetworkIdentifier(state),
|
||||||
|
autoLogoutTimeLimit,
|
||||||
|
|
||||||
// state needed to get account dropdown temporarily rendering from app bar
|
// state needed to get account dropdown temporarily rendering from app bar
|
||||||
identities,
|
identities,
|
||||||
@ -427,6 +447,11 @@ function mapDispatchToProps (dispatch, ownProps) {
|
|||||||
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
|
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
|
||||||
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
|
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
|
||||||
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
|
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,
|
setAdvancedInlineGasFeatureFlag: PropTypes.func,
|
||||||
advancedInlineGas: PropTypes.bool,
|
advancedInlineGas: PropTypes.bool,
|
||||||
showFiatInTestnets: PropTypes.bool,
|
showFiatInTestnets: PropTypes.bool,
|
||||||
|
autoLogoutTimeLimit: PropTypes.number,
|
||||||
|
setAutoLogoutTimeLimit: PropTypes.func.isRequired,
|
||||||
setShowFiatConversionOnTestnetsPreference: 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 () {
|
renderContent () {
|
||||||
const { warning } = this.props
|
const { warning } = this.props
|
||||||
|
|
||||||
@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
|
|||||||
{ this.renderAdvancedGasInputInline() }
|
{ this.renderAdvancedGasInputInline() }
|
||||||
{ this.renderHexDataOptIn() }
|
{ this.renderHexDataOptIn() }
|
||||||
{ this.renderShowConversionInTestnets() }
|
{ this.renderShowConversionInTestnets() }
|
||||||
|
{ this.renderAutoLogoutTimeLimit() }
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,11 @@ import {
|
|||||||
setFeatureFlag,
|
setFeatureFlag,
|
||||||
showModal,
|
showModal,
|
||||||
setShowFiatConversionOnTestnetsPreference,
|
setShowFiatConversionOnTestnetsPreference,
|
||||||
|
setAutoLogoutTimeLimit,
|
||||||
} from '../../../store/actions'
|
} from '../../../store/actions'
|
||||||
import {preferencesSelector} from '../../../selectors/selectors'
|
import {preferencesSelector} from '../../../selectors/selectors'
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
export const mapStateToProps = state => {
|
||||||
const { appState: { warning }, metamask } = state
|
const { appState: { warning }, metamask } = state
|
||||||
const {
|
const {
|
||||||
featureFlags: {
|
featureFlags: {
|
||||||
@ -19,17 +20,18 @@ const mapStateToProps = state => {
|
|||||||
advancedInlineGas,
|
advancedInlineGas,
|
||||||
} = {},
|
} = {},
|
||||||
} = metamask
|
} = metamask
|
||||||
const { showFiatInTestnets } = preferencesSelector(state)
|
const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
warning,
|
warning,
|
||||||
sendHexData,
|
sendHexData,
|
||||||
advancedInlineGas,
|
advancedInlineGas,
|
||||||
showFiatInTestnets,
|
showFiatInTestnets,
|
||||||
|
autoLogoutTimeLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
export const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
||||||
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
|
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
|
||||||
@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => {
|
|||||||
setShowFiatConversionOnTestnetsPreference: value => {
|
setShowFiatConversionOnTestnetsPreference: value => {
|
||||||
return 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',
|
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
|
||||||
setUseNativeCurrencyAsPrimaryCurrencyPreference,
|
setUseNativeCurrencyAsPrimaryCurrencyPreference,
|
||||||
setShowFiatConversionOnTestnetsPreference,
|
setShowFiatConversionOnTestnetsPreference,
|
||||||
|
setAutoLogoutTimeLimit,
|
||||||
|
|
||||||
// Migration of users to new UI
|
// Migration of users to new UI
|
||||||
setCompletedUiMigration,
|
setCompletedUiMigration,
|
||||||
@ -2439,6 +2440,10 @@ function setShowFiatConversionOnTestnetsPreference (value) {
|
|||||||
return setPreference('showFiatInTestnets', value)
|
return setPreference('showFiatInTestnets', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAutoLogoutTimeLimit (value) {
|
||||||
|
return setPreference('autoLogoutTimeLimit', value)
|
||||||
|
}
|
||||||
|
|
||||||
function setCompletedOnboarding () {
|
function setCompletedOnboarding () {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user