mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-25 11:28:51 +01:00
7929a92c66
* Clear the clipboard after the seed phrase is pasted On the "Import" page of the import onboarding flow, we now clear the clipboard after the secret recovery phrase is pasted. This ensures that the SRP isn't accidentally pasted somewhere else by the user, which can be an easy and disastrous mistake to make. * Clear clipboard during new onboarding flow as well
355 lines
9.8 KiB
JavaScript
355 lines
9.8 KiB
JavaScript
import { ethers } from 'ethers';
|
|
import React, { PureComponent } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import TextField from '../../../../components/ui/text-field';
|
|
import Button from '../../../../components/ui/button';
|
|
import {
|
|
INITIALIZE_SELECT_ACTION_ROUTE,
|
|
INITIALIZE_END_OF_FLOW_ROUTE,
|
|
} from '../../../../helpers/constants/routes';
|
|
import { clearClipboard } from '../../../../helpers/utils/util';
|
|
|
|
const { isValidMnemonic } = ethers.utils;
|
|
|
|
export default class ImportWithSeedPhrase extends PureComponent {
|
|
static contextTypes = {
|
|
t: PropTypes.func,
|
|
metricsEvent: PropTypes.func,
|
|
};
|
|
|
|
static propTypes = {
|
|
history: PropTypes.object,
|
|
onSubmit: PropTypes.func.isRequired,
|
|
setSeedPhraseBackedUp: PropTypes.func,
|
|
initializeThreeBox: PropTypes.func,
|
|
};
|
|
|
|
state = {
|
|
seedPhrase: '',
|
|
showSeedPhrase: false,
|
|
password: '',
|
|
confirmPassword: '',
|
|
seedPhraseError: '',
|
|
passwordError: '',
|
|
confirmPasswordError: '',
|
|
termsChecked: false,
|
|
};
|
|
|
|
parseSeedPhrase = (seedPhrase) =>
|
|
(seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '';
|
|
|
|
UNSAFE_componentWillMount() {
|
|
this._onBeforeUnload = () =>
|
|
this.context.metricsEvent({
|
|
eventOpts: {
|
|
category: 'Onboarding',
|
|
action: 'Import Seed Phrase',
|
|
name: 'Close window on import screen',
|
|
},
|
|
customVariables: {
|
|
errorLabel: 'Seed Phrase Error',
|
|
errorMessage: this.state.seedPhraseError,
|
|
},
|
|
});
|
|
window.addEventListener('beforeunload', this._onBeforeUnload);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
window.removeEventListener('beforeunload', this._onBeforeUnload);
|
|
}
|
|
|
|
handleSeedPhraseChange(seedPhrase) {
|
|
let seedPhraseError = '';
|
|
|
|
if (seedPhrase) {
|
|
const parsedSeedPhrase = this.parseSeedPhrase(seedPhrase);
|
|
const wordCount = parsedSeedPhrase.split(/\s/u).length;
|
|
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
|
|
seedPhraseError = this.context.t('seedPhraseReq');
|
|
} else if (!isValidMnemonic(parsedSeedPhrase)) {
|
|
seedPhraseError = this.context.t('invalidSeedPhrase');
|
|
}
|
|
}
|
|
|
|
this.setState({ seedPhrase, seedPhraseError });
|
|
}
|
|
|
|
handlePasswordChange(password) {
|
|
const { t } = this.context;
|
|
|
|
this.setState((state) => {
|
|
const { confirmPassword } = state;
|
|
let confirmPasswordError = '';
|
|
let passwordError = '';
|
|
|
|
if (password && password.length < 8) {
|
|
passwordError = t('passwordNotLongEnough');
|
|
}
|
|
|
|
if (confirmPassword && password !== confirmPassword) {
|
|
confirmPasswordError = t('passwordsDontMatch');
|
|
}
|
|
|
|
return {
|
|
password,
|
|
passwordError,
|
|
confirmPasswordError,
|
|
};
|
|
});
|
|
}
|
|
|
|
handleConfirmPasswordChange(confirmPassword) {
|
|
const { t } = this.context;
|
|
|
|
this.setState((state) => {
|
|
const { password } = state;
|
|
let confirmPasswordError = '';
|
|
|
|
if (password !== confirmPassword) {
|
|
confirmPasswordError = t('passwordsDontMatch');
|
|
}
|
|
|
|
return {
|
|
confirmPassword,
|
|
confirmPasswordError,
|
|
};
|
|
});
|
|
}
|
|
|
|
handleImport = async (event) => {
|
|
event.preventDefault();
|
|
|
|
if (!this.isValid()) {
|
|
return;
|
|
}
|
|
|
|
const { password, seedPhrase } = this.state;
|
|
const {
|
|
history,
|
|
onSubmit,
|
|
setSeedPhraseBackedUp,
|
|
initializeThreeBox,
|
|
} = this.props;
|
|
|
|
try {
|
|
await onSubmit(password, this.parseSeedPhrase(seedPhrase));
|
|
this.context.metricsEvent({
|
|
eventOpts: {
|
|
category: 'Onboarding',
|
|
action: 'Import Seed Phrase',
|
|
name: 'Import Complete',
|
|
},
|
|
});
|
|
|
|
setSeedPhraseBackedUp(true).then(async () => {
|
|
initializeThreeBox();
|
|
history.replace(INITIALIZE_END_OF_FLOW_ROUTE);
|
|
});
|
|
} catch (error) {
|
|
this.setState({ seedPhraseError: error.message });
|
|
}
|
|
};
|
|
|
|
isValid() {
|
|
const {
|
|
seedPhrase,
|
|
password,
|
|
confirmPassword,
|
|
passwordError,
|
|
confirmPasswordError,
|
|
seedPhraseError,
|
|
} = this.state;
|
|
|
|
if (
|
|
!password ||
|
|
!confirmPassword ||
|
|
!seedPhrase ||
|
|
password !== confirmPassword
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
return false;
|
|
}
|
|
|
|
return !passwordError && !confirmPasswordError && !seedPhraseError;
|
|
}
|
|
|
|
onTermsKeyPress = ({ key }) => {
|
|
if (key === ' ' || key === 'Enter') {
|
|
this.toggleTermsCheck();
|
|
}
|
|
};
|
|
|
|
toggleTermsCheck = () => {
|
|
this.context.metricsEvent({
|
|
eventOpts: {
|
|
category: 'Onboarding',
|
|
action: 'Import Seed Phrase',
|
|
name: 'Check ToS',
|
|
},
|
|
});
|
|
this.setState((prevState) => ({
|
|
termsChecked: !prevState.termsChecked,
|
|
}));
|
|
};
|
|
|
|
toggleShowSeedPhrase = () => {
|
|
this.setState(({ showSeedPhrase }) => ({
|
|
showSeedPhrase: !showSeedPhrase,
|
|
}));
|
|
};
|
|
|
|
render() {
|
|
const { t } = this.context;
|
|
const {
|
|
seedPhraseError,
|
|
showSeedPhrase,
|
|
passwordError,
|
|
confirmPasswordError,
|
|
termsChecked,
|
|
} = this.state;
|
|
|
|
return (
|
|
<form className="first-time-flow__form" onSubmit={this.handleImport}>
|
|
<div className="first-time-flow__create-back">
|
|
<a
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
this.context.metricsEvent({
|
|
eventOpts: {
|
|
category: 'Onboarding',
|
|
action: 'Import Seed Phrase',
|
|
name: 'Go Back from Onboarding Import',
|
|
},
|
|
customVariables: {
|
|
errorLabel: 'Seed Phrase Error',
|
|
errorMessage: seedPhraseError,
|
|
},
|
|
});
|
|
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE);
|
|
}}
|
|
href="#"
|
|
>
|
|
{`< ${t('back')}`}
|
|
</a>
|
|
</div>
|
|
<div className="first-time-flow__header">
|
|
{t('importAccountSeedPhrase')}
|
|
</div>
|
|
<div className="first-time-flow__text-block">{t('secretPhrase')}</div>
|
|
<div className="first-time-flow__textarea-wrapper">
|
|
<label>{t('secretRecoveryPhrase')}</label>
|
|
{showSeedPhrase ? (
|
|
<textarea
|
|
className="first-time-flow__textarea"
|
|
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
|
|
onPaste={clearClipboard}
|
|
value={this.state.seedPhrase}
|
|
placeholder={t('seedPhrasePlaceholder')}
|
|
autoComplete="off"
|
|
/>
|
|
) : (
|
|
<TextField
|
|
className="first-time-flow__textarea first-time-flow__seedphrase"
|
|
type="password"
|
|
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
|
|
value={this.state.seedPhrase}
|
|
placeholder={t('seedPhrasePlaceholderPaste')}
|
|
autoComplete="off"
|
|
onPaste={clearClipboard}
|
|
/>
|
|
)}
|
|
{seedPhraseError ? (
|
|
<span className="error">{seedPhraseError}</span>
|
|
) : null}
|
|
<div
|
|
className="first-time-flow__checkbox-container"
|
|
onClick={this.toggleShowSeedPhrase}
|
|
>
|
|
<div
|
|
className="first-time-flow__checkbox"
|
|
tabIndex="0"
|
|
role="checkbox"
|
|
onKeyPress={this.toggleShowSeedPhrase}
|
|
aria-checked={showSeedPhrase}
|
|
aria-labelledby="ftf-chk1-label"
|
|
>
|
|
{showSeedPhrase ? <i className="fa fa-check fa-2x" /> : null}
|
|
</div>
|
|
<span
|
|
id="ftf-chk1-label"
|
|
className="first-time-flow__checkbox-label"
|
|
>
|
|
{t('showSeedPhrase')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<TextField
|
|
id="password"
|
|
label={t('newPassword')}
|
|
type="password"
|
|
className="first-time-flow__input"
|
|
value={this.state.password}
|
|
onChange={(event) => this.handlePasswordChange(event.target.value)}
|
|
error={passwordError}
|
|
autoComplete="new-password"
|
|
margin="normal"
|
|
largeLabel
|
|
/>
|
|
<TextField
|
|
id="confirm-password"
|
|
label={t('confirmPassword')}
|
|
type="password"
|
|
className="first-time-flow__input"
|
|
value={this.state.confirmPassword}
|
|
onChange={(event) =>
|
|
this.handleConfirmPasswordChange(event.target.value)
|
|
}
|
|
error={confirmPasswordError}
|
|
autoComplete="new-password"
|
|
margin="normal"
|
|
largeLabel
|
|
/>
|
|
<div
|
|
className="first-time-flow__checkbox-container"
|
|
onClick={this.toggleTermsCheck}
|
|
>
|
|
<div
|
|
className="first-time-flow__checkbox first-time-flow__terms"
|
|
tabIndex="0"
|
|
role="checkbox"
|
|
onKeyPress={this.onTermsKeyPress}
|
|
aria-checked={termsChecked}
|
|
aria-labelledby="ftf-chk1-label"
|
|
>
|
|
{termsChecked ? <i className="fa fa-check fa-2x" /> : null}
|
|
</div>
|
|
<span id="ftf-chk1-label" className="first-time-flow__checkbox-label">
|
|
{t('acceptTermsOfUse', [
|
|
<a
|
|
onClick={(e) => e.stopPropagation()}
|
|
key="first-time-flow__link-text"
|
|
href="https://metamask.io/terms.html"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<span className="first-time-flow__link-text">{t('terms')}</span>
|
|
</a>,
|
|
])}
|
|
</span>
|
|
</div>
|
|
<Button
|
|
type="primary"
|
|
submit
|
|
className="first-time-flow__button"
|
|
disabled={!this.isValid() || !termsChecked}
|
|
>
|
|
{t('import')}
|
|
</Button>
|
|
</form>
|
|
);
|
|
}
|
|
}
|