From c446567ddaeb0e79d87046b31ed4ce7a00f56ee9 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Mon, 25 Jun 2018 14:42:33 -0500 Subject: [PATCH] feat(retype-seed): select 3 random seed indexes for the user to enter --- app/components/Onboarding/Onboarding.js | 9 ++- app/components/Onboarding/ReEnterSeed.js | 67 ++++++++++++-------- app/components/Onboarding/ReEnterSeed.scss | 20 +++--- app/components/Onboarding/RecoverForm.js | 10 +-- app/containers/Root.js | 18 ++++-- app/reducers/onboarding.js | 74 ++++++++++++++++++---- 6 files changed, 134 insertions(+), 64 deletions(-) diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index f686ae3c..811e79e0 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -163,7 +163,7 @@ const Onboarding = ({ description="Enter your cipherseed passphrase (or just submit if you don't have one)" back={() => changeStep(5)} next={() => { - const recoverySeed = recoverFormProps.seedInput.map(input => input.word) + const recoverySeed = recoverFormProps.recoverSeedInput.map(input => input.word) submitNewWallet(createWalletPassword, recoverySeed, aezeedPassword) }} @@ -185,8 +185,11 @@ const Onboarding = ({ case 7: return ( changeStep(6)} next={() => { // don't allow them to move on if they havent re-entered the seed correctly diff --git a/app/components/Onboarding/ReEnterSeed.js b/app/components/Onboarding/ReEnterSeed.js index 8f1a2fc5..3a6b7298 100644 --- a/app/components/Onboarding/ReEnterSeed.js +++ b/app/components/Onboarding/ReEnterSeed.js @@ -2,36 +2,49 @@ import React from 'react' import PropTypes from 'prop-types' import styles from './ReEnterSeed.scss' -const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => ( -
-
    - {seed.map((word, index) => ( -
  • -
    - -
    -
    - updateSeedInput({ word: event.target.value, index })} - className={`${styles.word} ${ - seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid - }`} - /> -
    -
  • - ))} -
-
-) +class ReEnterSeed extends React.Component { + componentWillMount() { + this.props.setReEnterSeedIndexes() + } + + render() { + const { seed, reEnterSeedInput, updateReEnterSeedInput, seedIndexesArr } = this.props + + return ( +
+
    + {seedIndexesArr.map(index => ( +
  • +
    + +
    +
    + updateReEnterSeedInput({ word: event.target.value, index })} + className={`${styles.word} ${ + reEnterSeedInput[index] && seed[index - 1] === reEnterSeedInput[index] + ? styles.valid + : styles.invalid + }`} + /> +
    +
  • + ))} +
+
+ ) + } +} ReEnterSeed.propTypes = { seed: PropTypes.array.isRequired, - seedInput: PropTypes.array.isRequired, - updateSeedInput: PropTypes.func.isRequired + reEnterSeedInput: PropTypes.object.isRequired, + updateReEnterSeedInput: PropTypes.func.isRequired, + setReEnterSeedIndexes: PropTypes.func.isRequired, + seedIndexesArr: PropTypes.array.isRequired } export default ReEnterSeed diff --git a/app/components/Onboarding/ReEnterSeed.scss b/app/components/Onboarding/ReEnterSeed.scss index 4c794ac5..d7e19505 100644 --- a/app/components/Onboarding/ReEnterSeed.scss +++ b/app/components/Onboarding/ReEnterSeed.scss @@ -1,14 +1,19 @@ @import '../../variables.scss'; .seedContainer { - position: relative; - display: inline-block; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; font-size: 12px; + margin-top: 10%; li { display: inline-block; - margin: 5px 0; + margin: 10px; width: 25%; + border: 0.2px solid #ccc; + padding: 5px 10px; section { display: inline-block; @@ -16,30 +21,25 @@ color: $white; &:nth-child(1) { - width: 15%; text-align: center; opacity: 0.5; } &:nth-child(2) { - width: calc(85% - 10px); margin: 0 5px; } } } } - - .word { margin: 0 3px; - background-color: #1c1e26; + background-color: inherit; outline: 0; border: none; padding: 5px 10px; color: $white; - font-family: courier; - font-family: 'Courier'; + font-family: 'Courier', courier, sans-serif; &.valid { color: $green; diff --git a/app/components/Onboarding/RecoverForm.js b/app/components/Onboarding/RecoverForm.js index 4e92b3f3..ca926331 100644 --- a/app/components/Onboarding/RecoverForm.js +++ b/app/components/Onboarding/RecoverForm.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import styles from './RecoverForm.scss' -const RecoverForm = ({ seedInput, updateSeedInput }) => ( +const RecoverForm = ({ recoverSeedInput, updateRecoverSeedInput }) => (
    {Array(24) @@ -17,8 +17,8 @@ const RecoverForm = ({ seedInput, updateSeedInput }) => ( type="text" id={index} placeholder="word" - value={seedInput[index] ? seedInput[index].word : ''} - onChange={event => updateSeedInput({ word: event.target.value, index })} + value={recoverSeedInput[index] ? recoverSeedInput[index].word : ''} + onChange={event => updateRecoverSeedInput({ word: event.target.value, index })} className={styles.word} /> @@ -29,8 +29,8 @@ const RecoverForm = ({ seedInput, updateSeedInput }) => ( ) RecoverForm.propTypes = { - seedInput: PropTypes.array.isRequired, - updateSeedInput: PropTypes.func.isRequired + recoverSeedInput: PropTypes.array.isRequired, + updateRecoverSeedInput: PropTypes.func.isRequired } export default RecoverForm diff --git a/app/containers/Root.js b/app/containers/Root.js index aecce408..916d20f9 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -27,7 +27,9 @@ import { unlockWallet, setSignupCreate, setSignupImport, - updateSeedInput + updateReEnterSeedInput, + updateRecoverSeedInput, + setReEnterSeedIndexes } from '../reducers/onboarding' import { fetchBlockHeight, lndSelectors } from '../reducers/lnd' import { newAddress } from '../reducers/address' @@ -52,8 +54,10 @@ const mapDispatchToProps = { unlockWallet, setSignupCreate, setSignupImport, - updateSeedInput, newAddress, + updateReEnterSeedInput, + updateRecoverSeedInput, + setReEnterSeedIndexes, fetchBlockHeight } @@ -151,15 +155,17 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { } const recoverFormProps = { - seedInput: stateProps.onboarding.seedInput, - updateSeedInput: dispatchProps.updateSeedInput + recoverSeedInput: stateProps.onboarding.recoverSeedInput, + updateRecoverSeedInput: dispatchProps.updateRecoverSeedInput } const reEnterSeedProps = { seed: stateProps.onboarding.seed, - seedInput: stateProps.onboarding.seedInput, + reEnterSeedInput: stateProps.onboarding.reEnterSeedInput, + seedIndexesArr: stateProps.onboarding.seedIndexesArr, reEnterSeedChecker: stateProps.reEnterSeedChecker, - updateSeedInput: dispatchProps.updateSeedInput + updateReEnterSeedInput: dispatchProps.updateReEnterSeedInput, + setReEnterSeedIndexes: dispatchProps.setReEnterSeedIndexes } const onboardingProps = { diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index 56c69f21..10a7f021 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -19,7 +19,8 @@ export const UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION = 'UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION' export const UPDATE_AEZEED_PASSWORD = 'UPDATE_AEZEED_PASSWORD' export const UPDATE_AEZEED_PASSWORD_CONFIRMATION = 'UPDATE_AEZEED_PASSWORD_CONFIRMATION' -export const UPDATE_SEED_INPUT = 'UPDATE_SEED_INPUT' +export const UPDATE_RE_ENTER_SEED_INPUT = 'UPDATE_RE_ENTER_SEED_INPUT' +export const UPDATE_RECOVER_SEED_INPUT = 'UPDATE_RECOVER_SEED_INPUT' export const CHANGE_STEP = 'CHANGE_STEP' @@ -28,6 +29,7 @@ export const SET_AUTOPILOT = 'SET_AUTOPILOT' export const FETCH_SEED = 'FETCH_SEED' export const SET_SEED = 'SET_SEED' export const SET_HAS_SEED = 'SET_HAS_SEED' +export const SET_RE_ENTER_SEED_INDEXES = 'SET_RE_ENTER_SEED_INDEXES' export const ONBOARDING_STARTED = 'ONBOARDING_STARTED' export const ONBOARDING_FINISHED = 'ONBOARDING_FINISHED' @@ -114,9 +116,16 @@ export function updateAezeedPasswordConfirmation(aezeedPasswordConfirmation) { } } -export function updateSeedInput(inputSeedObj) { +export function updateReEnterSeedInput(inputSeedObj) { return { - type: UPDATE_SEED_INPUT, + type: UPDATE_RE_ENTER_SEED_INPUT, + inputSeedObj + } +} + +export function updateRecoverSeedInput(inputSeedObj) { + return { + type: UPDATE_RECOVER_SEED_INPUT, inputSeedObj } } @@ -156,6 +165,28 @@ export function startLnd(options) { } } +export function setReEnterSeedIndexes() { + // we only want the user to have to verify 3 random indexes from the seed they were just given + const INDEX_AMOUNT = 3 + + const seedIndexesArr = [] + while (seedIndexesArr.length < INDEX_AMOUNT) { + // add 1 because we dont want this to be 0 index based + const ranNum = Math.floor(Math.random() * 24) + 1 + + if (seedIndexesArr.indexOf(ranNum) > -1) { + continue + } + + seedIndexesArr[seedIndexesArr.length] = ranNum + } + + return { + type: SET_RE_ENTER_SEED_INDEXES, + seedIndexesArr + } +} + export const submitNewWallet = ( wallet_password, cipher_seed_mnemonic, @@ -238,15 +269,22 @@ const ACTION_HANDLERS = { ...state, aezeedPasswordConfirmation }), - [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => ({ + [UPDATE_RE_ENTER_SEED_INPUT]: (state, { inputSeedObj }) => ({ + ...state, + reEnterSeedInput: { ...state.reEnterSeedInput, [inputSeedObj.index]: inputSeedObj.word } + }), + [UPDATE_RECOVER_SEED_INPUT]: (state, { inputSeedObj }) => ({ ...state, - seedInput: Object.assign([], state.seedInput, { [inputSeedObj.index]: inputSeedObj }) + recoverSeedInput: Object.assign([], state.recoverSeedInput, { + [inputSeedObj.index]: inputSeedObj + }) }), [SET_AUTOPILOT]: (state, { autopilot }) => ({ ...state, autopilot }), [SET_HAS_SEED]: (state, { hasSeed }) => ({ ...state, hasSeed }), [SET_SEED]: (state, { seed }) => ({ ...state, seed, fetchingSeed: false }), + [SET_RE_ENTER_SEED_INDEXES]: (state, { seedIndexesArr }) => ({ ...state, seedIndexesArr }), [CHANGE_STEP]: (state, { step }) => ({ ...state, step }), @@ -285,7 +323,8 @@ const aezeedPasswordSelector = state => state.onboarding.aezeedPassword const aezeedPasswordConfirmationSelector = state => state.onboarding.aezeedPasswordConfirmation const seedSelector = state => state.onboarding.seed -const seedInputSelector = state => state.onboarding.seedInput +const seedIndexesArrSelector = state => state.onboarding.seedIndexesArr +const reEnterSeedInputSelector = state => state.onboarding.reEnterSeedInput onboardingSelectors.passwordIsValid = createSelector( passwordSelector, @@ -306,9 +345,13 @@ onboardingSelectors.showAezeedPasswordConfirmationError = createSelector( onboardingSelectors.reEnterSeedChecker = createSelector( seedSelector, - seedInputSelector, - (seed, seedInput) => - seed.length === seedInput.length && seed.every((word, i) => word === seedInput[i].word) + seedIndexesArrSelector, + reEnterSeedInputSelector, + (seed, seedIndexArr, reEnterSeedInput) => + seedIndexArr.length === Object.keys(reEnterSeedInput).length && + seedIndexArr.every( + index => reEnterSeedInput[index] && reEnterSeedInput[index] === seed[index - 1] + ) ) export { onboardingSelectors } @@ -346,10 +389,15 @@ const initialState = { message: '' }, - // array of inputs for when the user re-enters their seed - // object has a word attr and a index attr: - // { word: 'foo', index: 0 } - seedInput: [], + seedIndexesArr: [], + // object of inputs for when the user re-enters their seed + // { + // index: word, + // index: word, + // index: word + // } + reEnterSeedInput: {}, + recoverSeedInput: [], // step where the user decides whether they want a newly created seed or to import an existing one signupForm: { create: false,