Discourage private keys (pt. 1) (#780)
* Insecure wallet blocker warning before unlocking insecure wallet. * Wrap in quotes to avoid prettier error. * Make account the homepage. Add a link to generate on the wallet unlock component. * Fix send routing weirdness.
This commit is contained in:
parent
2ac3015ad8
commit
4fb342a757
|
@ -59,16 +59,15 @@ export default class Root extends Component<Props, State> {
|
|||
const routes = (
|
||||
<CaptureRouteNotFound>
|
||||
<Switch>
|
||||
<Route exact={true} path="/" component={GenerateWallet} />
|
||||
<Route path="/generate" component={GenerateWallet} />
|
||||
<Redirect exact={true} from="/" to="/account" />
|
||||
<Route path="/account" component={SendTransaction} />
|
||||
<Route path="/generate" component={GenerateWallet} />
|
||||
<Route path="/swap" component={Swap} />
|
||||
<Route path="/contracts" component={Contracts} />
|
||||
<Route path="/ens" component={ENS} />
|
||||
<Route path="/help" component={Help} />
|
||||
<Route path="/sign-and-verify-message" component={SignAndVerifyMessage} />
|
||||
<Route path="/pushTx" component={BroadcastTx} />
|
||||
<Route path="/send-transaction" component={SendTransaction} />
|
||||
<RouteNotFound />
|
||||
</Switch>
|
||||
</CaptureRouteNotFound>
|
||||
|
|
|
@ -10,15 +10,14 @@ export interface TabLink {
|
|||
}
|
||||
|
||||
const tabs: TabLink[] = [
|
||||
{
|
||||
name: 'NAV_GenerateWallet',
|
||||
to: '/generate'
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Account View & Send',
|
||||
to: '/account'
|
||||
},
|
||||
{
|
||||
name: 'NAV_GenerateWallet',
|
||||
to: '/generate'
|
||||
},
|
||||
{
|
||||
name: 'NAV_Swap',
|
||||
to: '/swap'
|
||||
|
|
|
@ -45,6 +45,12 @@ $speed: 500ms;
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-generate {
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
margin-top: $space;
|
||||
}
|
||||
}
|
||||
|
||||
&-decrypt {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||
import {
|
||||
|
@ -27,7 +28,8 @@ import {
|
|||
TrezorDecrypt,
|
||||
ViewOnlyDecrypt,
|
||||
Web3Decrypt,
|
||||
WalletButton
|
||||
WalletButton,
|
||||
InsecureWalletWarning
|
||||
} from './components';
|
||||
import { AppState } from 'reducers';
|
||||
import DISABLES from './disables';
|
||||
|
@ -52,6 +54,7 @@ import { getNetworkConfig } from '../../selectors/config';
|
|||
interface OwnProps {
|
||||
hidden?: boolean;
|
||||
disabledWallets?: WalletName[];
|
||||
showGenerateLink?: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -78,6 +81,7 @@ type UnlockParams = {} | PrivateKeyValue;
|
|||
interface State {
|
||||
selectedWalletKey: WalletName | null;
|
||||
value: UnlockParams | null;
|
||||
hasAcknowledgedInsecure: boolean;
|
||||
}
|
||||
|
||||
interface BaseWalletInfo {
|
||||
|
@ -121,6 +125,10 @@ type Wallets = SecureWallets & InsecureWallets & MiscWallet;
|
|||
const WEB3_TYPE: string | false =
|
||||
(window as any).web3 && (window as any).web3.currentProvider.constructor.name;
|
||||
|
||||
const SECURE_WALLETS = Object.values(SecureWalletName);
|
||||
const INSECURE_WALLETS = Object.values(InsecureWalletName);
|
||||
const MISC_WALLETS = Object.values(MiscWalletName);
|
||||
|
||||
export class WalletDecrypt extends Component<Props, State> {
|
||||
// https://github.com/Microsoft/TypeScript/issues/13042
|
||||
// index signature should become [key: Wallets] (from config) once typescript bug is fixed
|
||||
|
@ -197,7 +205,8 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
|
||||
public state: State = {
|
||||
selectedWalletKey: null,
|
||||
value: null
|
||||
value: null,
|
||||
hasAcknowledgedInsecure: false
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
|
@ -220,36 +229,59 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public getDecryptionComponent() {
|
||||
const { selectedWalletKey, hasAcknowledgedInsecure } = this.state;
|
||||
const selectedWallet = this.getSelectedWallet();
|
||||
if (!selectedWallet) {
|
||||
if (!selectedWalletKey || !selectedWallet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (INSECURE_WALLETS.includes(selectedWalletKey) && !hasAcknowledgedInsecure) {
|
||||
return (
|
||||
<div className="WalletDecrypt-decrypt">
|
||||
<InsecureWalletWarning
|
||||
walletType={translate(selectedWallet.lid)}
|
||||
onContinue={this.handleAcknowledgeInsecure}
|
||||
onCancel={this.clearWalletChoice}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<selectedWallet.component
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
onUnlock={this.onUnlock}
|
||||
showNotification={this.props.showNotification}
|
||||
isWalletPending={
|
||||
this.state.selectedWalletKey === InsecureWalletName.KEYSTORE_FILE
|
||||
? this.props.isWalletPending
|
||||
: undefined
|
||||
}
|
||||
isPasswordPending={
|
||||
this.state.selectedWalletKey === InsecureWalletName.KEYSTORE_FILE
|
||||
? this.props.isPasswordPending
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<div className="WalletDecrypt-decrypt">
|
||||
<button className="WalletDecrypt-decrypt-back" onClick={this.clearWalletChoice}>
|
||||
<i className="fa fa-arrow-left" /> {translate('Change Wallet')}
|
||||
</button>
|
||||
<h2 className="WalletDecrypt-decrypt-title">
|
||||
{!selectedWallet.isReadOnly && 'Unlock your'} {translate(selectedWallet.lid)}
|
||||
</h2>
|
||||
<section className="WalletDecrypt-decrypt-form">
|
||||
<selectedWallet.component
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
onUnlock={this.onUnlock}
|
||||
showNotification={this.props.showNotification}
|
||||
isWalletPending={
|
||||
this.state.selectedWalletKey === InsecureWalletName.KEYSTORE_FILE
|
||||
? this.props.isWalletPending
|
||||
: undefined
|
||||
}
|
||||
isPasswordPending={
|
||||
this.state.selectedWalletKey === InsecureWalletName.KEYSTORE_FILE
|
||||
? this.props.isPasswordPending
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public buildWalletOptions() {
|
||||
const SECURE_WALLETS = Object.values(SecureWalletName);
|
||||
const INSECURE_WALLETS = Object.values(InsecureWalletName);
|
||||
const MISC_WALLETS = Object.values(MiscWalletName);
|
||||
public handleAcknowledgeInsecure = () => {
|
||||
this.setState({ hasAcknowledgedInsecure: true });
|
||||
};
|
||||
|
||||
public buildWalletOptions() {
|
||||
return (
|
||||
<div className="WalletDecrypt-wallets">
|
||||
<h2 className="WalletDecrypt-wallets-title">{translate('decrypt_Access')}</h2>
|
||||
|
@ -305,6 +337,12 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{this.props.showGenerateLink && (
|
||||
<div className="WalletDecrypt-wallets-generate">
|
||||
Don’t have a wallet? <Link to="/generate">Click here to get one</Link>.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -328,7 +366,8 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
window.setTimeout(() => {
|
||||
this.setState({
|
||||
selectedWalletKey: walletType,
|
||||
value: wallet.initialParams
|
||||
value: wallet.initialParams,
|
||||
hasAcknowledgedInsecure: false
|
||||
});
|
||||
}, timeout);
|
||||
};
|
||||
|
@ -336,7 +375,8 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
public clearWalletChoice = () => {
|
||||
this.setState({
|
||||
selectedWalletKey: null,
|
||||
value: null
|
||||
value: null,
|
||||
hasAcknowledgedInsecure: false
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -352,21 +392,7 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
<TransitionGroup>
|
||||
{decryptionComponent && selectedWallet ? (
|
||||
<CSSTransition classNames="DecryptContent" timeout={500} key="decrypt">
|
||||
<div className="WalletDecrypt-decrypt">
|
||||
<button
|
||||
className="WalletDecrypt-decrypt-back"
|
||||
onClick={this.clearWalletChoice}
|
||||
>
|
||||
<i className="fa fa-arrow-left" /> {translate('Change Wallet')}
|
||||
</button>
|
||||
<h2 className="WalletDecrypt-decrypt-title">
|
||||
{!selectedWallet.isReadOnly && 'Unlock your'}{' '}
|
||||
{translate(selectedWallet.lid)}
|
||||
</h2>
|
||||
<section className="WalletDecrypt-decrypt-form">
|
||||
{decryptionComponent}
|
||||
</section>
|
||||
</div>
|
||||
{decryptionComponent}
|
||||
</CSSTransition>
|
||||
) : (
|
||||
<CSSTransition classNames="DecryptContent" timeout={500} key="wallets">
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.WalletWarning {
|
||||
max-width: 780px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
|
||||
&-title {
|
||||
color: $brand-danger;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&-desc {
|
||||
margin-bottom: $space;
|
||||
}
|
||||
|
||||
&-check {
|
||||
margin-bottom: $space * 2;
|
||||
}
|
||||
|
||||
&-checkboxes {
|
||||
margin-bottom: $space * 2;
|
||||
}
|
||||
|
||||
&-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: -$space-sm;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
margin: 0 $space-sm $space-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AcknowledgeCheckbox {
|
||||
margin-bottom: $space-sm;
|
||||
|
||||
&-checkbox[type="checkbox"] {
|
||||
display: inline-block;
|
||||
margin-right: $space-sm;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-size: $font-size-bump-more;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
import React from 'react';
|
||||
import './InsecureWalletWarning.scss';
|
||||
|
||||
interface Props {
|
||||
walletType: string | React.ReactElement<string>;
|
||||
onContinue(): void;
|
||||
onCancel(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasConfirmedSite: boolean;
|
||||
hasAcknowledgedDownload: boolean;
|
||||
hasAcknowledgedWallets: boolean;
|
||||
}
|
||||
|
||||
interface Checkbox {
|
||||
name: keyof State;
|
||||
label: string | React.ReactElement<string>;
|
||||
}
|
||||
|
||||
export class InsecureWalletWarning extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
hasConfirmedSite: false,
|
||||
hasAcknowledgedDownload: false,
|
||||
hasAcknowledgedWallets: false
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
if (process.env.BUILD_DOWNLOADABLE) {
|
||||
props.onContinue();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (process.env.BUILD_DOWNLOADABLE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { walletType, onContinue, onCancel } = this.props;
|
||||
const checkboxes: Checkbox[] = [
|
||||
{
|
||||
name: 'hasAcknowledgedWallets',
|
||||
label: 'I acknowledge that I can and should use MetaMask or a Hardware Wallet'
|
||||
},
|
||||
{
|
||||
name: 'hasAcknowledgedDownload',
|
||||
label: 'I acknowledge that I can and should download and run MyEtherWallet locally'
|
||||
},
|
||||
{
|
||||
name: 'hasConfirmedSite',
|
||||
label:
|
||||
'I have checked the URL and SSL certificate to make sure this is the real MyEtherWallet'
|
||||
}
|
||||
];
|
||||
const canContinue = checkboxes.reduce(
|
||||
(prev, checkbox) => prev && this.state[checkbox.name],
|
||||
true
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="WalletWarning">
|
||||
<h2 className="WalletWarning-title">
|
||||
This is <u>not</u> a recommended way to access your wallet
|
||||
</h2>
|
||||
<p className="WalletWarning-desc">
|
||||
Entering your {walletType} on a website is <strong>dangerous</strong>. If our website is
|
||||
compromised, or you accidentally visit a phishing website, you could{' '}
|
||||
<strong>lose all of your funds</strong>. Before you continue, please consider:
|
||||
</p>
|
||||
<ul className="WalletWarning-bullets">
|
||||
<li>
|
||||
Using{' '}
|
||||
<a href="https://myetherwallet.github.io/knowledge-base/migration/moving-from-private-key-to-metamask.html">
|
||||
MetaMask
|
||||
</a>{' '}
|
||||
or a{' '}
|
||||
<a href="https://myetherwallet.github.io/knowledge-base/hardware-wallets/hardware-wallet-recommendations.html">
|
||||
Hardware Wallet
|
||||
</a>{' '}
|
||||
to access your wallet
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://myetherwallet.github.io/knowledge-base/offline/running-myetherwallet-locally.html">
|
||||
Downloading MEW and running it offline & locally
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
Reading{' '}
|
||||
<a href="https://myetherwallet.github.io/knowledge-base/security/securing-your-ethereum.html">
|
||||
How to Protect Yourself and Your Funds
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="WalletWarning-check">
|
||||
If you must use your {walletType} online, please double-check the URL & SSL certificate.
|
||||
It should say <code>{'https://www.myetherwallet.com'}</code>
|
||||
& <code>MYETHERWALLET LLC [US]</code> in your URL bar.
|
||||
</p>
|
||||
<div className="WalletWarning-checkboxes">{checkboxes.map(this.makeCheckbox)}</div>
|
||||
|
||||
<div className="WalletWarning-buttons">
|
||||
<button className="WalletWarning-cancel btn btn-lg btn-default" onClick={onCancel}>
|
||||
Go Back
|
||||
</button>
|
||||
<button
|
||||
className="WalletWarning-continue btn btn-lg btn-primary"
|
||||
onClick={onContinue}
|
||||
disabled={!canContinue}
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private makeCheckbox = (checkbox: Checkbox) => {
|
||||
return (
|
||||
<label className="AcknowledgeCheckbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
name={checkbox.name}
|
||||
className="AcknowledgeCheckbox-checkbox"
|
||||
onChange={this.handleCheckboxChange}
|
||||
checked={this.state[checkbox.name]}
|
||||
/>
|
||||
<span className="AcknowledgeCheckbox-label">{checkbox.label}</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
private handleCheckboxChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
[ev.currentTarget.name as any]: !!ev.currentTarget.checked
|
||||
});
|
||||
};
|
||||
}
|
|
@ -93,6 +93,8 @@ export class PrivateKeyDecrypt extends Component<Props> {
|
|||
};
|
||||
|
||||
public onPasswordChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
// NOTE: Textareas don't support password type, so we replace the value
|
||||
// with an equal length number of dots. On change, we replace
|
||||
const pkey = this.props.value.key;
|
||||
const pass = e.currentTarget.value;
|
||||
const { valid } = validatePkeyAndPass(pkey, pass);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * from './DeterministicWalletsModal';
|
||||
export * from './DigitalBitbox';
|
||||
export * from './InsecureWalletWarning';
|
||||
export * from './Keystore';
|
||||
export * from './LedgerNano';
|
||||
export * from './Mnemonic';
|
||||
|
|
|
@ -11,6 +11,7 @@ interface Props {
|
|||
title: TranslateType;
|
||||
wallet: IWallet;
|
||||
disabledWallets?: WalletName[];
|
||||
showGenerateLink?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -29,7 +30,7 @@ export class UnlockHeader extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { title, wallet, disabledWallets } = this.props;
|
||||
const { title, wallet, disabledWallets, showGenerateLink } = this.props;
|
||||
const { isExpanded } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -55,7 +56,11 @@ export class UnlockHeader extends React.PureComponent<Props, State> {
|
|||
<i className="fa fa-times" />
|
||||
</button>
|
||||
)}
|
||||
<WalletDecrypt hidden={!this.state.isExpanded} disabledWallets={disabledWallets} />
|
||||
<WalletDecrypt
|
||||
hidden={!this.state.isExpanded}
|
||||
disabledWallets={disabledWallets}
|
||||
showGenerateLink={showGenerateLink}
|
||||
/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -56,39 +56,41 @@ class SendTransaction extends React.Component<Props> {
|
|||
return (
|
||||
<TabSection>
|
||||
<section className="Tab-content">
|
||||
<UnlockHeader title={translate('Account')} />
|
||||
<div className="SubTabs row">
|
||||
<div className="col-sm-8">{wallet && <SubTabs tabs={tabs} match={match} />}</div>
|
||||
<div className="col-sm-8">
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={currentPath}
|
||||
render={() => (
|
||||
<Redirect
|
||||
from={`${currentPath}`}
|
||||
to={`${
|
||||
wallet && wallet.isReadOnly ? currentPath + '/info' : currentPath + '/send'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route exact={true} path={`${currentPath}/send`} component={Send} />
|
||||
<Route
|
||||
path={`${currentPath}/info`}
|
||||
exact={true}
|
||||
render={() => wallet && <WalletInfo wallet={wallet} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${currentPath}/request`}
|
||||
exact={true}
|
||||
render={() => <RequestPayment wallet={wallet} />}
|
||||
/>
|
||||
<RouteNotFound />
|
||||
</Switch>
|
||||
<UnlockHeader title={translate('Account')} showGenerateLink={true} />
|
||||
{wallet && (
|
||||
<div className="SubTabs row">
|
||||
<div className="col-sm-8">
|
||||
<SubTabs tabs={tabs} match={match} />
|
||||
</div>
|
||||
<div className="col-sm-8">
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={currentPath}
|
||||
render={() => (
|
||||
<Redirect
|
||||
from={`${currentPath}`}
|
||||
to={`${wallet.isReadOnly ? `${currentPath}/info` : `${currentPath}/send`}`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route exact={true} path={`${currentPath}/send`} component={Send} />
|
||||
<Route
|
||||
path={`${currentPath}/info`}
|
||||
exact={true}
|
||||
render={() => <WalletInfo wallet={wallet} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${currentPath}/request`}
|
||||
exact={true}
|
||||
render={() => <RequestPayment wallet={wallet} />}
|
||||
/>
|
||||
<RouteNotFound />
|
||||
</Switch>
|
||||
</div>
|
||||
<SideBar />
|
||||
</div>
|
||||
<SideBar />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</TabSection>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue