From e875a9c410c8fe2b0f67de872f9cff3ea2539e6d Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 12 Jan 2017 16:03:56 +0100 Subject: [PATCH] Added new PasswordStrength Component --- js/package.json | 3 +- .../CreateAccount/NewAccount/newAccount.js | 5 +- js/src/modals/CreateAccount/RawKey/rawKey.js | 3 +- .../RecoveryPhrase/recoveryPhrase.js | 14 +- js/src/modals/CreateAccount/createAccount.js | 56 ++++---- .../modals/PasswordManager/passwordManager.js | 6 +- js/src/ui/Form/PasswordStrength/index.js | 17 +++ .../PasswordStrength/passwordStrength.css | 31 +++++ .../Form/PasswordStrength/passwordStrength.js | 129 ++++++++++++++++++ js/src/ui/index.js | 2 + 10 files changed, 230 insertions(+), 36 deletions(-) create mode 100644 js/src/ui/Form/PasswordStrength/index.js create mode 100644 js/src/ui/Form/PasswordStrength/passwordStrength.css create mode 100644 js/src/ui/Form/PasswordStrength/passwordStrength.js diff --git a/js/package.json b/js/package.json index e53e983ff39..6dabace1768 100644 --- a/js/package.json +++ b/js/package.json @@ -189,6 +189,7 @@ "valid-url": "1.0.9", "validator": "6.2.0", "web3": "0.17.0-beta", - "whatwg-fetch": "2.0.1" + "whatwg-fetch": "2.0.1", + "zxcvbn": "4.4.1" } } diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index ed2c24612a6..a1a60fb775d 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -15,11 +15,11 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import IconButton from 'material-ui/IconButton'; +import { IconButton } from 'material-ui'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import ActionAutorenew from 'material-ui/svg-icons/action/autorenew'; -import { Form, Input, IdentityIcon } from '~/ui'; +import { Form, Input, IdentityIcon, PasswordStrength } from '~/ui'; import ERRORS from '../errors'; @@ -90,6 +90,7 @@ export default class CreateAccount extends Component { onChange={ this.onEditPassword2 } /> + { this.renderIdentitySelector() } { this.renderIdentities() } diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js index d0b3a4c7118..9ac871baf6e 100644 --- a/js/src/modals/CreateAccount/RawKey/rawKey.js +++ b/js/src/modals/CreateAccount/RawKey/rawKey.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -import { Form, Input } from '~/ui'; +import { Form, Input, PasswordStrength } from '~/ui'; import styles from '../createAccount.css'; @@ -92,6 +92,7 @@ export default class RawKey extends Component { onChange={ this.onEditPassword2 } /> + ); } diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js index dc80e27ae43..bf3b0fdf804 100644 --- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js @@ -17,7 +17,7 @@ import React, { Component, PropTypes } from 'react'; import { Checkbox } from 'material-ui'; -import { Form, Input } from '~/ui'; +import { Form, Input, PasswordStrength } from '~/ui'; import styles from '../createAccount.css'; @@ -88,12 +88,14 @@ export default class RecoveryPhrase extends Component { value={ password2 } onChange={ this.onEditPassword2 } /> - + + ); } diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js index 53be1f91839..e1e6abc7192 100644 --- a/js/src/modals/CreateAccount/createAccount.js +++ b/js/src/modals/CreateAccount/createAccount.js @@ -16,6 +16,7 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; + import ActionDone from 'material-ui/svg-icons/action/done'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; @@ -100,44 +101,45 @@ export default class CreateAccount extends Component { switch (stage) { case 0: return ( - + ); case 1: if (createType === 'fromNew') { return ( - + ); - } else if (createType === 'fromGeth') { + } + + if (createType === 'fromGeth') { return ( + onChange={ this.onChangeGeth } + /> ); - } else if (createType === 'fromPhrase') { + } + + if (createType === 'fromPhrase') { return ( - + ); - } else if (createType === 'fromRaw') { + } + + if (createType === 'fromRaw') { return ( - + ); } return ( - + ); case 2: if (createType === 'fromGeth') { return ( - + ); } @@ -145,7 +147,8 @@ export default class CreateAccount extends Component { + phrase={ this.state.phrase } + /> ); } } @@ -210,11 +213,14 @@ export default class CreateAccount extends Component { } return ( - - } /> + + } + /> ); } @@ -371,7 +377,7 @@ export default class CreateAccount extends Component { } onChangeDetails = (canCreate, { name, passwordHint, address, password, phrase, rawKey, windowsPhrase }) => { - this.setState({ + const nextState = { canCreate, name, passwordHint, @@ -380,7 +386,9 @@ export default class CreateAccount extends Component { phrase, windowsPhrase: windowsPhrase || false, rawKey - }); + }; + + this.setState(nextState); } onChangeRaw = (canCreate, rawKey) => { diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index 9bd6c08df0e..fa2293057a4 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -23,7 +23,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { newError, openSnackbar } from '~/redux/actions'; -import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; +import { Button, Modal, IdentityName, IdentityIcon, PasswordStrength } from '~/ui'; import Form, { Input } from '~/ui/Form'; import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons'; @@ -120,7 +120,7 @@ class PasswordManager extends Component { } renderPage () { - const { busy, isRepeatValid, passwordHint } = this.store; + const { busy, isRepeatValid, newPassword, passwordHint } = this.store; return ( + + diff --git a/js/src/ui/Form/PasswordStrength/index.js b/js/src/ui/Form/PasswordStrength/index.js new file mode 100644 index 00000000000..54e3c3d3e88 --- /dev/null +++ b/js/src/ui/Form/PasswordStrength/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './passwordStrength'; diff --git a/js/src/ui/Form/PasswordStrength/passwordStrength.css b/js/src/ui/Form/PasswordStrength/passwordStrength.css new file mode 100644 index 00000000000..6512f3afe2d --- /dev/null +++ b/js/src/ui/Form/PasswordStrength/passwordStrength.css @@ -0,0 +1,31 @@ +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.strength { + margin-top: 1.25em; +} + +.feedback { + font-size: 0.75em; +} + +.label { + user-select: none; + line-height: 18px; + font-size: 12px; + color: rgba(255, 255, 255, 0.498039); +} diff --git a/js/src/ui/Form/PasswordStrength/passwordStrength.js b/js/src/ui/Form/PasswordStrength/passwordStrength.js new file mode 100644 index 00000000000..92b45c0e882 --- /dev/null +++ b/js/src/ui/Form/PasswordStrength/passwordStrength.js @@ -0,0 +1,129 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import { debounce } from 'lodash'; +import { LinearProgress } from 'material-ui'; +import { FormattedMessage } from 'react-intl'; +import zxcvbn from 'zxcvbn'; + +import styles from './passwordStrength.css'; + +const BAR_STYLE = { + borderRadius: 1, + height: 7, + marginTop: '0.5em' +}; + +export default class PasswordStrength extends Component { + + static propTypes = { + input: PropTypes.string.isRequired + }; + + state = { + strength: null + }; + + constructor (props) { + super(props); + + this.updateStrength = debounce(this._updateStrength, 500, { leading: true }); + } + + componentWillMount () { + this.updateStrength(this.props.input); + } + + componentWillReceiveProps (nextProps) { + if (nextProps.input !== this.props.input) { + this.updateStrength(nextProps.input); + } + } + + _updateStrength (input = '') { + const strength = zxcvbn(input); + this.setState({ strength }); + } + + render () { + const { strength } = this.state; + + if (!strength) { + return null; + } + + const { score, feedback } = strength; + + // Score is between 0 and 4 + const value = score * 100 / 5 + 20; + const color = this.getStrengthBarColor(score); + + return ( +
+ + +
+ { this.renderFeedback(feedback) } +
+
+ ); + } + + // Note that the suggestions are in english, thus it wouldn't + // make sense to add translations to surrounding words + renderFeedback (feedback = {}) { + const { crack_times_display } = this.state.strength; + const { suggestions = [] } = feedback; + + return ( +
+

+ It would take { crack_times_display.offline_slow_hashing_1e4_per_second } to crack this password. +  { suggestions.join(' ') } +

+
+ ); + } + + getStrengthBarColor (score) { + switch (score) { + case 4: + case 3: + return 'lightgreen'; + + case 2: + return 'yellow'; + + case 1: + return 'orange'; + + default: + case 0: + return 'red'; + } + } +} diff --git a/js/src/ui/index.js b/js/src/ui/index.js index da61cc9b926..79a7c7f7a90 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -43,6 +43,7 @@ import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal'; import muiTheme from './Theme'; import Page from './Page'; import ParityBackground from './ParityBackground'; +import PasswordStrength from './Form/PasswordStrength'; import ShortenedHash from './ShortenedHash'; import SignerIcon from './SignerIcon'; import Tags from './Tags'; @@ -91,6 +92,7 @@ export { muiTheme, Page, ParityBackground, + PasswordStrength, RadioButtons, ShortenedHash, Select,