Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Add a password strength component (#4153)
Browse files Browse the repository at this point in the history
* Added new PasswordStrength Component

* Added tests

* PR Grumbles
  • Loading branch information
ngotchac authored and arkpar committed Jan 16, 2017
1 parent afbcb22 commit 7429239
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 33 deletions.
3 changes: 2 additions & 1 deletion js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
2 changes: 1 addition & 1 deletion js/src/modals/CreateAccount/NewAccount/newAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

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';

Expand Down
11 changes: 6 additions & 5 deletions js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ export default class RecoveryPhrase extends Component {
value={ password2 }
onChange={ this.onEditPassword2 } />
</div>
<Checkbox
className={ styles.checkbox }
label='Key was created with Parity <1.4.5 on Windows'
checked={ windowsPhrase }
onCheck={ this.onToggleWindowsPhrase } />
</div>
<Checkbox
className={ styles.checkbox }
label='Key was created with Parity <1.4.5 on Windows'
checked={ windowsPhrase }
onCheck={ this.onToggleWindowsPhrase }
/>
</Form>
);
}
Expand Down
56 changes: 32 additions & 24 deletions js/src/modals/CreateAccount/createAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -100,52 +101,54 @@ export default class CreateAccount extends Component {
switch (stage) {
case 0:
return (
<CreationType
onChange={ this.onChangeType } />
<CreationType onChange={ this.onChangeType } />
);

case 1:
if (createType === 'fromNew') {
return (
<NewAccount
onChange={ this.onChangeDetails } />
<NewAccount onChange={ this.onChangeDetails } />
);
} else if (createType === 'fromGeth') {
}

if (createType === 'fromGeth') {
return (
<NewGeth
accounts={ accounts }
onChange={ this.onChangeGeth } />
onChange={ this.onChangeGeth }
/>
);
} else if (createType === 'fromPhrase') {
}

if (createType === 'fromPhrase') {
return (
<RecoveryPhrase
onChange={ this.onChangeDetails } />
<RecoveryPhrase onChange={ this.onChangeDetails } />
);
} else if (createType === 'fromRaw') {
}

if (createType === 'fromRaw') {
return (
<RawKey
onChange={ this.onChangeDetails } />
<RawKey onChange={ this.onChangeDetails } />
);
}

return (
<NewImport
onChange={ this.onChangeWallet } />
<NewImport onChange={ this.onChangeWallet } />
);

case 2:
if (createType === 'fromGeth') {
return (
<AccountDetailsGeth
addresses={ this.state.gethAddresses } />
<AccountDetailsGeth addresses={ this.state.gethAddresses } />
);
}

return (
<AccountDetails
address={ this.state.address }
name={ this.state.name }
phrase={ this.state.phrase } />
phrase={ this.state.phrase }
/>
);
}
}
Expand Down Expand Up @@ -210,11 +213,14 @@ export default class CreateAccount extends Component {
}

return (
<Warning warning={
<FormattedMessage
id='createAccount.warning.insecurePassword'
defaultMessage='It is recommended that a strong password be used to secure your accounts. Empty and trivial passwords are a security risk.' />
} />
<Warning
warning={
<FormattedMessage
id='createAccount.warning.insecurePassword'
defaultMessage='It is recommended that a strong password be used to secure your accounts. Empty and trivial passwords are a security risk.'
/>
}
/>
);
}

Expand Down Expand Up @@ -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,
Expand All @@ -380,7 +386,9 @@ export default class CreateAccount extends Component {
phrase,
windowsPhrase: windowsPhrase || false,
rawKey
});
};

this.setState(nextState);
}

onChangeRaw = (canCreate, rawKey) => {
Expand Down
6 changes: 4 additions & 2 deletions js/src/modals/PasswordManager/passwordManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -120,7 +120,7 @@ class PasswordManager extends Component {
}

renderPage () {
const { busy, isRepeatValid, passwordHint } = this.store;
const { busy, isRepeatValid, newPassword, passwordHint } = this.store;

return (
<Tabs
Expand Down Expand Up @@ -236,6 +236,8 @@ class PasswordManager extends Component {
type='password' />
</div>
</div>

<PasswordStrength input={ newPassword } />
</div>
</Form>
</Tab>
Expand Down
17 changes: 17 additions & 0 deletions js/src/ui/Form/PasswordStrength/index.js
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

export default from './passwordStrength';
31 changes: 31 additions & 0 deletions js/src/ui/Form/PasswordStrength/passwordStrength.css
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

.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);
}
125 changes: 125 additions & 0 deletions js/src/ui/Form/PasswordStrength/passwordStrength.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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 <http://www.gnu.org/licenses/>.

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, 50, { 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 (
<div className={ styles.strength }>
<label className={ styles.label }>
<FormattedMessage
id='ui.passwordStrength.label'
defaultMessage='password strength'
/>
</label>
<LinearProgress
color={ color }
mode='determinate'
style={ BAR_STYLE }
value={ value }
/>
<div className={ styles.feedback }>
{ this.renderFeedback(feedback) }
</div>
</div>
);
}

// Note that the suggestions are in english, thus it wouldn't
// make sense to add translations to surrounding words
renderFeedback (feedback = {}) {
const { suggestions = [] } = feedback;

return (
<div>
<p>
{ suggestions.join(' ') }
</p>
</div>
);
}

getStrengthBarColor (score) {
switch (score) {
case 4:
case 3:
return 'lightgreen';

case 2:
return 'yellow';

case 1:
return 'orange';

default:
return 'red';
}
}
}
Loading

0 comments on commit 7429239

Please sign in to comment.