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:
William O'Beirne 2018-01-24 17:23:20 -05:00 committed by Daniel Ternyak
parent 2ac3015ad8
commit 4fb342a757
10 changed files with 311 additions and 82 deletions

View File

@ -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>

View File

@ -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'

View File

@ -45,6 +45,12 @@ $speed: 500ms;
margin: 0;
}
}
&-generate {
text-align: center;
font-weight: 300;
margin-top: $space;
}
}
&-decrypt {

View File

@ -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,12 +229,33 @@ 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 (
<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}
@ -242,14 +272,16 @@ export class WalletDecrypt extends Component<Props, State> {
: 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">
Dont 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>
</CSSTransition>
) : (
<CSSTransition classNames="DecryptContent" timeout={500} key="wallets">

View File

@ -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;
}
}

View File

@ -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
});
};
}

View File

@ -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);

View File

@ -1,5 +1,6 @@
export * from './DeterministicWalletsModal';
export * from './DigitalBitbox';
export * from './InsecureWalletWarning';
export * from './Keystore';
export * from './LedgerNano';
export * from './Mnemonic';

View File

@ -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>
);
}

View File

@ -56,9 +56,12 @@ class SendTransaction extends React.Component<Props> {
return (
<TabSection>
<section className="Tab-content">
<UnlockHeader title={translate('Account')} />
<UnlockHeader title={translate('Account')} showGenerateLink={true} />
{wallet && (
<div className="SubTabs row">
<div className="col-sm-8">{wallet && <SubTabs tabs={tabs} match={match} />}</div>
<div className="col-sm-8">
<SubTabs tabs={tabs} match={match} />
</div>
<div className="col-sm-8">
<Switch>
<Route
@ -67,9 +70,7 @@ class SendTransaction extends React.Component<Props> {
render={() => (
<Redirect
from={`${currentPath}`}
to={`${
wallet && wallet.isReadOnly ? currentPath + '/info' : currentPath + '/send'
}`}
to={`${wallet.isReadOnly ? `${currentPath}/info` : `${currentPath}/send`}`}
/>
)}
/>
@ -77,7 +78,7 @@ class SendTransaction extends React.Component<Props> {
<Route
path={`${currentPath}/info`}
exact={true}
render={() => wallet && <WalletInfo wallet={wallet} />}
render={() => <WalletInfo wallet={wallet} />}
/>
<Route
path={`${currentPath}/request`}
@ -89,6 +90,7 @@ class SendTransaction extends React.Component<Props> {
</div>
<SideBar />
</div>
)}
</section>
</TabSection>
);