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 #1007 from LiskHQ/1003-fix-encrypt-decrypt
Browse files Browse the repository at this point in the history
Fix bugs of Encrypt/Decrypt message - Closes #1003
  • Loading branch information
slaweet authored Nov 24, 2017
2 parents 10bb97b + 783b037 commit 32cc926
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 57 deletions.
6 changes: 6 additions & 0 deletions i18n/locales/de/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Downvotes:": "Abgewählte:",
"Edit": "Bearbeiten",
"Encrypt message": "Nachricht verschlüsseln",
"Encrypted Message": "Verschlüsselte Nachricht",
"Enter the missing word": "Gebe das fehlende Wort ein",
"Enter your passphrase": "Gebe deine Passphrase ein",
"Entered passphrase does not belong to the active account": "Die eingegebene Passphrase gehört nicht zum aktiven Konto",
Expand Down Expand Up @@ -76,6 +77,8 @@
"Maximum of {{n}} votes exceeded.": "Das Maximum an {{n}} Stimmen wurde überschritten.",
"Maximum of {{n}} votes in one transaction exceeded.": "Das Maximum an {{n}} Stimmen in einer Transaktion wurde überschritten.",
"Message": "Nachricht",
"Message decryption failed": "Nachricht-Entschlüsselung fehlgeschlagen",
"Message encryption failed": "Nachricht-Verschlüsselung fehlgeschlagen",
"Message is decrypted successfully": "Die Nachricht wurde erfolgreich entschlüsselt",
"Minimize": "Minimieren",
"Move your mouse to generate random bytes": "Bewege die Maustaste, um zufällige Bytes zu erzeugen",
Expand Down Expand Up @@ -175,6 +178,7 @@
"URL is invalid": "URL ist ungültig",
"Unable to connect to the node": "Verbindung zum Node nicht möglich",
"Undo": "Rückgängig",
"Unlock account": "Account freischalten",
"Update download finished": "Der Download wurde beendet",
"Update now": "Jetzt aktualisieren",
"Updates downloaded, application has to be restarted to apply the updates.": "Updates wurden heruntergeladen. Die App muss nun neu gestartet werden.",
Expand Down Expand Up @@ -208,7 +212,9 @@
"confirmation": "Bestätigung",
"confirmations": "Bestätigungen",
"decrypt": "Entschlüsseln",
"decrypt message": "Nachricht entschlüsseln",
"encrypt": "Verschlüsseln",
"encrypt message": "Nachricht verschlüsseln",
"logout": "Abmelden",
"my votes": "Meine Abstimmungen",
"send": "senden",
Expand Down
7 changes: 6 additions & 1 deletion i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"Downvotes:": "Downvotes:",
"Edit": "Edit",
"Encrypt message": "Encrypt message",
"Encrypted Message": "Encrypted Message",
"Enter the missing word": "Enter the missing word",
"Enter your passphrase": "Enter your passphrase",
"Entered passphrase does not belong to the active account": "Entered passphrase does not belong to the active account",
Expand Down Expand Up @@ -74,6 +75,8 @@
"Maximum of {{n}} votes exceeded.": "Maximum of {{n}} votes exceeded.",
"Maximum of {{n}} votes in one transaction exceeded.": "Maximum of {{n}} votes in one transaction exceeded.",
"Message": "Message",
"Message decryption failed": "Message decryption failed",
"Message encryption failed": "Message encryption failed",
"Message is decrypted successfully": "Message is decrypted successfully",
"Minimize": "Minimize",
"Move your mouse to generate random bytes": "Move your mouse to generate random bytes",
Expand Down Expand Up @@ -109,7 +112,6 @@
"Processing delegate names: ": "Processing delegate names: ",
"Proxy Authentication": "Proxy Authentication",
"Public Key": "Public Key",
"Public key : ": "Public key:",
"Quit": "Quit",
"Rank": "Rank",
"Receive LSK": "Receive LSK",
Expand Down Expand Up @@ -173,6 +175,7 @@
"URL is invalid": "URL is invalid",
"Unable to connect to the node": "Unable to connect to the node",
"Undo": "Undo",
"Unlock account": "Unlock account",
"Update download finished": "Update download finished",
"Update now": "Update now",
"Updates downloaded, application has to be restarted to apply the updates.": "Updates downloaded, application has to be restarted to apply the updates.",
Expand Down Expand Up @@ -207,7 +210,9 @@
"confirmation": "confirmation",
"confirmations": "confirmations",
"decrypt": "decrypt",
"decrypt message": "decrypt message",
"encrypt": "encrypt",
"encrypt message": "encrypt message",
"logout": "logout",
"my votes": "my votes",
"send": "send",
Expand Down
2 changes: 1 addition & 1 deletion src/components/authenticate/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class Authenticate extends React.Component {
onClick: this.props.closeDialog,
}}
primaryButton={{
label: this.props.t('Submit'),
label: this.props.t('Unlock account'),
onClick: this.update.bind(this),
className: 'authenticate-button',
disabled: (!authStateIsValid(this.state)),
Expand Down
37 changes: 17 additions & 20 deletions src/components/decryptMessage/decryptMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import React from 'react';
import Input from 'react-toolbox/lib/input';
import Lisk from 'lisk-js';
import { translate } from 'react-i18next';
import SignVerifyResult from '../signVerifyResult';
import ActionBar from '../actionBar';
import Authenticate from '../authenticate';
import SignVerifyResult from '../signVerifyResult';

class DecryptMessage extends React.Component {
constructor() {
super();
this.state = {
result: '',
nonce: {
senderPublicKey: {
value: '',
},
message: {
value: '',
},
senderPublicKey: {
nonce: {
value: '',
},
};
Expand All @@ -28,6 +29,7 @@ class DecryptMessage extends React.Component {
value,
error,
},
result: '',
});
}

Expand All @@ -41,47 +43,41 @@ class DecryptMessage extends React.Component {
this.props.account.passphrase,
this.state.senderPublicKey.value);
} catch (error) {
this.props.errorToast({ label: error.message });
this.props.errorToast({ label: this.props.t('Message decryption failed') });
}
if (decryptedMessage) {
const result = [
'-----DECRYPTED MESSAGE-----',
decryptedMessage,
].join('\n');
this.setState({ result, resultIsShown: false });
this.setState({ resultIsShown: true });
this.setState({ result: decryptedMessage });
this.props.successToast({ label: this.props.t('Message is decrypted successfully') });
}
}

render() {
return (
<div className='sign-message'>
return (typeof this.props.account.passphrase === 'string' && this.props.account.passphrase.length > 0 ?
<div className='decrypt-message'>
<form onSubmit={this.showResult.bind(this)}>
<section>
<Input className='senderPublicKey' label={this.props.t('Sender PublicKey')}
autoFocus={true}
value={this.state.senderPublicKey.value}
onChange={this.handleChange.bind(this, 'senderPublicKey')} />
<Input className='message' multiline label={this.props.t('Encrypted Message')}
autoFocus={true}
value={this.state.message.value}
onChange={this.handleChange.bind(this, 'message')} />
<Input className='nonce' label={this.props.t('Nonce')}
autoFocus={true}
value={this.state.nonce.value}
onChange={this.handleChange.bind(this, 'nonce')} />
<Input className='message' multiline label={this.props.t('Message')}
autoFocus={true}
value={this.state.message.value}
onChange={this.handleChange.bind(this, 'message')} />

</section>
{this.state.resultIsShown ?
{this.state.result ?
<SignVerifyResult result={this.state.result} title={this.props.t('Result')} /> :
<ActionBar
secondaryButton={{
onClick: this.props.closeDialog,
}}
primaryButton={{
label: this.props.t('decrypt'),
className: 'sign-button',
className: 'decrypt-button',
type: 'submit',
disabled: (this.state.message.value.length === 0 ||
this.state.senderPublicKey.value.length === 0 ||
Expand All @@ -90,7 +86,8 @@ class DecryptMessage extends React.Component {
}} />
}
</form>
</div>
</div> :
<Authenticate nextAction={this.props.t('decrypt message')}/>
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/decryptMessage/decryptMessage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ describe('DecryptMessage', () => {
});

it('shows error toast when couldn\'t decrypt a message', () => {
decryptMessageMock.throws({ message: 'couldn\'t decrypt the message' });
decryptMessageMock.throws(new Error('couldn\'t decrypt the message'));
wrapper.find('.message textarea').simulate('change', { target: { value: message } });
wrapper.find('.senderPublicKey input').simulate('change', { target: { value: senderPublicKey } });
wrapper.find('.nonce input').simulate('change', { target: { value: nonce } });
wrapper.find('form').simulate('submit');
expect(errorSpy).to.have.been.calledWith({ label: 'couldn\'t decrypt the message' });
expect(errorSpy).to.have.been.calledWith({ label: 'Message decryption failed' });
});

it('allows to decrypt a message, copies encrypted message result to clipboard and shows success toast', () => {
Expand Down
27 changes: 13 additions & 14 deletions src/components/encryptMessage/encryptMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from 'react';
import Input from 'react-toolbox/lib/input';
import Lisk from 'lisk-js';
import { translate } from 'react-i18next';
import InfoParagraph from '../infoParagraph';
import SignVerifyResult from '../signVerifyResult';
import ActionBar from '../actionBar';
import Authenticate from '../authenticate';
import SignVerifyResult from '../signVerifyResult';


class EncryptMessage extends React.Component {
Expand All @@ -27,6 +27,7 @@ class EncryptMessage extends React.Component {
value,
error,
},
result: null,
});
}

Expand All @@ -39,16 +40,20 @@ class EncryptMessage extends React.Component {
this.props.account.passphrase,
this.state.recipientPublicKey.value);
} catch (error) {
this.props.errorToast({ label: error.message });
this.props.errorToast({ label: this.props.t('Message encryption failed') });
}
if (cryptoResult) {
const result = [
'-----BEGIN LISK ENCRYPTED MESSAGE-----',
'-----SENDER PUBLIC KEY-----',
this.props.account.publicKey,
'-----ENCRYPTED MESSAGE-----',
cryptoResult.encryptedMessage,
'-----NONCE-----',
cryptoResult.nonce,
'-----END LISK ENCRYPTED MESSAGE-----',
].join('\n');
this.setState({ result, resultIsShown: false });
this.setState({ result });
this.showResult();
}
}
Expand All @@ -60,20 +65,13 @@ class EncryptMessage extends React.Component {
if (copied) {
this.props.successToast({ label: this.props.t('Result copied to clipboard') });
}
this.setState({ resultIsShown: true });
}

render() {
return (
return (typeof this.props.account.passphrase === 'string' && this.props.account.passphrase.length > 0 ?
<div className='sign-message'>
<form onSubmit={this.encrypt.bind(this)}>
<section>
<InfoParagraph>
<h3>
{this.props.t('Public key : ')}
</h3>
{this.props.account.publicKey}
</InfoParagraph>
<Input className='recipientPublicKey' label={this.props.t('Recipient PublicKey')}
autoFocus={true}
value={this.state.recipientPublicKey.value}
Expand All @@ -83,7 +81,7 @@ class EncryptMessage extends React.Component {
value={this.state.message.value}
onChange={this.handleChange.bind(this, 'message')} />
</section>
{this.state.resultIsShown ?
{this.state.result ?
<SignVerifyResult id='encryptResult' result={this.state.result} title={this.props.t('Result')} /> :
<ActionBar
secondaryButton={{
Expand All @@ -98,7 +96,8 @@ class EncryptMessage extends React.Component {
}} />
}
</form>
</div>
</div> :
<Authenticate nextAction={this.props.t('encrypt message')}/>
);
}
}
Expand Down
59 changes: 40 additions & 19 deletions src/components/encryptMessage/encryptMessage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { Provider } from 'react-redux';
import { I18nextProvider } from 'react-i18next';
import PropTypes from 'prop-types';
import Lisk from 'lisk-js';
import i18n from '../../i18n';
import store from '../../store';
import Authenticate from '../authenticate';
import EncryptMessage from './encryptMessage';


describe('EncryptMessage', () => {
let wrapper;
let successToastSpy;
let copyMock;
let encryptMessageSpy;
let props;
const recipientPublicKey = '164a0580cd2b430bc3496f82adf51b799546a3a4658bb9dca550a0e20cb579c8';
const message = 'Hello world';
const publicKey = '164a0580cd2b430bc3496f82adf51b799546a3a4658bb9dca550a0e20cb579c8';
Expand All @@ -24,42 +23,64 @@ describe('EncryptMessage', () => {
};

beforeEach(() => {
successToastSpy = sinon.spy();
copyMock = sinon.mock();
encryptMessageSpy = sinon.spy(Lisk.crypto, 'encryptMessageWithSecret');
const props = {
props = {
account,
successToast: successToastSpy,
copyToClipboard: copyMock,
successToast: sinon.spy(),
errorToast: sinon.spy(),
copyToClipboard: sinon.mock(),
t: key => key,
};

wrapper = mount(<Provider store={store}>
<I18nextProvider i18n={ i18n }>
<EncryptMessage {...props} />
</I18nextProvider>
</Provider>);
const options = {
context: { store, i18n },
childContextTypes: {
store: PropTypes.object.isRequired,
i18n: PropTypes.object.isRequired,
},
};

wrapper = mount(<EncryptMessage {...props} />, options);
});

afterEach(() => {
encryptMessageSpy.restore();
});

it('shows Authenticate step if passphrase is not available', () => {
wrapper.setProps({
account: { publicKey },
});
expect(wrapper.find(Authenticate)).to.have.length(1);
});

it('allows to encrypt a message, copies encrypted message result to clipboard and shows success toast', () => {
copyMock.returns(true);
props.copyToClipboard.returns(true);
wrapper.find('.message textarea').simulate('change', { target: { value: message } });
wrapper.find('.recipientPublicKey input').simulate('change', { target: { value: recipientPublicKey } });
wrapper.find('form').simulate('submit');
expect(encryptMessageSpy).to.have.been
.calledWith(message, account.passphrase, recipientPublicKey);
expect(successToastSpy).to.have.been.calledWith({ label: 'Result copied to clipboard' });
expect(props.successToast).to.have.been.calledWith({ label: 'Result copied to clipboard' });
});

it('shows error toast if encryp message failed', () => {
const invalidPublicKey = 'INVALID';
props.copyToClipboard.returns(true);
wrapper.find('.message textarea').simulate('change', { target: { value: message } });
wrapper.find('.recipientPublicKey input').simulate('change', { target: { value: invalidPublicKey } });
wrapper.find('form').simulate('submit');
expect(encryptMessageSpy).to.have.been.calledWith(
message, account.passphrase, invalidPublicKey);
expect(props.errorToast).to.have.been.calledWith(
{ label: 'Message encryption failed' });
});

it('does not show success toast if copy-to-clipboard failed', () => {
copyMock.returns(false);
props.copyToClipboard.returns(false);
wrapper.find('.message textarea').simulate('change', { target: { value: message } });
wrapper.find('.recipientPublicKey input').simulate('change', { target: { value: recipientPublicKey } });
wrapper.find('.primary-button').simulate('click');
expect(successToastSpy).to.have.not.been.calledWith();
wrapper.find('form').simulate('submit');
expect(props.successToast).to.have.not.been.calledWith();
});
});

0 comments on commit 32cc926

Please sign in to comment.