Skip to content

Commit

Permalink
Auto logout after specific time (#6558)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
chikeichan authored May 8, 2019
1 parent 0497d20 commit 56ed189
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 6 deletions.
6 changes: 6 additions & 0 deletions app/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
31 changes: 28 additions & 3 deletions ui/app/pages/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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} />
Expand All @@ -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 () {
Expand Down Expand Up @@ -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,
Expand All @@ -344,6 +361,7 @@ Routes.propTypes = {
t: PropTypes.func,
providerId: PropTypes.string,
providerRequests: PropTypes.array,
autoLogoutTimeLimit: PropTypes.number,
}

function mapStateToProps (state) {
Expand All @@ -358,6 +376,7 @@ function mapStateToProps (state) {
} = appState

const accounts = getMetaMaskAccounts(state)
const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)

const {
identities,
Expand Down Expand Up @@ -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,
Expand All @@ -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())
},
}
}

Expand Down
45 changes: 45 additions & 0 deletions ui/app/pages/settings/advanced-tab/advanced-tab.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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

Expand All @@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
{ this.renderAutoLogoutTimeLimit() }
</div>
)
}
Expand Down
11 changes: 8 additions & 3 deletions ui/app/pages/settings/advanced-tab/advanced-tab.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,30 @@ 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: {
sendHexData,
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)),
Expand All @@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => {
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
setAutoLogoutTimeLimit: value => {
return dispatch(setAutoLogoutTimeLimit(value))
},
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
})
})
Original file line number Diff line number Diff line change
@@ -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')
})
})
5 changes: 5 additions & 0 deletions ui/app/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
setAutoLogoutTimeLimit,

// Migration of users to new UI
setCompletedUiMigration,
Expand Down Expand Up @@ -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())
Expand Down

0 comments on commit 56ed189

Please sign in to comment.