Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Commit

Permalink
Merge pull request #560 from LiskHQ/540-second-passphrase-field
Browse files Browse the repository at this point in the history
Create a React component for second passphrase field - Closes #540
  • Loading branch information
slaweet authored Aug 8, 2017
2 parents 28d1de7 + e872975 commit 3b06b1a
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 11 deletions.
10 changes: 2 additions & 8 deletions src/components/login/loginFormComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import Button from 'react-toolbox/lib/button';
import Checkbox from 'react-toolbox/lib/checkbox';
import { getAccount, extractAddress, extractPublicKey } from '../../utils/api/account';
import { getDelegate } from '../../utils/api/delegate';
import { isValidPassphrase } from '../../utils/passphrase';
import networksRaw from './networks';
import Passphrase from '../passphrase';
import styles from './login.css';

if (global._bitcore) delete global._bitcore;

const mnemonic = require('bitcore-mnemonic');

/**
* The container component containing login
* and create account functionality
Expand Down Expand Up @@ -64,12 +63,7 @@ class LoginFormComponent extends React.Component {
if (!value || value === '') {
data.passphraseValidity = 'Empty passphrase';
} else {
const normalizedValue = value.replace(/ +/g, ' ').trim().toLowerCase();
if (normalizedValue.split(' ').length < 12 || !mnemonic.isValid(normalizedValue)) {
data.passphraseValidity = 'Invalid passphrase';
} else {
data.passphraseValidity = '';
}
data.passphraseValidity = isValidPassphrase(value) ? '' : 'Invalid passphrase';
}

this.setState(data);
Expand Down
9 changes: 9 additions & 0 deletions src/components/secondPassphraseInput/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import SecondPassphraseInput from './secondPassphraseInput';

const mapStateToProps = state => ({
hasSecondPassphrase: !!state.account.secondSignature,
});

export default connect(mapStateToProps)(SecondPassphraseInput);

30 changes: 30 additions & 0 deletions src/components/secondPassphraseInput/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import SecondPassphraseInputContainer from './index';

chai.use(sinonChai);

describe('SecondPassphraseInputContainer', () => {
let wrapper;

it('should render SecondPassphraseInput with props.hasSecondPassphrase if store.account.secondSignature is truthy', () => {
const store = configureMockStore([])({ account: { secondSignature: 1 } });
wrapper = mount(<Provider store={store}>
<SecondPassphraseInputContainer onError={ () => {} } />
</Provider>);
expect(wrapper.find('SecondPassphraseInput')).to.have.lengthOf(1);
expect(wrapper.find('SecondPassphraseInput').props().hasSecondPassphrase).to.equal(true);
});

it('should render SecondPassphraseInput with !props.hasSecondPassphrase if store.account.secondSignature is falsy', () => {
const store = configureMockStore([])({ account: { secondSignature: 0 } });
wrapper = mount(<Provider store={store}>
<SecondPassphraseInputContainer onError={ () => {} } />
</Provider>);
expect(wrapper.find('SecondPassphraseInput').props().hasSecondPassphrase).to.equal(false);
});
});
37 changes: 37 additions & 0 deletions src/components/secondPassphraseInput/secondPassphraseInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import Input from 'react-toolbox/lib/input';
import { isValidPassphrase } from '../../utils/passphrase';

class SecondPassphraseInput extends React.Component {
componentDidMount() {
if (this.props.hasSecondPassphrase) {
this.props.onError(undefined, '');
}
}

handleValueChange(value) {
this.props.onChange(value);
let error;
if (!value) {
error = 'Required';
} else if (!isValidPassphrase(value)) {
error = 'Invalid passphrase';
}
if (error) {
this.props.onError(error, value);
}
}

render() {
return (this.props.hasSecondPassphrase ?
<Input label='Second Passphrase' required={true}
className='second-passphrase'
error={this.props.error}
value={this.props.value}
onChange={this.handleValueChange.bind(this)} /> :
null);
}
}

export default SecondPassphraseInput;

38 changes: 38 additions & 0 deletions src/components/secondPassphraseInput/secondPassphraseInput.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
import SecondPassphraseInput from './secondPassphraseInput';

chai.use(sinonChai);

describe('SecondPassphraseInput', () => {
let wrapper;
let props;

beforeEach(() => {
props = {
onChange: sinon.spy(),
onError: sinon.spy(),
};
});

it('should render Input if props.hasSecondPassphrase', () => {
props.hasSecondPassphrase = true;
wrapper = mount(<SecondPassphraseInput {...props} />);
expect(wrapper.find('Input')).to.have.lengthOf(1);
});

it('should render null if !props.hasSecondPassphrase', () => {
props.hasSecondPassphrase = false;
wrapper = mount(<SecondPassphraseInput {...props} />);
expect(wrapper.html()).to.equal(null);
});

it('should render null if !props.hasSecondPassphrase', () => {
props.hasSecondPassphrase = false;
wrapper = mount(<SecondPassphraseInput {...props} />);
expect(wrapper.html()).to.equal(null);
});
});
22 changes: 21 additions & 1 deletion src/components/send/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IconMenu, MenuItem } from 'react-toolbox/lib/menu';

import { send } from '../../utils/api/account';
import { fromRawLsk, toRawLsk } from '../../utils/lsk';
import SecondPassphraseInput from '../secondPassphraseInput';
import ActionBar from '../actionBar';

import styles from './send.css';
Expand All @@ -18,6 +19,9 @@ class Send extends React.Component {
amount: {
value: '',
},
secondPassphrase: {
value: null,
},
};
this.fee = 0.1;
this.inputValidationRegexps = {
Expand Down Expand Up @@ -47,6 +51,15 @@ class Send extends React.Component {
});
}

setErrorAndValue(name, error, value) {
this.setState({
[name]: {
value,
error,
},
});
}

validateInput(name, value) {
if (!value) {
return 'Required';
Expand All @@ -65,7 +78,7 @@ class Send extends React.Component {
this.state.recipient.value,
toRawLsk(this.state.amount.value),
this.props.account.passphrase,
this.props.account.sencodPassphrase,
this.state.secondPassphrase.value,
).then((data) => {
this.props.showSuccessAlert({
text: `Your transaction of ${this.state.amount.value} LSK to ${this.state.recipient.value} was accepted and will be processed in a few seconds.`,
Expand Down Expand Up @@ -107,6 +120,11 @@ class Send extends React.Component {
error={this.state.amount.error}
value={this.state.amount.value}
onChange={this.handleChange.bind(this, 'amount')} />
<SecondPassphraseInput
error={this.state.secondPassphrase.error}
value={this.state.secondPassphrase.value}
onChange={this.handleChange.bind(this, 'secondPassphrase')}
onError={this.setErrorAndValue.bind(this, 'secondPassphrase')} />
<div className={styles.fee}> Fee: {this.fee} LSK</div>
<IconMenu icon='more_vert' position='topRight' menuRipple className={`${styles.sendAllMenu} transaction-amount`} >
<MenuItem onClick={this.setMaxAmount.bind(this)}
Expand All @@ -122,6 +140,8 @@ class Send extends React.Component {
disabled: (
!!this.state.recipient.error ||
!!this.state.amount.error ||
!!this.state.secondPassphrase.error ||
this.state.secondPassphrase.value === '' ||
!this.state.recipient.value ||
!this.state.amount.value),
onClick: this.send.bind(this),
Expand Down
4 changes: 3 additions & 1 deletion src/components/send/send.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { mount } from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { Provider } from 'react-redux';
import store from '../../store';
import Send from './send';
import * as accountApi from '../../utils/api/account';

Expand All @@ -28,7 +30,7 @@ describe('Send', () => {
showErrorAlert: sinon.spy(),
addTransaction: sinon.spy(),
};
wrapper = mount(<Send {...props} />);
wrapper = mount(<Provider store={store}><Send {...props} /></Provider>);
});

afterEach(() => {
Expand Down
11 changes: 11 additions & 0 deletions src/utils/passphrase.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,14 @@ export const generateSeed = ({ byte, seed, percentage, step } = init(), rand = M
* @returns {string} The generated passphrase
*/
export const generatePassphrase = ({ seed }) => (new mnemonic(new Buffer(seed.join(''), 'hex'))).toString();

