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

Fix bugs of Encrypt/Decrypt message - Closes #1003 #1007

Merged
merged 8 commits into from
Nov 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
});
});