recommit
This commit is contained in:
parent
bca759b522
commit
c2b881e10e
24 changed files with 625 additions and 16 deletions
|
@ -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" />
|
||||
|
|
68
src/ui/component/walletSecurityAndSync/index.js
Normal file
68
src/ui/component/walletSecurityAndSync/index.js
Normal 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);
|
340
src/ui/component/walletSecurityAndSync/view.jsx
Normal file
340
src/ui/component/walletSecurityAndSync/view.jsx
Normal 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;
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
3
src/ui/constants/keychain.js
Normal file
3
src/ui/constants/keychain.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const AUTH_ORG = 'LBRY';
|
||||
export const KEY_WALLET_PASSWORD = 'wallet_password';
|
||||
export const KEY_AUTH_TOKEN = 'auth_token';
|
|
@ -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';
|
||||
|
|
15
src/ui/modal/modalPasswordUnsave/index.js
Normal file
15
src/ui/modal/modalPasswordUnsave/index.js
Normal 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);
|
38
src/ui/modal/modalPasswordUnsave/view.jsx
Normal file
38
src/ui/modal/modalPasswordUnsave/view.jsx
Normal 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;
|
|
@ -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:
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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">
|
||||
{__(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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)),
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -133,3 +133,8 @@ export const selectScrollStartingPosition = createSelector(
|
|||
selectState,
|
||||
state => state.currentScroll
|
||||
);
|
||||
|
||||
export const selectIsPasswordSaved = createSelector(
|
||||
selectState,
|
||||
state => state.isPasswordSaved
|
||||
);
|
||||
|
|
10
src/ui/util/saved-passwords.js
Normal file
10
src/ui/util/saved-passwords.js
Normal 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));
|
|
@ -81,6 +81,7 @@ let baseConfig = {
|
|||
loader: 'raw-loader',
|
||||
},
|
||||
},
|
||||
{ test: /\.node$/, loader: 'node-loader' },
|
||||
],
|
||||
},
|
||||
// Allows imports for all directories inside '/ui'
|
||||
|
|
Loading…
Reference in a new issue