Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic wallet encryption flows #1785

Merged
merged 6 commits into from
Jul 25, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 6 additions & 2 deletions src/renderer/component/splash/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { selectDaemonVersionMatched } from 'redux/selectors/app';
import { selectNotification } from 'lbry-redux';
import { doCheckDaemonVersion } from 'redux/actions/app';
import { doCheckDaemonVersion, doNotifyUnlockWallet } from 'redux/actions/app';
import SplashScreen from './view';

const select = state => ({
Expand All @@ -11,6 +11,10 @@ const select = state => ({

const perform = dispatch => ({
checkDaemonVersion: () => dispatch(doCheckDaemonVersion()),
notifyUnlockWallet: () => dispatch(doNotifyUnlockWallet()),
});

export default connect(select, perform)(SplashScreen);
export default connect(
select,
perform
)(SplashScreen);
23 changes: 22 additions & 1 deletion src/renderer/component/splash/view.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as React from 'react';
import { Lbry, MODALS } from 'lbry-redux';
import LoadScreen from './internal/load-screen';
import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
import ModalUpgrade from 'modal/modalUpgrade';
import ModalDownloading from 'modal/modalDownloading';

type Props = {
checkDaemonVersion: () => Promise<any>,
notifyUnlockWallet: () => Promise<any>,
notification: ?{
id: string,
},
Expand All @@ -17,6 +19,7 @@ type State = {
message: string,
isRunning: boolean,
isLagging: boolean,
launchedModal: boolean,
};

export class SplashScreen extends React.PureComponent<Props, State> {
Expand All @@ -28,18 +31,23 @@ export class SplashScreen extends React.PureComponent<Props, State> {
message: __('Connecting'),
isRunning: false,
isLagging: false,
launchedModal: false,
};
}

updateStatus() {
Lbry.status().then(status => {
this._updateStatusCallback(status);
window.status = status;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

});
}

_updateStatusCallback(status) {
const { notifyUnlockWallet } = this.props;
const { launchedModal } = this.state;

const startupStatus = status.startup_status;
if (startupStatus.code == 'started') {
if (startupStatus.code === 'started') {
// Wait until we are able to resolve a name before declaring
// that we are done.
// TODO: This is a hack, and the logic should live in the daemon
Expand All @@ -61,6 +69,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
});
return;
}

if (status.blockchain_status && status.blockchain_status.blocks_behind > 0) {
const format =
status.blockchain_status.blocks_behind == 1 ? '%s block behind' : '%s blocks behind';
Expand All @@ -69,6 +78,17 @@ export class SplashScreen extends React.PureComponent<Props, State> {
details: __(format, status.blockchain_status.blocks_behind),
isLagging: startupStatus.is_lagging,
});
} else if (startupStatus.code === 'waiting_for_wallet_unlock') {
this.setState({
message: __('Unlock Wallet'),
details: __('Please unlock your wallet to proceed.'),
isLagging: false,
isRunning: true,
});

if (launchedModal === false) {
this.setState({ launchedModal: true }, () => notifyUnlockWallet());
}
} else {
this.setState({
message: __('Network Loading'),
Expand Down Expand Up @@ -114,6 +134,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
in the modals won't work. */}
{isRunning && (
<React.Fragment>
{notificationId === MODALS.WALLET_UNLOCK && <ModalWalletUnlock />}
{notificationId === MODALS.INCOMPATIBLE_DAEMON && <ModalIncompatibleDaemon />}
{notificationId === MODALS.UPGRADE && <ModalUpgrade />}
{notificationId === MODALS.DOWNLOADING && <ModalDownloading />}
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/modal/modalRouter/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import ModalSendTip from '../modalSendTip';
import ModalPublish from '../modalPublish';
import ModalOpenExternalLink from '../modalOpenExternalLink';
import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload';
import ModalWalletEncrypt from 'modal/modalWalletEncrypt';
import ModalWalletDecrypt from 'modal/modalWalletDecrypt';
import ModalWalletUnlock from 'modal/modalWalletUnlock';

type Props = {
modal: string,
Expand Down Expand Up @@ -165,6 +168,12 @@ class ModalRouter extends React.PureComponent<Props> {
return <ModalConfirmTransaction {...notificationProps} />;
case MODALS.CONFIRM_THUMBNAIL_UPLOAD:
return <ModalConfirmThumbnailUpload {...notificationProps} />;
case MODALS.WALLET_ENCRYPT:
return <ModalWalletEncrypt {...notificationProps} />;
case MODALS.WALLET_DECRYPT:
return <ModalWalletDecrypt {...notificationProps} />;
case MODALS.WALLET_UNLOCK:
return <ModalWalletUnlock {...notificationProps} />;
default:
return null;
}
Expand Down
23 changes: 23 additions & 0 deletions src/renderer/modal/modalWalletDecrypt/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import {
doHideNotification,
doWalletStatus,
doWalletDecrypt,
selectWalletDecryptSucceeded,
} from 'lbry-redux';
import ModalWalletDecrypt from './view';

const select = state => ({
walletDecryptSucceded: selectWalletDecryptSucceeded(state),
});

const perform = dispatch => ({
closeModal: () => dispatch(doHideNotification()),
decryptWallet: password => dispatch(doWalletDecrypt(password)),
updateWalletStatus: () => dispatch(doWalletStatus()),
});

export default connect(
select,
perform
)(ModalWalletDecrypt);
61 changes: 61 additions & 0 deletions src/renderer/modal/modalWalletDecrypt/view.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// @flow
import React from 'react';
import { FormRow, FormField } from 'component/common/form';
import { Modal } from 'modal/modal';
import Button from 'component/button';

type Props = {
closeModal: () => void,
unlockWallet: string => void,
walletDecryptSucceded: boolean,
updateWalletStatus: boolean,
};

class ModalWalletDecrypt extends React.PureComponent<Props> {
state = {
submitted: false, // Prior actions could be marked complete
};

submitDecryptForm() {
this.setState({ submitted: true });
this.props.decryptWallet();
}

componentDidUpdate() {
const { props, state } = this;

if (state.submitted && props.walletDecryptSucceded === true) {
props.closeModal();
props.updateWalletStatus();
}
}

render() {
const { closeModal, walletDecryptSucceded } = this.props;

return (
<Modal
isOpen
contentLabel={__('Decrypt Wallet')}
type="confirm"
confirmButtonLabel={__('Decrypt Wallet')}
abortButtonLabel={__('Cancel')}
onConfirmed={() => this.submitDecryptForm()}
onAborted={closeModal}
>
{__(
'Your wallet has been encrypted with a local password, performing this action will remove this password.'
)}
<FormRow padded>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be a FormRow

<Button
button="link"
label={__('Learn more')}
href="https://lbry.io/faq/wallet-encryption"
/>
</FormRow>
</Modal>
);
}
}

export default ModalWalletDecrypt;
26 changes: 26 additions & 0 deletions src/renderer/modal/modalWalletEncrypt/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { connect } from 'react-redux';
import {
doHideNotification,
doWalletStatus,
doWalletEncrypt,
selectWalletEncryptPending,
selectWalletEncryptSucceeded,
selectWalletEncryptResult,
} from 'lbry-redux';
import ModalWalletEncrypt from './view';

const select = state => ({
walletEncryptSucceded: selectWalletEncryptSucceeded(state),
walletEncryptResult: selectWalletEncryptResult(state),
});

const perform = dispatch => ({
closeModal: () => dispatch(doHideNotification()),
encryptWallet: password => dispatch(doWalletEncrypt(password)),
updateWalletStatus: () => dispatch(doWalletStatus()),
});

export default connect(
select,
perform
)(ModalWalletEncrypt);
142 changes: 142 additions & 0 deletions src/renderer/modal/modalWalletEncrypt/view.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// @flow
import React from 'react';
import { FormRow, FormField } from 'component/common/form';
import { Modal } from 'modal/modal';
import Button from 'component/button';

type Props = {
closeModal: () => void,
unlockWallet: string => void,
walletEncryptSucceded: boolean,
walletEncryptResult: boolean,
updateWalletStatus: boolean,
};

class ModalWalletEncrypt extends React.PureComponent<Props> {
state = {
newPassword: null,
newPasswordConfirm: null,
passwordMismatch: false,
understandConfirmed: false,
understandError: false,
submitted: false, // Prior actions could be marked complete
failMessage: false,
};

onChangeNewPassword(event) {
this.setState({ newPassword: event.target.value });
}

onChangeNewPasswordConfirm(event) {
this.setState({ newPasswordConfirm: event.target.value });
}

onChangeUnderstandConfirm(event) {
this.setState({
understandConfirmed: /^.?i understand.?$/i.test(event.target.value),
});
}

submitEncryptForm() {
const { state } = this;

let invalidEntries = false;

if (state.newPassword !== state.newPasswordConfirm) {
this.setState({ passwordMismatch: true });
invalidEntries = true;
}

if (state.understandConfirmed === false) {
this.setState({ understandError: true });
invalidEntries = true;
}

if (invalidEntries === true) {
return;
}

this.setState({ submitted: true });
this.props.encryptWallet(state.newPassword);
}

componentDidUpdate() {
const { props, state } = this;

if (state.submitted) {
if (props.walletEncryptSucceded === true) {
props.closeModal();
props.updateWalletStatus();
} else if (props.walletEncryptSucceded === false) {
// See https://github.com/lbryio/lbry/issues/1307
this.setState({ failMessage: 'Unable to encrypt wallet.' });
}
}
}

render() {
const { closeModal } = this.props;

const { passwordMismatch, understandError, failMessage } = this.state;

return (
<Modal
isOpen
contentLabel={__('Encrypt Wallet')}
type="confirm"
confirmButtonLabel={__('Encrypt Wallet')}
abortButtonLabel={__('Cancel')}
onConfirmed={() => this.submitEncryptForm()}
onAborted={closeModal}
>
{__(
'Encrypting your wallet will require a password to access your local wallet data when LBRY starts. Please enter a new password for your wallet.'
)}
<FormRow padded>
<FormField
stretch
error={passwordMismatch === true ? 'Passwords do not match' : false}
label={__('New Password')}
type="password"
name="wallet-new-password"
onChange={event => this.onChangeNewPassword(event)}
/>
</FormRow>
<FormRow padded>
<FormField
stretch
error={passwordMismatch === true ? 'Passwords do not match' : false}
label={__('Confirm Password')}
type="password"
name="wallet-new-password-confirm"
onChange={event => this.onChangeNewPasswordConfirm(event)}
/>
</FormRow>
<br />
{__(
'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.'
)}
<FormRow padded>
<FormField
stretch
error={understandError === true ? 'You must enter "I understand"' : false}
label={__('Enter "I understand"')}
type="text"
name="wallet-understand"
onChange={event => this.onChangeUnderstandConfirm(event)}
/>
</FormRow>
<FormRow padded>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, use className="card__actions"

<Button
button="link"
label={__('Learn more')}
href="https://lbry.io/faq/wallet-encryption"
/>
</FormRow>
{failMessage && <div className="error-text">{__(failMessage)}</div>}
</Modal>
);
}
}

export default ModalWalletEncrypt;
Loading