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..cbf7d158716 100644
--- a/js/src/modals/CreateAccount/NewAccount/newAccount.js
+++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js
@@ -15,7 +15,7 @@
// 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';
diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
index dc80e27ae43..9d76cebfaaf 100644
--- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
+++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
@@ -88,12 +88,13 @@ 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..6bfe681b941
--- /dev/null
+++ b/js/src/ui/Form/PasswordStrength/passwordStrength.js
@@ -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 .
+
+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 (
+
+
+
+
+ { 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 { suggestions = [] } = feedback;
+
+ return (
+
+
+ { suggestions.join(' ') }
+
+
+ );
+ }
+
+ getStrengthBarColor (score) {
+ switch (score) {
+ case 4:
+ case 3:
+ return 'lightgreen';
+
+ case 2:
+ return 'yellow';
+
+ case 1:
+ return 'orange';
+
+ default:
+ return 'red';
+ }
+ }
+}
diff --git a/js/src/ui/Form/PasswordStrength/passwordStrength.spec.js b/js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
new file mode 100644
index 00000000000..ac616a87b9b
--- /dev/null
+++ b/js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
@@ -0,0 +1,62 @@
+// 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import PasswordStrength from './passwordStrength';
+
+const INPUT_A = 'l33t_test';
+const INPUT_B = 'Fu£dk;s$%kdlaOe9)_';
+const INPUT_NULL = '';
+
+function render (props) {
+ return shallow(
+
+ ).shallow();
+}
+
+describe('ui/Form/PasswordStrength', () => {
+ describe('rendering', () => {
+ it('renders', () => {
+ expect(render({ input: INPUT_A })).to.be.ok;
+ });
+
+ it('renders a linear progress', () => {
+ expect(render({ input: INPUT_A }).find('LinearProgress')).to.be.ok;
+ });
+
+ describe('compute strength', () => {
+ it('has low score with empty input', () => {
+ expect(
+ render({ input: INPUT_NULL }).find('LinearProgress').props().value
+ ).to.equal(20);
+ });
+
+ it('has medium score', () => {
+ expect(
+ render({ input: INPUT_A }).find('LinearProgress').props().value
+ ).to.equal(60);
+ });
+
+ it('has high score', () => {
+ expect(
+ render({ input: INPUT_B }).find('LinearProgress').props().value
+ ).to.equal(100);
+ });
+ });
+ });
+});
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,