Skip to content
Permalink
Browse files

feat(onboarding): ability to import encrypted seed

If the import fails with an error, check the error message to determine
the reason for the failure. If the reason was that an invalid passphrase
was provided, present the user with a password field where they can
enter the cipher seed passphrase.

This does not change the standard import flow. It adds a new step to the
end of the process only if we detect that the import failed due to a
missing passphrase.

Fix #2071
  • Loading branch information...
mrfelton committed Apr 23, 2019
1 parent 9d5da9f commit 8ed8241be0cf87f837b48b667dfab0833e22a37f
@@ -26,6 +26,7 @@ class Onboarding extends React.Component {
// STATE
autopilot: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming
clearStartLndError: PropTypes.func.isRequired,
clearWalletRecoveryError: PropTypes.func.isRequired,
connectionCert: PropTypes.string,
connectionHost: PropTypes.string,
connectionMacaroon: PropTypes.string,
@@ -35,10 +36,12 @@ class Onboarding extends React.Component {
fetchSeed: PropTypes.func.isRequired,
isFetchingSeed: PropTypes.bool,
isLightningGrpcActive: PropTypes.bool,
isRecoveringWallet: PropTypes.bool,
isWalletUnlockerGrpcActive: PropTypes.bool,
lndConnect: PropTypes.string,
name: PropTypes.string,
network: PropTypes.string,
passphrase: PropTypes.string,
recoverOldWallet: PropTypes.func.isRequired,
resetOnboarding: PropTypes.func.isRequired,

@@ -53,6 +56,7 @@ class Onboarding extends React.Component {
setLndconnect: PropTypes.func.isRequired,
setName: PropTypes.func.isRequired,
setNetwork: PropTypes.func.isRequired,
setPassphrase: PropTypes.func.isRequired,
setPassword: PropTypes.func.isRequired,
setSeed: PropTypes.func.isRequired,
setUnlockWalletError: PropTypes.func.isRequired,
@@ -66,6 +70,7 @@ class Onboarding extends React.Component {
validateCert: PropTypes.func.isRequired,
validateHost: PropTypes.func.isRequired,
validateMacaroon: PropTypes.func.isRequired,
walletRecoveryError: PropTypes.string,
}

componentWillUnmount() {
@@ -106,15 +111,19 @@ class Onboarding extends React.Component {
connectionString,
isLightningGrpcActive,
isWalletUnlockerGrpcActive,
passphrase,
seed,
startLndHostError,
startLndCertError,
startLndMacaroonError,
unlockWalletError,
isFetchingSeed,
isRecoveringWallet,
walletRecoveryError,
lndConnect,

// DISPATCH
clearWalletRecoveryError,
setAutopilot,
setConnectionType,
setConnectionHost,
@@ -125,6 +134,7 @@ class Onboarding extends React.Component {
setNetwork,
setUnlockWalletError,
setPassword,
setPassphrase,
setSeed,
clearStartLndError,
setLndconnect,
@@ -181,7 +191,18 @@ class Onboarding extends React.Component {
{...{ network, setNetwork, setAutopilot }}
/>,
<Wizard.Step key="Autopilot" component={Autopilot} {...{ autopilot, setAutopilot }} />,
<Wizard.Step key="WalletRecover" component={WalletRecover} {...{ recoverOldWallet }} />,
<Wizard.Step
key="WalletRecover"
component={WalletRecover}
{...{
clearWalletRecoveryError,
isRecoveringWallet,
passphrase,
recoverOldWallet,
setPassphrase,
walletRecoveryError,
}}
/>,
]
break

@@ -1,12 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage } from 'react-intl'
import { Form, Spinner, Text } from 'components/UI'
import { FormattedMessage, intlShape, injectIntl } from 'react-intl'
import { Bar, Form, Header, Message, PasswordInput, Spinner, Text } from 'components/UI'
import messages from './messages'

const isInvalidPassphrase = error => error === 'invalid passphrase'

class WalletRecover extends React.Component {
static propTypes = {
clearWalletRecoveryError: PropTypes.func.isRequired,
intl: intlShape.isRequired,
isRecoveringWallet: PropTypes.bool,
passphrase: PropTypes.string,
recoverOldWallet: PropTypes.func.isRequired,
setPassphrase: PropTypes.func.isRequired,
walletRecoveryError: PropTypes.string,
wizardApi: PropTypes.object,
wizardState: PropTypes.object,
}
@@ -20,18 +28,67 @@ class WalletRecover extends React.Component {
this.formApi.submitForm()
}

handleSubmit = () => {
const { recoverOldWallet } = this.props
componentDidUpdate(prevProps) {
const { isRecoveringWallet, walletRecoveryError } = this.props

// Handle success case.
if (!walletRecoveryError && !isRecoveringWallet && prevProps.isRecoveringWallet) {
this.handleSuccess()
}

// Handle failure case.
if (walletRecoveryError && !isRecoveringWallet && prevProps.isRecoveringWallet) {
this.handleError()
}
}

componentWillUnmount() {
const { clearWalletRecoveryError } = this.props
clearWalletRecoveryError()
}

handleSubmit = values => {
const { recoverOldWallet, setPassphrase } = this.props
const { passphrase } = values
if (passphrase) {
setPassphrase(passphrase)
}
recoverOldWallet()
}

handleSuccess = () => {
const { wizardApi } = this.props
wizardApi.onSubmit()
}

handleError = () => {
const { passphrase, wizardApi, walletRecoveryError } = this.props
wizardApi.onSubmitFailure()

// If the user entered an incorrect passphrase, set the error on the passphrase form element.
if (passphrase && walletRecoveryError && isInvalidPassphrase(walletRecoveryError)) {
this.formApi.setError('passphrase', walletRecoveryError)
}
}

setFormApi = formApi => {
this.formApi = formApi
}

render() {
const { wizardApi, wizardState, recoverOldWallet, ...rest } = this.props
const { getApi, onChange, onSubmit, onSubmitFailure } = wizardApi
const {
wizardApi,
wizardState,
passphrase,
recoverOldWallet,
setPassphrase,
clearWalletRecoveryError,
isRecoveringWallet,
walletRecoveryError,
intl,
...rest
} = this.props
const { getApi, onChange, onSubmitFailure } = wizardApi
const { currentItem } = wizardState
return (
<Form
@@ -43,21 +100,55 @@ class WalletRecover extends React.Component {
}
}}
onChange={onChange && (formState => onChange(formState, currentItem))}
onSubmit={values => {
this.handleSubmit(values)
if (onSubmit) {
onSubmit(values)
}
}}
onSubmit={this.handleSubmit}
onSubmitFailure={onSubmitFailure}
>
<Text textAlign="center">
<Spinner />
<FormattedMessage {...messages.importing_wallet} />
</Text>
{({ formState }) => {
const shouldValidateInline = formState.submits > 0
return (
<>
<Header
align="left"
subtitle={<FormattedMessage {...messages.importing_wallet_subtitle} />}
title={<FormattedMessage {...messages.importing_wallet_title} />}
/>

<Bar my={4} />

{isRecoveringWallet && (
<Text textAlign="center">
<Spinner />
<FormattedMessage {...messages.importing_wallet} />
</Text>
)}

{!isRecoveringWallet && walletRecoveryError && (
<>
{isInvalidPassphrase(walletRecoveryError) ? (
<PasswordInput
autoComplete="current-password"
description={intl.formatMessage({ ...messages.passphrase_description })}
field="passphrase"
isRequired
label={<FormattedMessage {...messages.passphrase_label} />}
minLength={1}
name="passphrase"
placeholder={intl.formatMessage({ ...messages.passphrase_placeholder })}
validateOnBlur={shouldValidateInline}
validateOnChange={shouldValidateInline}
willAutoFocus
/>
) : (
<Message variant="error">{walletRecoveryError}</Message>
)}
</>
)}
</>
)
}}
</Form>
)
}
}

export default WalletRecover
export default injectIntl(WalletRecover)
@@ -61,6 +61,10 @@ export default defineMessages({
password_placeholder: 'Enter your password',
password_description:
'You would have set your password when first creating your wallet. This is separate from your 24 word seed.',
passphrase_label: 'Passphrase',
passphrase_placeholder: 'Enter your cipher seed passphrase',
passphrase_description:
'Your seed is encrypted. Please enter the passphrase that you set when creating the wallet.',
retype_seed_description:
"Your seed is important! If you lose your seed you'll have no way to recover your wallet. To make sure that you have properly saved your seed, please retype words {word1}, {word2} & {word3}",
retype_seed_title: 'Retype your seed',
@@ -90,4 +94,7 @@ export default defineMessages({
word_placeholder: 'word',
generating_seed: 'Generating Seed...',
importing_wallet: 'Importing wallet...',
importing_wallet_title: 'Importing wallet',
importing_wallet_subtitle:
'Please wait whilst we start the wallet import process. This should only take a moment.',
})
@@ -16,6 +16,7 @@ import {
setName,
setNetwork,
setPassword,
setPassphrase,
setSeed,
setLndconnect,
validateHost,
@@ -28,6 +29,7 @@ import {
startLnd,
stopLnd,
fetchSeed,
clearWalletRecoveryError,
createNewWallet,
recoverOldWallet,
clearStartLndError,
@@ -46,8 +48,11 @@ const mapStateToProps = state => ({
connectionString: state.onboarding.connectionString,
lndConnect: state.onboarding.lndConnect,
network: state.onboarding.network,
isRecoveringWallet: state.lnd.isRecoveringWallet,
walletRecoveryError: state.lnd.walletRecoveryError,
isLightningGrpcActive: state.lnd.isLightningGrpcActive,
isWalletUnlockerGrpcActive: state.lnd.isWalletUnlockerGrpcActive,
passphrase: state.onboarding.passphrase,
startLndHostError: lndSelectors.startLndHostError(state),
startLndCertError: lndSelectors.startLndCertError(state),
startLndMacaroonError: lndSelectors.startLndMacaroonError(state),
@@ -57,6 +62,7 @@ const mapStateToProps = state => ({
})

const mapDispatchToProps = {
clearWalletRecoveryError,
setAlias,
setAutopilot,
setConnectionType,
@@ -67,6 +73,7 @@ const mapDispatchToProps = {
setName,
setNetwork,
setPassword,
setPassphrase,
setSeed,
clearStartLndError,
setUnlockWalletError,

0 comments on commit 8ed8241

Please sign in to comment.
You can’t perform that action at this time.