This commit is contained in:
jessop 2019-08-20 08:29:59 -04:00 committed by Sean Yesmunt
parent bca759b522
commit c2b881e10e
24 changed files with 625 additions and 16 deletions

View file

@ -276,6 +276,18 @@ export const icons = {
<polyline points="17 6 23 6 23 12" />
</g>
),
[ICONS.EYE]: buildIcon(
<g>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</g>
),
[ICONS.EYE_OFF]: buildIcon(
<g>
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</g>
),
[ICONS.VIEW]: buildIcon(
<g>
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />

View file

@ -0,0 +1,68 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import {
doWalletStatus,
doWalletEncrypt,
doWalletDecrypt,
selectWalletEncryptSucceeded,
selectWalletEncryptPending,
selectWalletEncryptResult,
selectWalletIsEncrypted,
selectHasTransactions,
} from 'lbry-redux';
import { doPasswordSaved } from 'redux/actions/app';
import WalletSecurityAndSync from './view';
import {
doCheckSync,
doGetSync,
doSetDefaultAccount,
doSyncApply,
selectHasSyncedWallet,
selectGetSyncIsPending,
selectSetSyncIsPending,
selectSyncApplyIsPending,
selectSyncApplyErrorMessage,
selectSyncData,
selectSyncHash,
selectHashChanged,
selectUser,
} from 'lbryinc';
import { selectIsPasswordSaved } from 'redux/selectors/app';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
const select = state => ({
walletEncryptSucceeded: selectWalletEncryptSucceeded(state),
walletEncryptPending: selectWalletEncryptPending(state),
walletEncryptResult: selectWalletEncryptResult(state),
walletEncrypted: selectWalletIsEncrypted(state),
walletHasTransactions: selectHasTransactions(state),
user: selectUser(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
hasSyncedWallet: selectHasSyncedWallet(state),
getSyncIsPending: selectGetSyncIsPending(state),
setSyncIsPending: selectSetSyncIsPending(state),
syncApplyIsPending: selectSyncApplyIsPending(state),
syncApplyErrorMessage: selectSyncApplyErrorMessage(state),
syncData: selectSyncData(state),
syncHash: selectSyncHash(state),
isPasswordSaved: selectIsPasswordSaved(state),
hashChanged: selectHashChanged(state),
});
const perform = dispatch => ({
encryptWallet: password => dispatch(doWalletEncrypt(password)),
decryptWallet: () => dispatch(doWalletDecrypt()),
updateWalletStatus: () => dispatch(doWalletStatus()),
setPasswordSaved: saved => dispatch(doPasswordSaved(saved)),
syncApply: (hash, data, password) => dispatch(doSyncApply(hash, data, password)),
getSync: password => dispatch(doGetSync(password)),
checkSync: () => dispatch(doCheckSync()),
setDefaultAccount: () => dispatch(doSetDefaultAccount()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
});
export default connect(
select,
perform
)(WalletSecurityAndSync);

View file

@ -0,0 +1,340 @@
// @flow
import React, { useState, useEffect } from 'react';
import { Form, FormField, Submit } from 'component/common/form';
import Button from 'component/button';
import UserEmail from 'component/userEmail';
import * as ICONS from 'constants/icons';
import { getSavedPassword, setSavedPassword, deleteSavedPassword } from 'util/saved-passwords';
// import { AUTH_ORG } from 'constants/keychain';
/*
On mount: checkSync()
if
Display on mount
if !email
SyncWallet.disabled = true
SyncWallet.checked = false
if walletEncrypted
EncryptWallet.checked = true
password = savedPassword ? savedPassword : ''
rememberPassword.checked = savedPassword
if !walletEncrypted
password = savedPassword ? savedPassword : ''
rememberPassword.checked = savedPassword
else email
if walletEncrypted
EncryptWallet.checked = true
password = savedPassword ? savedPassword : ''
rememberPassword.checked = savedPassword
syncEnabled = settings.syncEnabled
if syncEnabled
message = 'hi'
else
message = 'hi'
else if !walletEncrypted
EncryptWallet.checked = false
password = savedPassword ? savedPassword : ''
rememberPassword.checked = savedPassword
syncEnabled = settings.syncEnabled
if syncEnabled
message = 'hi'
else
message = 'hi'
*/
type Props = {
// wallet statuses
walletEncryptSucceeded: boolean,
walletEncryptPending: boolean,
walletDecryptSucceeded: boolean,
walletDecryptPending: boolean,
updateWalletStatus: boolean,
walletEncrypted: boolean,
// wallet methods
encryptWallet: (?string) => void,
decryptWallet: (?string) => void,
updateWalletStatus: () => void,
// housekeeping
setPasswordSaved: () => void,
syncEnabled: boolean,
setClientSetting: (string, boolean | string) => void,
isPasswordSaved: boolean,
// data
user: any,
// sync statuses
hasSyncedWallet: boolean,
getSyncIsPending?: boolean,
syncApplyErrorMessage?: string,
hashChanged: boolean,
// sync data
syncData: string | null,
syncHash: string | null,
// sync methods
syncApply: (string | null, string | null, string) => void,
checkSync: () => void,
setDefaultAccount: () => void,
hasTransactions: boolean,
};
type State = {
newPassword: string,
newPasswordConfirm: string,
passwordMatch: boolean,
understandConfirmed: boolean,
understandError: boolean,
submitted: boolean,
failMessage: ?string,
rememberPassword: boolean,
showEmailReg: boolean,
failed: boolean,
enableSync: boolean,
encryptWallet: boolean,
obscurePassword: boolean,
};
function WalletSecurityAndSync(props: Props) {
const {
// walletEncryptSucceeded,
// walletEncryptPending,
// walletDecryptSucceeded,
// walletDecryptPending,
// updateWalletStatus,
walletEncrypted,
encryptWallet,
decryptWallet,
// setPasswordSaved,
syncEnabled,
// setClientSetting,
// isPasswordSaved,
user,
hasSyncedWallet,
getSyncIsPending,
syncApplyErrorMessage,
hashChanged,
syncData,
syncHash,
syncApply,
checkSync,
hasTransactions,
// setDefaultAccount,
} = props;
const defaultComponentState: State = {
newPassword: '',
newPasswordConfirm: '',
passwordMatch: false,
understandConfirmed: false,
understandError: false,
submitted: false, // Prior actions could be marked complete
failMessage: undefined,
rememberPassword: false,
showEmailReg: false,
failed: false,
enableSync: syncEnabled,
encryptWallet: walletEncrypted,
obscurePassword: true,
};
const [componentState, setComponentState] = useState<State>(defaultComponentState);
const safeToSync = !hasTransactions || !hashChanged;
useEffect(() => {
checkSync();
}, []);
useEffect(() => {
setComponentState({
...componentState,
passwordMatch: componentState.newPassword === componentState.newPasswordConfirm,
});
}, [componentState.newPassword, componentState.newPasswordConfirm]);
const isEmailVerified = user && user.primary_email && user.has_verified_email;
// const syncDisabledMessage = 'You cannot sync without an email';
function onChangeNewPassword(event: SyntheticInputEvent<>) {
setComponentState({ ...componentState, newPassword: event.target.value });
}
function onChangeRememberPassword(event: SyntheticInputEvent<>) {
setComponentState({ ...componentState, rememberPassword: event.target.checked });
}
function onChangeNewPasswordConfirm(event: SyntheticInputEvent<>) {
setComponentState({ ...componentState, newPasswordConfirm: event.target.value });
}
function onChangeUnderstandConfirm(event: SyntheticInputEvent<>) {
setComponentState({ ...componentState, understandConfirmed: /^.?i understand.?$/i.test(event.target.value) });
}
function onChangeSync(event: SyntheticInputEvent<>) {
setComponentState({ ...componentState, enableSync: event.target.checked });
}
function onChangeEncrypt(event: SyntheticInputEvent<>) {
setComponentState({ ...componentState, encryptWallet: event.target.checked });
}
async function apply() {
setComponentState({ ...componentState, failed: false });
await checkSync();
if (componentState.enableSync) {
await syncApply(syncHash, syncData, componentState.newPassword);
if (syncApplyErrorMessage) {
setComponentState({ ...componentState, failed: true });
}
}
await decryptWallet();
if (componentState.failed !== true) {
await encryptWallet(componentState.newPassword);
}
if (componentState.encryptWallet) {
await encryptWallet(componentState.newPassword);
}
if (componentState.failed === false) {
}
// this.setState({ submitted: true });
// this.props.encryptWallet(state.newPassword);
}
return (
<section className="card card--section">
<h2 className="card__title">{__('Wallet Sync and Security')}</h2>
{!isEmailVerified && (
<React.Fragment>
<p className="card__subtitle">
{__(`It looks like we don't have your email.`)}{' '}
<Button
button="link"
label={__('Verify your email')}
onClick={() => setComponentState({ ...componentState, showEmailReg: !componentState.showEmailReg })}
/>{' '}
{__(`and then come back here.`)}
</p>
{componentState.showEmailReg && <UserEmail />}
</React.Fragment>
)}
<Form onSubmit={() => apply()}>
<p>
{__(
'Your LBRY password can help you encrypt your wallet or sync it to another device. You must use the same LBRY password on every device if you wish to sync.'
)}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
</p>
<fieldset-section>
<FormField
autoFocus
inputButton={
<Button
icon={componentState.obscurePassword ? ICONS.EYE : ICONS.EYE_OFF}
button={'primary'}
onClick={() =>
setComponentState({ ...componentState, obscurePassword: !componentState.obscurePassword })
}
/>
}
label={__('Password')}
placeholder={__('Shh...')}
type={componentState.obscurePassword ? 'password' : 'text'}
name="wallet-new-password"
onChange={event => onChangeNewPassword(event)}
/>
</fieldset-section>
<fieldset-section>
<FormField
error={componentState.passwordMatch === false ? 'No match' : false}
label={__('Same Password')}
placeholder={__('Your eyes only')}
type="password"
name="wallet-new-password-confirm"
onChange={event => onChangeNewPasswordConfirm(event)}
/>
</fieldset-section>
<fieldset-section>
<FormField
label={__('Remember Password')}
type="checkbox"
name="wallet-remember-password"
onChange={event => onChangeRememberPassword(event)}
checked={componentState.rememberPassword}
/>
<FormField
type="checkbox"
name="encrypt_enabled"
checked={componentState.encryptWallet}
disabled={false}
onChange={event => onChangeEncrypt(event)}
label={__('Encrypt Wallet')}
/>
<FormField
type="checkbox"
name="sync_enabled"
checked={componentState.enableSync}
disabled={!isEmailVerified || !safeToSync}
error={!!syncApplyErrorMessage && syncApplyErrorMessage}
helper={!!syncApplyErrorMessage && syncApplyErrorMessage}
prefix={<span className="badge badge--alert">ALPHA</span>}
onChange={event => onChangeSync(event)}
label={
<React.Fragment>
{__('Enable Sync')} <Button button="link" label={__('(?)')} href="https://lbry.com/privacypolicy" />{' '}
<span className="badge badge--alert">ALPHA</span>
</React.Fragment>
}
/>
</fieldset-section>
<div className="card__subtitle--status">
{__(
'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.'
)}
</div>
<FormField
error={componentState.understandError === true ? 'You must enter "I understand"' : false}
label={__('Enter "I understand"')}
placeholder={__('I understand')}
type="text"
name="wallet-understand"
onChange={event => onChangeUnderstandConfirm(event)}
/>
{componentState.failMessage && <div className="error-text">{__(componentState.failMessage)}</div>}
<Submit
disabled={!componentState.passwordMatch}
label={componentState.failMessage ? __('Encrypting Wallet') : __('Apply')}
/>
</Form>
testing stuff
<Button
button="primary"
label={__('Sync Apply')}
onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
/>{' '}
<Button button="primary" label={__('Check Sync')} onClick={() => checkSync()} />{' '}
<Button button="primary" label={__('Setpass test')} onClick={() => setSavedPassword('test', 'testpass')} />{' '}
<Button
button="primary"
label={__('Getpass test')}
onClick={() => getSavedPassword('test').then(p => setComponentState({ ...componentState, newPassword: p }))}
/>{' '}
<Button button="primary" label={__('Deletepass test')} onClick={() => deleteSavedPassword('test')} />{' '}
<p>password: {componentState.newPassword}</p>
<p>encryptWallet: {String(componentState.encryptWallet)}</p>
<p>enableSync: {String(componentState.enableSync)}</p>
<p>syncApplyError: {String(syncApplyErrorMessage)}</p>
<p>Has Synced: {String(hasSyncedWallet)}</p>
<p>getSyncPending: {String(getSyncIsPending)}</p>
<p>syncEnabled: {String(syncEnabled)}</p>
<p>syncHash: {syncHash ? syncHash.slice(0, 10) : 'null'}</p>
<p>syncData: {syncData ? syncData.slice(0, 10) : 'null'}</p>
<p>walletEncrypted: {String(walletEncrypted)}</p>
<p>emailRegistered: {String(isEmailVerified)}</p>
<p>hashChanged: {String(hashChanged)}</p>
</section>
);
}
export default WalletSecurityAndSync;

View file

@ -19,6 +19,7 @@ export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
export const CHANGE_MODALS_ALLOWED = 'CHANGE_MODALS_ALLOWED';
export const TOGGLE_SEARCH_EXPANDED = 'TOGGLE_SEARCH_EXPANDED';
export const PASSWORD_SAVED = 'PASSWORD_SAVED';
// Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';

View file

@ -73,3 +73,5 @@ export const SUPPORT = 'TrendingUp';
export const BLOCK = 'Slash';
export const UNBLOCK = 'Circle';
export const VIEW = 'View';
export const EYE = 'Eye';
export const EYE_OFF = 'EyeOff';

View file

@ -0,0 +1,3 @@
export const AUTH_ORG = 'LBRY';
export const KEY_WALLET_PASSWORD = 'wallet_password';
export const KEY_AUTH_TOKEN = 'auth_token';

View file

@ -28,3 +28,5 @@ export const CONFIRM_THUMBNAIL_UPLOAD = 'confirm_thumbnail_upload';
export const WALLET_ENCRYPT = 'wallet_encrypt';
export const WALLET_DECRYPT = 'wallet_decrypt';
export const WALLET_UNLOCK = 'wallet_unlock';
export const WALLET_SYNC = 'wallet_sync';
export const WALLET_PASSWORD_UNSAVE = 'wallet_password_unsave';

View file

@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import ModalPasswordUnsave from './view';
import { doHideModal, doPasswordSaved } from 'redux/actions/app';
// const select = () => ({});
//
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
setPasswordSaved: saved => dispatch(doPasswordSaved(saved)),
});
export default connect(
null,
perform
)(ModalPasswordUnsave);

View file

@ -0,0 +1,38 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import keytar from 'keytar';
type Props = {
closeModal: () => void,
setPasswordSaved: boolean => void,
};
class ModalPasswordUnsave extends React.PureComponent<Props> {
render() {
return (
<Modal
isOpen
contentLabel={__('Unsave Password')}
title={__('Clear Saved Password')}
type="confirm"
confirmButtonLabel={__('Forget')}
abortButtonLabel={__('Nevermind')}
onConfirmed={() =>
keytar.deletePassword('LBRY', 'wallet_password').then(() => {
this.props.setPasswordSaved(false);
this.props.closeModal();
})
}
onAborted={this.props.closeModal}
>
<p>
{__('You are about to delete your saved password.')}{' '}
{__('Your wallet will still be encrypted, but you will have to remember and enter it manually on startup.')}
</p>
</Modal>
);
}
}
export default ModalPasswordUnsave;

View file

@ -28,6 +28,7 @@ import ModalWalletEncrypt from 'modal/modalWalletEncrypt';
import ModalWalletDecrypt from 'modal/modalWalletDecrypt';
import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalRewardCode from 'modal/modalRewardCode';
import ModalPasswordUnsave from 'modal/modalPasswordUnsave';
type Props = {
modal: { id: string, modalProps: {} },
@ -100,6 +101,8 @@ function ModalRouter(props: Props) {
return <ModalWalletDecrypt {...modalProps} />;
case MODALS.WALLET_UNLOCK:
return <ModalWalletUnlock {...modalProps} />;
case MODALS.WALLET_PASSWORD_UNSAVE:
return <ModalPasswordUnsave {...modalProps} />;
case MODALS.REWARD_GENERATED_CODE:
return <ModalRewardCode {...modalProps} />;
default:

View file

@ -1,16 +1,19 @@
import { connect } from 'react-redux';
import { doWalletStatus, doWalletDecrypt, selectWalletDecryptSucceeded } from 'lbry-redux';
import { doHideModal } from 'redux/actions/app';
import { doHideModal, doPasswordSaved } from 'redux/actions/app';
import ModalWalletDecrypt from './view';
import { selectIsPasswordSaved } from 'redux/selectors/app';
const select = state => ({
walletDecryptSucceded: selectWalletDecryptSucceeded(state),
isPasswordSaved: selectIsPasswordSaved(state),
});
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
decryptWallet: password => dispatch(doWalletDecrypt(password)),
updateWalletStatus: () => dispatch(doWalletStatus()),
setPasswordSaved: saved => dispatch(doPasswordSaved(saved)),
});
export default connect(

View file

@ -2,12 +2,15 @@
import React from 'react';
import { Modal } from 'modal/modal';
import Button from 'component/button';
import keytar from 'keytar';
type Props = {
closeModal: () => void,
decryptWallet: () => void,
updateWalletStatus: () => void,
walletDecryptSucceded: boolean,
passwordUnsaved: () => void,
setPasswordSaved: boolean => void,
};
type State = {
@ -23,6 +26,8 @@ class ModalWalletDecrypt extends React.PureComponent<Props, State> {
const { props, state } = this;
if (state.submitted && props.walletDecryptSucceded === true) {
keytar.deletePassword('LBRY', 'wallet_password');
props.setPasswordSaved(false);
props.closeModal();
props.updateWalletStatus();
}

View file

@ -1,12 +1,6 @@
import { connect } from 'react-redux';
import {
doWalletStatus,
doWalletEncrypt,
selectWalletEncryptPending,
selectWalletEncryptSucceeded,
selectWalletEncryptResult,
} from 'lbry-redux';
import { doHideModal } from 'redux/actions/app';
import { doWalletStatus, doWalletEncrypt, selectWalletEncryptSucceeded, selectWalletEncryptResult } from 'lbry-redux';
import { doHideModal, doPasswordSaved } from 'redux/actions/app';
import ModalWalletEncrypt from './view';
const select = state => ({
@ -18,6 +12,7 @@ const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
encryptWallet: password => dispatch(doWalletEncrypt(password)),
updateWalletStatus: () => dispatch(doWalletStatus()),
setPasswordSaved: saved => dispatch(doPasswordSaved(saved)),
});
export default connect(

View file

@ -3,6 +3,7 @@ import React from 'react';
import { Form, FormField, Submit } from 'component/common/form';
import { Modal } from 'modal/modal';
import Button from 'component/button';
import keytar from 'keytar';
type Props = {
closeModal: () => void,
@ -10,6 +11,7 @@ type Props = {
updateWalletStatus: boolean,
encryptWallet: (?string) => void,
updateWalletStatus: () => void,
setPasswordSaved: boolean => void,
};
type State = {
@ -20,6 +22,7 @@ type State = {
understandError: boolean,
submitted: boolean,
failMessage: ?string,
rememberPassword: boolean,
};
class ModalWalletEncrypt extends React.PureComponent<Props, State> {
@ -31,6 +34,7 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
understandError: false,
submitted: false, // Prior actions could be marked complete
failMessage: undefined,
rememberPassword: false,
};
componentDidUpdate() {
@ -51,6 +55,10 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
this.setState({ newPassword: event.target.value });
}
onChangeRememberPassword(event: SyntheticInputEvent<>) {
this.setState({ rememberPassword: event.target.checked });
}
onChangeNewPasswordConfirm(event: SyntheticInputEvent<>) {
this.setState({ newPasswordConfirm: event.target.value });
}
@ -79,7 +87,10 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
if (invalidEntries === true) {
return;
}
if (state.rememberPassword === true) {
this.props.setPasswordSaved(true);
keytar.setPassword('LBRY', 'wallet_password', state.newPassword);
}
this.setState({ submitted: true });
this.props.encryptWallet(state.newPassword);
}
@ -88,7 +99,6 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
const { closeModal } = this.props;
const { passwordMismatch, understandError, failMessage } = this.state;
return (
<Modal
isOpen
@ -126,6 +136,15 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
onChange={event => this.onChangeNewPasswordConfirm(event)}
/>
</fieldset-section>
<fieldset-section>
<FormField
label={__('Remember Password')}
type="checkbox"
name="wallet-remember-password"
onChange={event => this.onChangeRememberPassword(event)}
checked={this.state.rememberPassword}
/>
</fieldset-section>
<div className="card__subtitle--status">
{__(

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { doWalletUnlock, selectWalletUnlockPending, selectWalletUnlockSucceeded } from 'lbry-redux';
import { doQuit, doHideModal } from 'redux/actions/app';
import { doWalletUnlock, selectWalletUnlockSucceeded } from 'lbry-redux';
import { doQuit, doHideModal, doPasswordSaved } from 'redux/actions/app';
import ModalWalletUnlock from './view';
const select = state => ({
@ -11,6 +11,7 @@ const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
quit: () => dispatch(doQuit()),
unlockWallet: password => dispatch(doWalletUnlock(password)),
setPasswordSaved: saved => dispatch(doPasswordSaved(saved)),
});
export default connect(

View file

@ -3,27 +3,35 @@ import React from 'react';
import { Form, FormField } from 'component/common/form';
import { Modal } from 'modal/modal';
import Button from 'component/button';
import keytar from 'keytar';
type Props = {
quit: () => void,
closeModal: () => void,
unlockWallet: (?string) => void,
walletUnlockSucceded: boolean,
setPasswordSaved: boolean => void,
};
type State = {
password: string,
rememberPassword: boolean,
};
class ModalWalletUnlock extends React.PureComponent<Props, State> {
state = {
password: '',
rememberPassword: false,
};
componentDidUpdate() {
const { props } = this;
if (props.walletUnlockSucceded === true) {
if (this.state.rememberPassword) {
this.props.setPasswordSaved(true);
keytar.setPassword('LBRY', 'wallet_password', this.state.password);
}
props.closeModal();
}
}
@ -32,11 +40,14 @@ class ModalWalletUnlock extends React.PureComponent<Props, State> {
this.setState({ password: event.target.value });
}
onChangeRememberPassword(event: SyntheticInputEvent<>) {
this.setState({ rememberPassword: event.target.checked });
}
render() {
const { quit, unlockWallet, walletUnlockSucceded } = this.props;
const { password } = this.state;
const { password, rememberPassword } = this.state;
return (
<Modal
isOpen
@ -61,7 +72,18 @@ class ModalWalletUnlock extends React.PureComponent<Props, State> {
type="password"
name="wallet-password"
onChange={event => this.onChangePassword(event)}
value={password || ''}
/>
<fieldset-section>
<FormField
label={__('Remember Password')}
type="checkbox"
name="wallet-remember-password"
onChange={event => this.onChangeRememberPassword(event)}
checked={rememberPassword}
helper={__('You will no longer see this at startup')}
/>
</fieldset-section>
</Form>
</Modal>
);

View file

@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import * as SETTINGS from 'constants/settings';
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet } from 'redux/actions/app';
import { doSetDaemonSetting, doSetClientSetting, doGetThemes, doSetDarkTime } from 'redux/actions/settings';
import { selectIsPasswordSaved } from 'redux/selectors/app';
import { doSetPlayingUri } from 'redux/actions/content';
import { makeSelectClientSetting, selectDaemonSettings, selectosNotificationsEnabled } from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, selectBlockedChannelsCount } from 'lbry-redux';
@ -34,6 +35,8 @@ const perform = dispatch => ({
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
updateWalletStatus: () => dispatch(doWalletStatus()),
confirmForgetPassword: modalProps => dispatch(doNotifyForgetPassword(modalProps)),
setPasswordSaved: saved => dispatch(doPasswordSaved(saved)),
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
setDarkTime: (time, options) => dispatch(doSetDarkTime(time, options)),
});

View file

@ -11,6 +11,9 @@ import I18nMessage from 'component/i18nMessage';
import Page from 'component/page';
import SettingLanguage from 'component/settingLanguage';
import FileSelector from 'component/common/file-selector';
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
import keytar from 'keytar';
import WalletSecurityAndSync from '../../component/walletSecurityAndSync';
type Price = {
currency: string,
@ -60,6 +63,9 @@ type Props = {
supportOption: boolean,
userBlockedChannelsCount?: number,
hideBalance: boolean,
confirmForgetPassword: () => void,
isPasswordSaved: boolean,
setPasswordSaved: boolean => void,
floatingPlayer: boolean,
clearPlayingUri: () => void,
darkModeTimes: DarkModeTimes,
@ -68,6 +74,7 @@ type Props = {
type State = {
clearingCache: boolean,
storedPassword: boolean,
};
class SettingsPage extends React.PureComponent<Props, State> {
@ -76,6 +83,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
this.state = {
clearingCache: false,
storedPassword: false,
};
(this: any).onKeyFeeChange = this.onKeyFeeChange.bind(this);
@ -91,6 +99,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
componentDidMount() {
this.props.getThemes();
this.props.updateWalletStatus();
keytar.getPassword('LBRY', 'wallet_password').then(p => {
if (p || p === '') {
this.props.setPasswordSaved(true);
} else {
this.props.setPasswordSaved(false);
}
});
}
onKeyFeeChange(newValue: Price) {
@ -137,6 +152,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
}
onConfirmForgetPassword() {
const { confirmForgetPassword } = this.props;
confirmForgetPassword();
}
onChangeTime(event: SyntheticInputEvent<*>, options: OptionTimes) {
const { value } = event.target;
@ -189,6 +209,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
floatingPlayer,
clearPlayingUri,
darkModeTimes,
isPasswordSaved,
} = this.props;
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
@ -229,6 +250,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
<p className="help">{__('LBRY downloads will be saved here.')}</p>
</div>
</section>
<WalletSecurityAndSync />
<section className="card card--section">
<h2 className="card__title">{__('Network and Data Settings')}</h2>
<Form>
@ -497,7 +519,16 @@ class SettingsPage extends React.PureComponent<Props, State> {
</React.Fragment>
}
/>
{isPasswordSaved && (
<p className="card__subtitle card__help">
{__('Your password is saved in your OS keychain.')}{' '}
<Button
button="link"
label={__('I want to type it manually')}
onClick={this.onConfirmForgetPassword}
/>
</p>
)}
<FormField
type="checkbox"
name="hide_balance"

View file

@ -20,6 +20,7 @@ import {
filteredReducer,
homepageReducer,
statsReducer,
syncReducer,
} from 'lbryinc';
import appReducer from 'redux/reducers/app';
import availabilityReducer from 'redux/reducers/availability';
@ -52,4 +53,5 @@ export default history =>
blocked: blockedReducer,
user: userReducer,
wallet: walletReducer,
sync: syncReducer,
});

View file

@ -307,12 +307,33 @@ export function doNotifyUnlockWallet() {
};
}
export function doNotifyForgetPassword() {
return dispatch => {
dispatch(doOpenModal(MODALS.WALLET_PASSWORD_UNSAVE));
};
}
export function doNotifySyncWallet() {
return dispatch => {
dispatch(doOpenModal(MODALS.WALLET_SYNC));
};
}
export function doAlertError(errorList) {
return dispatch => {
dispatch(doError(errorList));
};
}
export function doPasswordSaved(saved) {
return dispatch => {
dispatch({
type: ACTIONS.PASSWORD_SAVED,
data: saved,
});
};
}
export function doDaemonReady() {
return (dispatch, getState) => {
const state = getState();

View file

@ -38,6 +38,7 @@ export type AppState = {
hasClickedComment: boolean,
enhancedLayout: boolean,
searchOptionsExpanded: boolean,
isPasswordSaved: boolean,
};
const defaultState: AppState = {
@ -66,6 +67,7 @@ const defaultState: AppState = {
searchOptionsExpanded: false,
currentScroll: 0,
scrollHistory: [0],
isPasswordSaved: false,
};
// @@router comes from react-router
@ -96,6 +98,11 @@ reducers[ACTIONS.DAEMON_READY] = state =>
daemonReady: true,
});
reducers[ACTIONS.PASSWORD_SAVED] = (state, action) =>
Object.assign({}, state, {
isPasswordSaved: action.data,
});
reducers[ACTIONS.DAEMON_VERSION_MATCH] = state =>
Object.assign({}, state, {
daemonVersionMatched: true,

View file

@ -133,3 +133,8 @@ export const selectScrollStartingPosition = createSelector(
selectState,
state => state.currentScroll
);
export const selectIsPasswordSaved = createSelector(
selectState,
state => state.isPasswordSaved
);

View file

@ -0,0 +1,10 @@
import keytar from 'keytar';
import { AUTH_ORG } from 'constants/keychain';
export const setSavedPassword = (key, value) => {
keytar.setPassword(AUTH_ORG, key, value);
};
export const getSavedPassword = key => keytar.getPassword(AUTH_ORG, key).then(p => p);
export const deleteSavedPassword = key => keytar.deletePassword(AUTH_ORG, key).catch(e => console.log(e));

View file

@ -81,6 +81,7 @@ let baseConfig = {
loader: 'raw-loader',
},
},
{ test: /\.node$/, loader: 'node-loader' },
],
},
// Allows imports for all directories inside '/ui'