diff --git a/common/Root.tsx b/common/Root.tsx index a869fd86..38ea866f 100644 --- a/common/Root.tsx +++ b/common/Root.tsx @@ -59,16 +59,15 @@ export default class Root extends Component { const routes = ( - - + + - diff --git a/common/components/Header/components/Navigation.tsx b/common/components/Header/components/Navigation.tsx index 1f68c236..103070f2 100644 --- a/common/components/Header/components/Navigation.tsx +++ b/common/components/Header/components/Navigation.tsx @@ -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' diff --git a/common/components/WalletDecrypt/WalletDecrypt.scss b/common/components/WalletDecrypt/WalletDecrypt.scss index 9b5f7c2d..35ba52e7 100644 --- a/common/components/WalletDecrypt/WalletDecrypt.scss +++ b/common/components/WalletDecrypt/WalletDecrypt.scss @@ -45,6 +45,12 @@ $speed: 500ms; margin: 0; } } + + &-generate { + text-align: center; + font-weight: 300; + margin-top: $space; + } } &-decrypt { diff --git a/common/components/WalletDecrypt/WalletDecrypt.tsx b/common/components/WalletDecrypt/WalletDecrypt.tsx index 1276e67c..d0a330c9 100644 --- a/common/components/WalletDecrypt/WalletDecrypt.tsx +++ b/common/components/WalletDecrypt/WalletDecrypt.tsx @@ -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 { // 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 { public state: State = { selectedWalletKey: null, - value: null + value: null, + hasAcknowledgedInsecure: false }; public componentWillReceiveProps(nextProps: Props) { @@ -220,36 +229,59 @@ export class WalletDecrypt extends Component { } 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 ( +
+ +
+ ); + } + return ( - +
+ +

+ {!selectedWallet.isReadOnly && 'Unlock your'} {translate(selectedWallet.lid)} +

+
+ +
+
); } - 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 (

{translate('decrypt_Access')}

@@ -305,6 +337,12 @@ export class WalletDecrypt extends Component { ); })}
+ + {this.props.showGenerateLink && ( +
+ Don’t have a wallet? Click here to get one. +
+ )} ); } @@ -328,7 +366,8 @@ export class WalletDecrypt extends Component { window.setTimeout(() => { this.setState({ selectedWalletKey: walletType, - value: wallet.initialParams + value: wallet.initialParams, + hasAcknowledgedInsecure: false }); }, timeout); }; @@ -336,7 +375,8 @@ export class WalletDecrypt extends Component { public clearWalletChoice = () => { this.setState({ selectedWalletKey: null, - value: null + value: null, + hasAcknowledgedInsecure: false }); }; @@ -352,21 +392,7 @@ export class WalletDecrypt extends Component { {decryptionComponent && selectedWallet ? ( -
- -

- {!selectedWallet.isReadOnly && 'Unlock your'}{' '} - {translate(selectedWallet.lid)} -

-
- {decryptionComponent} -
-
+ {decryptionComponent}
) : ( diff --git a/common/components/WalletDecrypt/components/InsecureWalletWarning.scss b/common/components/WalletDecrypt/components/InsecureWalletWarning.scss new file mode 100644 index 00000000..d93742a9 --- /dev/null +++ b/common/components/WalletDecrypt/components/InsecureWalletWarning.scss @@ -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; + } +} diff --git a/common/components/WalletDecrypt/components/InsecureWalletWarning.tsx b/common/components/WalletDecrypt/components/InsecureWalletWarning.tsx new file mode 100644 index 00000000..87d2d55f --- /dev/null +++ b/common/components/WalletDecrypt/components/InsecureWalletWarning.tsx @@ -0,0 +1,138 @@ +import React from 'react'; +import './InsecureWalletWarning.scss'; + +interface Props { + walletType: string | React.ReactElement; + onContinue(): void; + onCancel(): void; +} + +interface State { + hasConfirmedSite: boolean; + hasAcknowledgedDownload: boolean; + hasAcknowledgedWallets: boolean; +} + +interface Checkbox { + name: keyof State; + label: string | React.ReactElement; +} + +export class InsecureWalletWarning extends React.Component { + 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 ( +
+

+ This is not a recommended way to access your wallet +

+

+ Entering your {walletType} on a website is dangerous. If our website is + compromised, or you accidentally visit a phishing website, you could{' '} + lose all of your funds. Before you continue, please consider: +

+ +

+ If you must use your {walletType} online, please double-check the URL & SSL certificate. + It should say {'https://www.myetherwallet.com'} + & MYETHERWALLET LLC [US] in your URL bar. +

+
{checkboxes.map(this.makeCheckbox)}
+ +
+ + +
+
+ ); + } + + private makeCheckbox = (checkbox: Checkbox) => { + return ( + + ); + }; + + private handleCheckboxChange = (ev: React.FormEvent) => { + this.setState({ + [ev.currentTarget.name as any]: !!ev.currentTarget.checked + }); + }; +} diff --git a/common/components/WalletDecrypt/components/PrivateKey.tsx b/common/components/WalletDecrypt/components/PrivateKey.tsx index abead3e4..9f1ef09d 100644 --- a/common/components/WalletDecrypt/components/PrivateKey.tsx +++ b/common/components/WalletDecrypt/components/PrivateKey.tsx @@ -93,6 +93,8 @@ export class PrivateKeyDecrypt extends Component { }; public onPasswordChange = (e: React.FormEvent) => { + // 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); diff --git a/common/components/WalletDecrypt/components/index.tsx b/common/components/WalletDecrypt/components/index.tsx index 3886676a..7e683111 100644 --- a/common/components/WalletDecrypt/components/index.tsx +++ b/common/components/WalletDecrypt/components/index.tsx @@ -1,5 +1,6 @@ export * from './DeterministicWalletsModal'; export * from './DigitalBitbox'; +export * from './InsecureWalletWarning'; export * from './Keystore'; export * from './LedgerNano'; export * from './Mnemonic'; diff --git a/common/components/ui/UnlockHeader.tsx b/common/components/ui/UnlockHeader.tsx index 21762d2c..ddc700de 100644 --- a/common/components/ui/UnlockHeader.tsx +++ b/common/components/ui/UnlockHeader.tsx @@ -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 { } 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 { )} -