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
feature/default_network_editable
Chi Kei Chan 6 years ago committed by GitHub
parent 0497d209b2
commit 56ed189aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      app/_locales/en/messages.json
  2. 5
      package-lock.json
  3. 1
      package.json
  4. 31
      ui/app/pages/routes/index.js
  5. 45
      ui/app/pages/settings/advanced-tab/advanced-tab.component.js
  6. 11
      ui/app/pages/settings/advanced-tab/advanced-tab.container.js
  7. 44
      ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
  8. 46
      ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
  9. 5
      ui/app/store/actions.js

@ -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

@ -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…
Cancel
Save