/**
* Checks if passphrase is valid using mnemonic
*
* @param {string} passphrase
* @returns {bool} isValidPassphrase
*/
export const isValidPassphrase = (passphrase) => {
const normalizedValue = passphrase.replace(/ +/g, ' ').trim().toLowerCase();
return normalizedValue.split(' ').length >= 12 && mnemonic.isValid(normalizedValue);
};
14 changes: 13 additions & 1 deletion src/utils/passphrase.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import chai, { expect } from 'chai';
import { spy } from 'sinon';
import sinonChai from 'sinon-chai';
import { generateSeed, generatePassphrase } from './passphrase';
import { generateSeed, generatePassphrase, isValidPassphrase } from './passphrase';

if (global._bitcore) delete global._bitcore;
const mnemonic = require('bitcore-mnemonic');
Expand Down Expand Up @@ -122,4 +122,16 @@ describe('Passphrase', () => {
expect(mnemonic.isValid(passphrase)).to.be.equal(true);
});
});

describe('isValidPassphrase', () => {
it('recognises a valid passphrase', () => {
const passphrase = 'wagon stock borrow episode laundry kitten salute link globe zero feed marble';
expect(isValidPassphrase(passphrase)).to.be.equal(true);
});

it('recognises an invalid passphrase', () => {
const passphrase = 'stock borrow episode laundry kitten salute link globe zero feed marble';
expect(isValidPassphrase(passphrase)).to.be.equal(false);
});
});
});

0 comments on commit 3b06b1a

Please sign in to comment.