diff options
author | Chi Kei Chan <chikeichan@gmail.com> | 2019-05-09 02:57:21 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-09 02:57:21 +0800 |
commit | 56ed189aeb4ebc8e5dff7a6886e07791ce6569bb (patch) | |
tree | ad3471ba0d715e9ae00c9aff2620992ae46435af | |
parent | 0497d209b2adb4ac026a54159069fd44aaace9f7 (diff) | |
download | tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.tar tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.tar.gz tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.tar.bz2 tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.tar.lz tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.tar.xz tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.tar.zst tangerine-wallet-browser-56ed189aeb4ebc8e5dff7a6886e07791ce6569bb.zip |
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
-rw-r--r-- | app/_locales/en/messages.json | 6 | ||||
-rw-r--r-- | package-lock.json | 5 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | ui/app/pages/routes/index.js | 31 | ||||
-rw-r--r-- | ui/app/pages/settings/advanced-tab/advanced-tab.component.js | 45 | ||||
-rw-r--r-- | ui/app/pages/settings/advanced-tab/advanced-tab.container.js | 11 | ||||
-rw-r--r-- | ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js | 44 | ||||
-rw-r--r-- | ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js | 46 | ||||
-rw-r--r-- | ui/app/store/actions.js | 5 |
9 files changed, 188 insertions, 6 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 184507cbb..7a5f9297c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -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" }, diff --git a/package-lock.json b/package-lock.json index de174b789..e26c37150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 0ab366c17..8d9da397d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index e38a6d6ce..7ff741405 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -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()) + }, } } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js index 14b9daae6..8d70cd2df 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js @@ -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> ) } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js index 69d7e07e6..bcac55f5e 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js @@ -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)) + }, } } diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js new file mode 100644 index 000000000..f81329533 --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js @@ -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) + }) +}) diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js new file mode 100644 index 000000000..62122073d --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js @@ -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') + }) +}) diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index e7b855119..f2a9ed08f 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -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()) |