From 3e21346889eb3cff958018f91bac0b8d236dacd5 Mon Sep 17 00:00:00 2001 From: petitPapillon Date: Sat, 24 Jun 2017 14:20:15 +0200 Subject: [PATCH 1/9] Wallet creation - enable the user to create a custom wallet seed --- react/src/components/login/login.js | 52 ++++++++++++- react/src/components/login/login.render.js | 87 +++++++++++++++------- react/src/components/login/login.scss | 5 ++ react/src/translate/en.js | 4 +- 4 files changed, 118 insertions(+), 30 deletions(-) diff --git a/react/src/components/login/login.js b/react/src/components/login/login.js index 7ca9981..e51d613 100644 --- a/react/src/components/login/login.js +++ b/react/src/components/login/login.js @@ -32,7 +32,9 @@ class Login extends React.Component { randomSeed: PassPhraseGenerator.generatePassPhrase(256), randomSeedConfirm: '', isSeedConfirmError: false, + isSeedBlank: false, displaySeedBackupModal: false, + customWalletSeed: false }; this.toggleActivateCoinForm = this.toggleActivateCoinForm.bind(this); this.updateInput = this.updateInput.bind(this); @@ -44,6 +46,31 @@ class Login extends React.Component { this.execWalletCreate = this.execWalletCreate.bind(this); } + isCustomWalletSeed() { + return this.state.customWalletSeed; + } + + toggleCustomWalletSeed() { + this.setState({ + customWalletSeed: !this.state.customWalletSeed + }, () => { + // if customWalletSeed is set to false, regenerate the seed + if (!this.state.customWalletSeed) { + this.setState({ + randomSeed: PassPhraseGenerator.generatePassPhrase(this.state.bitsOption), + isSeedConfirmError: false, + isSeedBlank: false + }); + } else { + // if customWalletSeed is set to true, reset to seed to an empty string + this.setState({ + randomSeed: '', + randomSeedConfirm: '' + }); + } + }); + } + openSyncOnlyModal() { Store.dispatch(getSyncOnlyForks()); @@ -74,6 +101,7 @@ class Login extends React.Component { this.setState(Object.assign({}, this.state, { randomSeed: PassPhraseGenerator.generatePassPhrase(bits), bitsOption: bits, + isSeedBlank: false })); } @@ -126,6 +154,15 @@ class Login extends React.Component { this.setState({ [e.target.name]: e.target.value, isSeedConfirmError: false, + isSeedBlank: this.isBlank(e.target.value) + }); + } + + updateWalletSeed(e) { + this.setState({ + randomSeed: e.target.value, + isSeedConfirmError: false, + isSeedBlank: this.isBlank(e.target.value) }); } @@ -157,15 +194,26 @@ class Login extends React.Component { } handleRegisterWallet() { - if (this.state.randomSeed === this.state.randomSeedConfirm) { + const enteredSeedsMatch = this.state.randomSeed === this.state.randomSeedConfirm; + const isSeedBlank = this.isBlank(this.state.randomSeed); + + if (enteredSeedsMatch && !isSeedBlank) { this.toggleSeedBackupModal(); - } else { + } else if (!enteredSeedsMatch) { this.setState({ isSeedConfirmError: true, }); + } else if (isSeedBlank) { + this.setState({ + isSeedBlank: true + }); } } + isBlank(str) { + return (!str || /^\s*$/.test(str)); + } + handleKeydown(e) { if (e.key === 'Enter') { this.loginSeed(); diff --git a/react/src/components/login/login.render.js b/react/src/components/login/login.render.js index 5a9b94d..4537ba3 100644 --- a/react/src/components/login/login.render.js +++ b/react/src/components/login/login.render.js @@ -106,42 +106,73 @@ const LoginRender = function () {

{ translate('INDEX.SELECT_SEED_TYPE') }:

-
-
this.generateNewSeed(256) }> - - -
-
this.generateNewSeed(160) }> - - + +
+
+
+ + +
this.toggleCustomWalletSeed() }> + { translate('LOGIN.CUSTOM_WALLET_SEED') } +
+
+
-
this.generateNewSeed(128) }> - - +
+ { !this.isCustomWalletSeed() && +
+
+
this.generateNewSeed(256) }> + + +
+
this.generateNewSeed(160) }> + + +
+
this.generateNewSeed(128) }> + + +
+
+
+ }
+
+ onChange={ (e) => this.updateWalletSeed(e) } + readOnly={ !this.isCustomWalletSeed() } + > @@ -151,8 +182,10 @@ const LoginRender = function () { className="form-control placeholder-no-fix height-100" type="text" name="randomSeedConfirm" + value={ this.state.randomSeedConfirm } onChange={ this.updateInput } id="rwalletseed"> + { translate('LOGIN.MUST_ENTER_SEED') }. { translate('LOGIN.ENTER_VALUE_AGAIN') }.
this.toggleCustomWalletSeed() }> { translate('LOGIN.CUSTOM_WALLET_SEED') }
From fd84260ef2fe7dbbc16ca77807d55f2589b9d616 Mon Sep 17 00:00:00 2001 From: petitPapillon Date: Mon, 26 Jun 2017 22:45:14 +0200 Subject: [PATCH 3/9] Wallet login - add seed type selection; check if entered seed fits the selected seed type --- react/src/components/login/login.js | 59 ++++++--- react/src/components/login/login.render.js | 131 +++++++++++++------ react/src/translate/en.js | 7 +- react/src/util/crypto/passphrasegenerator.js | 9 ++ 4 files changed, 151 insertions(+), 55 deletions(-) diff --git a/react/src/components/login/login.js b/react/src/components/login/login.js index cd631ab..5542cb8 100644 --- a/react/src/components/login/login.js +++ b/react/src/components/login/login.js @@ -1,21 +1,20 @@ import React from 'react'; -import { translate } from '../../translate/translate'; import { toggleAddcoinModal, iguanaWalletPassphrase, iguanaActiveHandle, startInterval, - stopInterval, getDexCoins, toggleSyncOnlyModal, getSyncOnlyForks, - createNewWallet + createNewWallet, + triggerToaster } from '../../actions/actionCreators'; import Store from '../../store'; -import { PassPhraseGenerator } from '../../util/crypto/passphrasegenerator'; - +import {PassPhraseGenerator} from '../../util/crypto/passphrasegenerator'; import SwallModalRender from './swall-modal.render'; import LoginRender from './login.render'; +import {translate} from '../../translate/translate'; const IGUNA_ACTIVE_HANDLE_TIMEOUT = 3000; const IGUNA_ACTIVE_COINS_TIMEOUT = 10000; @@ -28,6 +27,7 @@ class Login extends React.Component { activeLoginSection: 'activateCoin', loginPassphrase: null, seedInputVisibility: false, + loginPassPhraseBitsOption: 256, bitsOption: 256, randomSeed: PassPhraseGenerator.generatePassPhrase(256), randomSeedConfirm: '', @@ -107,16 +107,16 @@ class Login extends React.Component { componentWillReceiveProps(props) { if (props && - props.Main && - props.Main.isLoggedIn) { + props.Main && + props.Main.isLoggedIn) { this.setState({ display: false, }); } if (props && - props.Main && - !props.Main.isLoggedIn) { + props.Main && + !props.Main.isLoggedIn) { this.setState({ display: true, }); @@ -133,8 +133,8 @@ class Login extends React.Component { if (this.state.activeLoginSection !== 'signup') { if (props && - props.Main && - props.Main.activeCoins) { + props.Main && + props.Main.activeCoins) { this.setState({ activeLoginSection: 'login', }); @@ -167,11 +167,41 @@ class Login extends React.Component { } loginSeed() { + if (!PassPhraseGenerator.isPassPhraseValid(this.state.loginPassphrase, + this.state.loginPassPhraseBitsOption)) { + Store.dispatch(triggerToaster( + `${translate('LOGIN.SEED_NOT_OF_TYPE')} ${this.selectedLoginSeedTypeLabel()}`, + translate('LOGIN.INVALID_SEED'), + 'error', + false + )); + return; + } Store.dispatch( iguanaWalletPassphrase(this.state.loginPassphrase) ); } + selectedLoginSeedTypeLabel() { + if (this.state.loginPassPhraseBitsOption === 256) { + return translate('LOGIN.IGUANA_SEED'); + } + + if (this.state.loginPassPhraseBitsOption === 160) { + return translate('LOGIN.WAVES_SEED'); + } + + if (this.state.loginPassPhraseBitsOption === 128) { + return translate('LOGIN.NXT_SEED'); + } + } + + onLoginPassPhraseBitsOptionChange(bitsOption) { + this.setState({ + loginPassPhraseBitsOption: +bitsOption + }); + } + updateActiveLoginSection(name) { // reset login/create form this.setState({ @@ -184,8 +214,8 @@ class Login extends React.Component { isSeedConfirmError: false, isSeedBlank: false, displaySeedBackupModal: false, - customWalletSeed: false - }); + customWalletSeed: false + }); } execWalletCreate() { @@ -245,8 +275,7 @@ class Login extends React.Component { } render() { - if ((this.state && this.state.display) || - !this.props.Main) { + if ((this.state && this.state.display) || !this.props.Main) { return LoginRender.call(this); } diff --git a/react/src/components/login/login.render.js b/react/src/components/login/login.render.js index f19596b..4e2474f 100644 --- a/react/src/components/login/login.render.js +++ b/react/src/components/login/login.render.js @@ -51,41 +51,88 @@ const LoginRender = function () {

{translate('INDEX.WELCOME_LOGIN')}

-
- this.handleKeydown(event) } /> - - +
+

+ { translate('INDEX.SELECT_SEED_TYPE') }: +

+
+
+ this.onLoginPassPhraseBitsOptionChange(e.target.value) } + /> + +
+
+ this.onLoginPassPhraseBitsOptionChange(e.target.value) } + /> + +
+
+ this.onLoginPassPhraseBitsOptionChange(e.target.value) } + /> + +
+
+
+
+ this.handleKeydown(event) }/> + + +
+ +
-
- - - -
+ className="btn btn-lg btn-flat btn-block waves-effect" + id="register-btn" + onClick={ () => this.updateActiveLoginSection('signup') }>{ translate('INDEX.CREATE_WALLET') } + +
@@ -137,8 +184,10 @@ const LoginRender = function () { - + checked={ this.state.bitsOption === 256 }/> +
- + checked={ this.state.bitsOption === 160 }/> +
- + checked={ this.state.bitsOption === 128 }/> +
diff --git a/react/src/translate/en.js b/react/src/translate/en.js index a5e3184..7551eef 100644 --- a/react/src/translate/en.js +++ b/react/src/translate/en.js @@ -403,7 +403,12 @@ export const _lang = { 'DISPLAY_SYNC_ONLY': 'Display sync only coins progress', 'ENTER_VALUE_AGAIN': 'Please enter the same value again', 'CUSTOM_WALLET_SEED': 'Custom wallet seed', - 'MUST_ENTER_SEED': 'You must enter a seed' + 'MUST_ENTER_SEED': 'You must enter a seed', + 'INVALID_SEED': 'Invalid Seed', + 'SEED_NOT_OF_TYPE': 'The inserted seed is not of type', + 'IGUANA_SEED': 'Iguana (256 bits)', + 'WAVES_SEED': 'Waves', + 'NXT_SEED': 'NXT' }, 'SIDEBAR': { 'EDEX_MOTTO': 'Most Secure, Easy and Native Decentralised Exchange', diff --git a/react/src/util/crypto/passphrasegenerator.js b/react/src/util/crypto/passphrasegenerator.js index 72e903f..f1d214a 100755 --- a/react/src/util/crypto/passphrasegenerator.js +++ b/react/src/util/crypto/passphrasegenerator.js @@ -80,6 +80,15 @@ export const PassPhraseGenerator = { return this.passPhrase; }, + // checks if it's possible that the pass phrase supplied as the first parameter + // was generated with the number of bits supplied as the second parameter + isPassPhraseValid: function (passPhrase, bits) { + // the required number of words based on the number of bits + // mirrors the generatePassPhrase function above + const wordsCount = bits / 32 * 3; + return passPhrase && passPhrase.split(" ").length === wordsCount; + }, + reset: function() { this.passPhrase = ""; this.seeds = 0; From a4b3a7fb0a8681182687dc81b8ad9b1bb9f59a18 Mon Sep 17 00:00:00 2001 From: petitPapillon Date: Tue, 27 Jun 2017 22:19:45 +0200 Subject: [PATCH 4/9] Wallet login - display the enter pass phrase seed type (if applicable) Disable the 'Sign in' button if no seed type is matched --- react/src/components/login/login.js | 37 +++++------- react/src/components/login/login.render.js | 63 ++++---------------- react/src/components/login/login.scss | 4 ++ react/src/util/crypto/passphrasegenerator.js | 27 +++++++-- 4 files changed, 53 insertions(+), 78 deletions(-) diff --git a/react/src/components/login/login.js b/react/src/components/login/login.js index 5542cb8..a385ab0 100644 --- a/react/src/components/login/login.js +++ b/react/src/components/login/login.js @@ -27,7 +27,7 @@ class Login extends React.Component { activeLoginSection: 'activateCoin', loginPassphrase: null, seedInputVisibility: false, - loginPassPhraseBitsOption: 256, + loginPassPhraseSeedType: null, bitsOption: 256, randomSeed: PassPhraseGenerator.generatePassPhrase(256), randomSeedConfirm: '', @@ -154,7 +154,8 @@ class Login extends React.Component { this.setState({ [e.target.name]: e.target.value, isSeedConfirmError: false, - isSeedBlank: this.isBlank(e.target.value) + isSeedBlank: this.isBlank(e.target.value), + loginPassPhraseSeedType: this.getLoginPassPhraseSeedType(e.target.value) }); } @@ -167,39 +168,33 @@ class Login extends React.Component { } loginSeed() { - if (!PassPhraseGenerator.isPassPhraseValid(this.state.loginPassphrase, - this.state.loginPassPhraseBitsOption)) { - Store.dispatch(triggerToaster( - `${translate('LOGIN.SEED_NOT_OF_TYPE')} ${this.selectedLoginSeedTypeLabel()}`, - translate('LOGIN.INVALID_SEED'), - 'error', - false - )); - return; - } Store.dispatch( iguanaWalletPassphrase(this.state.loginPassphrase) ); } - selectedLoginSeedTypeLabel() { - if (this.state.loginPassPhraseBitsOption === 256) { + getLoginPassPhraseSeedType(passPhrase) { + if (!passPhrase) { + return null; + } + + const passPhraseWords = passPhrase.split(" "); + if (!PassPhraseGenerator.arePassPhraseWordsValid(passPhraseWords)) + return null; + + if (PassPhraseGenerator.isPassPhraseValid(passPhraseWords, 256)) { return translate('LOGIN.IGUANA_SEED'); } - if (this.state.loginPassPhraseBitsOption === 160) { + if (PassPhraseGenerator.isPassPhraseValid(passPhraseWords, 160)) { return translate('LOGIN.WAVES_SEED'); } - if (this.state.loginPassPhraseBitsOption === 128) { + if (PassPhraseGenerator.isPassPhraseValid(passPhraseWords, 128)) { return translate('LOGIN.NXT_SEED'); } - } - onLoginPassPhraseBitsOptionChange(bitsOption) { - this.setState({ - loginPassPhraseBitsOption: +bitsOption - }); + return null; } updateActiveLoginSection(name) { diff --git a/react/src/components/login/login.render.js b/react/src/components/login/login.render.js index 4e2474f..46d79ff 100644 --- a/react/src/components/login/login.render.js +++ b/react/src/components/login/login.render.js @@ -6,7 +6,7 @@ const LoginRender = function () {
{ this.renderSwallModal() }
-
+

{translate('INDEX.WELCOME_LOGIN')}

-
-
-

- { translate('INDEX.SELECT_SEED_TYPE') }: -

-
-
- this.onLoginPassPhraseBitsOptionChange(e.target.value) } - /> - -
-
- this.onLoginPassPhraseBitsOptionChange(e.target.value) } - /> - -
-
- this.onLoginPassPhraseBitsOptionChange(e.target.value) } - /> - -
-
-
-
-
+
{ translate('INDEX.WALLET_SEED') }
+
+ { this.state.loginPassPhraseSeedType + ? + this.state.loginPassPhraseSeedType + : +
Seed Type
+ } +
+ || !this.state.loginPassphrase.length + || !this.state.loginPassPhraseSeedType }>{ translate('INDEX.SIGN_IN') }
{ translate('INDEX.IE_UNSUPPORTED') }
@@ -49,7 +53,9 @@ const LoginRender = function () {
-

{translate('INDEX.WELCOME_LOGIN')}

+

+ {translate('INDEX.WELCOME_LOGIN')} +

{ translate('INDEX.SIGN_IN') } + || !this.state.loginPassphrase.length }>{ translate('INDEX.SIGN_IN') }
-

{ translate('INDEX.WELCOME_PLEASE_ADD') }

+

+ { translate('INDEX.WELCOME_PLEASE_ADD') } +

@@ -114,7 +125,6 @@ const LoginRender = function () {

{ translate('INDEX.SELECT_SEED_TYPE') }:

-
@@ -178,7 +188,7 @@ const LoginRender = function () {
-
+
+ + Weak seed!

+ Your seed must contain:
+ - at least 1 upper case letter
+ - at least 1 digit
+ - at least 1 special character
+ - minimum 10 characters long +
@@ -199,8 +217,12 @@ const LoginRender = function () { value={ this.state.randomSeedConfirm } onChange={ this.updateInput } id="rwalletseed"> - { translate('LOGIN.MUST_ENTER_SEED') }. - { translate('LOGIN.ENTER_VALUE_AGAIN') }. + + { translate('LOGIN.MUST_ENTER_SEED') }. + + + { translate('LOGIN.ENTER_VALUE_AGAIN') }. + diff --git a/react/src/components/login/login.scss b/react/src/components/login/login.scss index 9acede3..c93839c 100644 --- a/react/src/components/login/login.scss +++ b/react/src/components/login/login.scss @@ -58,4 +58,30 @@ textarea { .placeholder-label { color: #808080; +} + +.seed-tooltip { + .tooltiptext { + width: 230px; + padding: 10px 20px; + background-color: black; + color: #fff; + text-align: left; + border-radius: 6px; + position: absolute; + z-index: 1; + top: -5px; + left: 105%; + + &::after { + content: ''; + position: absolute; + top: 50%; + right: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent black transparent transparent; + } + } } \ No newline at end of file From c9d466661ff6cfcca716b1dfdea05e9674bd7b5d Mon Sep 17 00:00:00 2001 From: pbca26 Date: Wed, 28 Jun 2017 11:50:43 -0700 Subject: [PATCH 6/9] reverted passphrase visibility icon --- react/src/components/dashboard/settings/settings.render.js | 2 +- react/src/components/login/login.render.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react/src/components/dashboard/settings/settings.render.js b/react/src/components/dashboard/settings/settings.render.js index c762fbe..2a4974f 100644 --- a/react/src/components/dashboard/settings/settings.render.js +++ b/react/src/components/dashboard/settings/settings.render.js @@ -310,7 +310,7 @@ export const SettingsRender = function() { id="wifkeysPassphrase" onChange={ this.updateInput } />