From 06b76d9227f74c5a8bbf38698bddf42df6c0f8a1 Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Wed, 4 Dec 2019 15:16:32 -0300 Subject: [PATCH 01/24] Corrections by code review --- examples/wallet/app/actions/account.js | 4 +++- examples/wallet/app/actions/router.js | 12 +----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/wallet/app/actions/account.js b/examples/wallet/app/actions/account.js index d96577a..06403d3 100644 --- a/examples/wallet/app/actions/account.js +++ b/examples/wallet/app/actions/account.js @@ -15,7 +15,9 @@ import type { Delegation, Identifier, TransactionHash, - Transaction + Transaction, + Balance, + Counter } from '../models'; import { updateNodeSettings } from './nodeSettings'; import { diff --git a/examples/wallet/app/actions/router.js b/examples/wallet/app/actions/router.js index 669c12b..8381c5d 100644 --- a/examples/wallet/app/actions/router.js +++ b/examples/wallet/app/actions/router.js @@ -16,17 +16,7 @@ export const redirectToFirstAppPage = () => { return (dispatch: Dispatch, getState: GetState) => { const accountKeys = readAccountKeysFromLocalStorage(); if (accountKeys) { - dispatch({ - type: SET_KEYS, - ...accountKeys - }); - return dispatch(updateAccountTransactionsAndState()).catch(error => { - if (error.message === ACCOUNT_STATE_ERROR) { - console.error('There was an error retrieving account status'); - } - - return dispatch(push(routes.WALLET)); - }); + return dispatch(setAccountFromPrivateKey(accountKeys.privateKey)); } const { account: { address } From a547967aaacced1c55f8197f56040407e86d67e8 Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Wed, 11 Dec 2019 13:16:19 -0300 Subject: [PATCH 02/24] decrypt utils are added to save encrypted spending password and data --- examples/wallet/app/Routes.js | 5 ++ examples/wallet/app/actions/router.js | 13 ++-- examples/wallet/app/constants/routes.json | 3 +- .../app/containers/CreateSpendingPassword.js | 14 ++++ .../app/pages/CreateSpendingPassword.js | 70 +++++++++++++++++++ examples/wallet/app/utils/decrypt.js | 43 ++++++++++++ examples/wallet/app/utils/storage.js | 24 +++++++ 7 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 examples/wallet/app/containers/CreateSpendingPassword.js create mode 100644 examples/wallet/app/pages/CreateSpendingPassword.js create mode 100644 examples/wallet/app/utils/decrypt.js diff --git a/examples/wallet/app/Routes.js b/examples/wallet/app/Routes.js index 5764ab0..68c080b 100644 --- a/examples/wallet/app/Routes.js +++ b/examples/wallet/app/Routes.js @@ -10,6 +10,7 @@ import ChooseRestoreOrImport from './containers/ChooseRestoreOrImport'; import Delegate from './pages/Delegate'; import Index from './containers/Index'; import InputKeys from './containers/InputKeys'; +import CreateSpendingPassword from './containers/CreateSpendingPassword'; export default () => ( @@ -23,6 +24,10 @@ export default () => ( path={routes.CHOOSE_RESTORE_OR_IMPORT} component={ChooseRestoreOrImport} /> + diff --git a/examples/wallet/app/actions/router.js b/examples/wallet/app/actions/router.js index 8381c5d..0961717 100644 --- a/examples/wallet/app/actions/router.js +++ b/examples/wallet/app/actions/router.js @@ -2,18 +2,21 @@ import { push } from 'connected-react-router'; import type { Dispatch, GetState } from '../reducers/types'; import type { Address } from '../models'; -import routes from '../constants/routes.json'; +import routes from '../constants/routes'; +import { setAccountFromPrivateKey } from './account'; + import { - updateAccountTransactionsAndState, - ACCOUNT_STATE_ERROR -} from './account'; -import { readAccountKeysFromLocalStorage } from '../utils/storage'; + readAccountKeysFromLocalStorage, + isSpedingPasswordCreated +} from '../utils/storage'; export const SET_KEYS = 'SET_KEYS'; // eslint-disable-next-line import/prefer-default-export export const redirectToFirstAppPage = () => { return (dispatch: Dispatch, getState: GetState) => { + if (!isSpedingPasswordCreated()) + return dispatch(push(routes.CREATE_SPENDING_PASSWORD)); const accountKeys = readAccountKeysFromLocalStorage(); if (accountKeys) { return dispatch(setAccountFromPrivateKey(accountKeys.privateKey)); diff --git a/examples/wallet/app/constants/routes.json b/examples/wallet/app/constants/routes.json index 40494a1..bc1e0f3 100644 --- a/examples/wallet/app/constants/routes.json +++ b/examples/wallet/app/constants/routes.json @@ -5,5 +5,6 @@ "INPUT_KEYS": "/input_keys", "SEND": "/send", "STAKING": "/staking", - "SETTINGS": "/settings" + "SETTINGS": "/settings", + "CREATE_SPENDING_PASSWORD": "/create_spending_password" } diff --git a/examples/wallet/app/containers/CreateSpendingPassword.js b/examples/wallet/app/containers/CreateSpendingPassword.js new file mode 100644 index 0000000..467c9f4 --- /dev/null +++ b/examples/wallet/app/containers/CreateSpendingPassword.js @@ -0,0 +1,14 @@ +// @flow +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import CreateSpendingPassword from '../pages/CreateSpendingPassword'; +import { setAccount, setAccountFromMnemonic } from '../actions/account'; + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ setAccount, setAccountFromMnemonic }, dispatch); +} + +export default connect( + undefined, + mapDispatchToProps +)(CreateSpendingPassword); diff --git a/examples/wallet/app/pages/CreateSpendingPassword.js b/examples/wallet/app/pages/CreateSpendingPassword.js new file mode 100644 index 0000000..4e4a6b0 --- /dev/null +++ b/examples/wallet/app/pages/CreateSpendingPassword.js @@ -0,0 +1,70 @@ +// @flow +import React, { useState } from 'react'; +import Form from 'react-bootstrap/Form'; +import Button from 'react-bootstrap/Button'; +import Container from 'react-bootstrap/Container'; +import Row from 'react-bootstrap/Row'; +import typeof { setAccountFromMnemonic as SetAccountFromMnemonic } from '../actions/account'; +import { isValidMnemonic } from '../utils/mnemonic'; + +type Props = { + setAccountFromMnemonic: SetAccountFromMnemonic +}; + +// FIXME: this has no error handling, neither while parsing the address +// nor when fetching the balance. +export default ({ setAccountFromMnemonic }: Props) => { + const handleSubmitCreateSpending = function handleSubmitCreateSpending( + event + ) { + event.preventDefault(); + if (isValidMnemonic(newMnemonicPhrase)) { + return Promise.all([ + setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword) + ]); + } + setIsMnemonicValid(false); + }; + + const [isMnemonicValid, setIsMnemonicValid] = useState(true); + + const [newMnemonicPhrase, setNewMnemonicPhrase] = useState(''); + + const [newMnemonicPassword, setNewMnemonicPassword] = useState(''); + + return ( + +
+ + Create your new wallet password: + setNewMnemonicPhrase(event.target.value)} + /> + + setNewMnemonicPassword(event.target.value)} + className="mt-3" + /> + + + + +
+
+ ); +}; diff --git a/examples/wallet/app/utils/decrypt.js b/examples/wallet/app/utils/decrypt.js new file mode 100644 index 0000000..03a2c08 --- /dev/null +++ b/examples/wallet/app/utils/decrypt.js @@ -0,0 +1,43 @@ +import aesjs from 'aes-js'; +import blakejs from 'blakejs'; + +const blake2b = data => blakejs.blake2b(data, null, 32); +const blake2bHex = data => blakejs.blake2bHex(data, null, 32); +const SECRET = 'wallet_demo#'; +const AES_SECRET = ':aes_secret#'; + +export function computeBlake2bHexWithSecret(spendingPassword: string): string { + const fullSpendingPassword = spendingPassword.concat(SECRET); + return blake2bHex(fullSpendingPassword); +} + +export function isBlake2HashHexWithSecretOk( + spendingPassword: string, + blake2bHashHex: string +): boolean { + const fullSpendingPassword = spendingPassword.concat(SECRET); + return blake2bHex(fullSpendingPassword) === blake2bHashHex; +} + +export function aesEncrypt(spendingPassword: string, text: string): string { + const aesPasswordText = spendingPassword.concat(SECRET).concat(AES_SECRET); + const aesKey = blake2b(aesPasswordText); + // eslint-disable-next-line new-cap + const aesCtr = new aesjs.ModeOfOperation.ctr(aesKey, new aesjs.Counter(5)); + const encryptedBytes = aesCtr.encrypt(aesjs.utils.utf8.toBytes(text)); + const encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes); + return encryptedHex; +} + +export function aesDecrypt( + spendingPassword: string, + encryptedHex: string +): string { + const aesPasswordText = spendingPassword.concat(SECRET).concat(AES_SECRET); + const aesKey = blake2b(aesPasswordText); + // eslint-disable-next-line new-cap + const aesCtr = new aesjs.ModeOfOperation.ctr(aesKey, new aesjs.Counter(5)); + const decryptedBytes = aesCtr.decrypt(aesjs.utils.hex.toBytes(encryptedHex)); + const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); + return decryptedText; +} diff --git a/examples/wallet/app/utils/storage.js b/examples/wallet/app/utils/storage.js index b716a38..57d526f 100644 --- a/examples/wallet/app/utils/storage.js +++ b/examples/wallet/app/utils/storage.js @@ -1,6 +1,30 @@ // @flow import type { AccountKeys } from '../reducers/types'; import type { Address, Identifier, PrivateKey } from '../models'; +import { + computeBlake2bHexWithSecret, + isBlake2HashHexWithSecretOk +} from './decrypt'; + +const WALLET_SPEDING_PASSWORD_KEY = 'wallet.spending.pwd'; + +export function saveSpendingPassword(spendingPassword: string): void { + const spendingHash = computeBlake2bHexWithSecret(spendingPassword); + localStorage.setItem(WALLET_SPEDING_PASSWORD_KEY, spendingHash); +} + +export function isValidSpendingPassword(spendingPassword: string): boolean { + const savedSpendingHash = localStorage.getItem(WALLET_SPEDING_PASSWORD_KEY); + if (!savedSpendingHash) + throw new Error('There is not a spending password created'); + return isBlake2HashHexWithSecretOk(spendingPassword, savedSpendingHash); +} + +export function isSpedingPasswordCreated(): boolean { + const savedSpendingHash = localStorage.getItem(WALLET_SPEDING_PASSWORD_KEY); + if (savedSpendingHash) return true; + return false; +} export function saveAccountInfoInLocalStorage(keys: AccountKeys): void { Object.keys(keys).forEach(key => localStorage.setItem(key, keys[key])); From d76a2640efac8251e3d98c1e2f74e4f8d164599c Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Wed, 11 Dec 2019 17:15:45 -0300 Subject: [PATCH 03/24] Create spending password screen is added and displayed when password does not exists. --- .../app/pages/CreateSpendingPassword.js | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/examples/wallet/app/pages/CreateSpendingPassword.js b/examples/wallet/app/pages/CreateSpendingPassword.js index 4e4a6b0..d85ca60 100644 --- a/examples/wallet/app/pages/CreateSpendingPassword.js +++ b/examples/wallet/app/pages/CreateSpendingPassword.js @@ -5,7 +5,6 @@ import Button from 'react-bootstrap/Button'; import Container from 'react-bootstrap/Container'; import Row from 'react-bootstrap/Row'; import typeof { setAccountFromMnemonic as SetAccountFromMnemonic } from '../actions/account'; -import { isValidMnemonic } from '../utils/mnemonic'; type Props = { setAccountFromMnemonic: SetAccountFromMnemonic @@ -18,46 +17,70 @@ export default ({ setAccountFromMnemonic }: Props) => { event ) { event.preventDefault(); - if (isValidMnemonic(newMnemonicPhrase)) { - return Promise.all([ - setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword) - ]); + if (checkValidPassword(password, confirmPassword)) { + return Promise.all([setAccountFromMnemonic(password, confirmPassword)]); } - setIsMnemonicValid(false); }; - const [isMnemonicValid, setIsMnemonicValid] = useState(true); + const checkValidPassword = function checkValidPassword(pass, confirmation) { + if (!pass) return false; + if (pass.length < 8) { + setIsValidPassword(false); + return false; + } + setIsValidPassword(true); + + if (pass !== confirmation) { + setArePasswordAndConfirmationEqual(false); + return false; + } + setArePasswordAndConfirmationEqual(true); + return true; + }; + + const [isValidPassword, setIsValidPassword] = useState(true); + const [ + arePasswordAndConfirmationEqual, + setArePasswordAndConfirmationEqual + ] = useState(true); - const [newMnemonicPhrase, setNewMnemonicPhrase] = useState(''); + const [password, setPassword] = useState(''); - const [newMnemonicPassword, setNewMnemonicPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); return (
- Create your new wallet password: + Create a password for your encrypted storage setNewMnemonicPhrase(event.target.value)} + value={password} + isInvalid={!isValidPassword} + onChange={event => setPassword(event.target.value)} /> -
diff --git a/examples/wallet/app/constants/routes.json b/examples/wallet/app/constants/routes.json index bc1e0f3..40494a1 100644 --- a/examples/wallet/app/constants/routes.json +++ b/examples/wallet/app/constants/routes.json @@ -5,6 +5,5 @@ "INPUT_KEYS": "/input_keys", "SEND": "/send", "STAKING": "/staking", - "SETTINGS": "/settings", - "CREATE_SPENDING_PASSWORD": "/create_spending_password" + "SETTINGS": "/settings" } diff --git a/examples/wallet/app/containers/CreateSpendingPassword.js b/examples/wallet/app/containers/CreateSpendingPassword.js deleted file mode 100644 index 467c9f4..0000000 --- a/examples/wallet/app/containers/CreateSpendingPassword.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; -import CreateSpendingPassword from '../pages/CreateSpendingPassword'; -import { setAccount, setAccountFromMnemonic } from '../actions/account'; - -function mapDispatchToProps(dispatch) { - return bindActionCreators({ setAccount, setAccountFromMnemonic }, dispatch); -} - -export default connect( - undefined, - mapDispatchToProps -)(CreateSpendingPassword); diff --git a/examples/wallet/app/containers/InputKeys.js b/examples/wallet/app/containers/InputKeys.js index fd065c0..1a26d38 100644 --- a/examples/wallet/app/containers/InputKeys.js +++ b/examples/wallet/app/containers/InputKeys.js @@ -2,10 +2,10 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import InputKeys from '../pages/InputKeys'; -import { setAccount, setAccountFromMnemonic } from '../actions/account'; +import { setAccountFromMnemonic, setAccount } from '../actions/account'; function mapDispatchToProps(dispatch) { - return bindActionCreators({ setAccount, setAccountFromMnemonic }, dispatch); + return bindActionCreators({ setAccountFromMnemonic, setAccount }, dispatch); } export default connect(undefined, mapDispatchToProps)(InputKeys); diff --git a/examples/wallet/app/containers/RestoreWalletFromPrivateKey.js b/examples/wallet/app/containers/RestoreWalletFromPrivateKey.js new file mode 100644 index 0000000..6e17147 --- /dev/null +++ b/examples/wallet/app/containers/RestoreWalletFromPrivateKey.js @@ -0,0 +1,14 @@ +// @flow +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import RestoreWalletFromPrivateKey from '../components/RestoreWalletFromPrivateKey'; +import { setAccount } from '../actions/account'; + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ setAccount }, dispatch); +} + +export default connect( + undefined, + mapDispatchToProps +)(RestoreWalletFromPrivateKey); diff --git a/examples/wallet/app/pages/InputKeys.js b/examples/wallet/app/pages/InputKeys.js index d469613..e9ece0a 100644 --- a/examples/wallet/app/pages/InputKeys.js +++ b/examples/wallet/app/pages/InputKeys.js @@ -6,6 +6,7 @@ import Container from 'react-bootstrap/Container'; import Tabs from 'react-bootstrap/Tabs'; import Tab from 'react-bootstrap/Tab'; import Row from 'react-bootstrap/Row'; +import RestoreWalletFromPrivateKey from '../components/RestoreWalletFromPrivateKey'; import typeof { setAccountFromMnemonic as SetAccountFromMnemonic, setAccount as SetAccount @@ -19,11 +20,7 @@ type Props = { // FIXME: this has no error handling, neither while parsing the address // nor when fetching the balance. -export default ({ setAccount, setAccountFromMnemonic }: Props) => { - const handleSubmitKeyString = function handleSubmitKeyString(event) { - event.preventDefault(); - return setAccount(newPrivateKey); - }; +export default ({ setAccountFromMnemonic, setAccount }: Props) => { const checkIsValidMnemonicPhrase = function checkIsValidMnemonicPhrase() { setIsMnemonicValid(isValidMnemonic(newMnemonicPhrase)); }; @@ -38,8 +35,6 @@ export default ({ setAccount, setAccountFromMnemonic }: Props) => { const [isMnemonicValid, setIsMnemonicValid] = useState(true); - const [newPrivateKey, setNewPrivateKey] = useState(''); - const [newMnemonicPhrase, setNewMnemonicPhrase] = useState(''); const [newMnemonicPassword, setNewMnemonicPassword] = useState(''); @@ -47,36 +42,7 @@ export default ({ setAccount, setAccountFromMnemonic }: Props) => { return ( - -
- - Private key: - setNewPrivateKey(event.target.value)} - /> - - It's a string like: -
- - ed25519e_sk15psr45hyqnpwcl8xd4lv0m32prenhh8kcltgte2305h5jgynndxect9274j0am0qmmd0snjuadnm6xkgssnkn2njvkg8et8qg0vevsgnwvmpl - -
-
- - {/* TODO: bind this button */} - - - -
-
+
From e42bc53f2777a44cbe918f887b43b0dda1fbc6da Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Thu, 12 Dec 2019 16:03:38 -0300 Subject: [PATCH 05/24] A new component to revoer BIP39 phrases is added --- .../components/RestoreWalletFromMnemonic.js | 143 ++++++++++++++++++ .../containers/RestoreWalletFromMnemonic.js | 14 ++ examples/wallet/app/pages/InputKeys.js | 58 +------ 3 files changed, 163 insertions(+), 52 deletions(-) create mode 100644 examples/wallet/app/components/RestoreWalletFromMnemonic.js create mode 100644 examples/wallet/app/containers/RestoreWalletFromMnemonic.js diff --git a/examples/wallet/app/components/RestoreWalletFromMnemonic.js b/examples/wallet/app/components/RestoreWalletFromMnemonic.js new file mode 100644 index 0000000..9ca2b61 --- /dev/null +++ b/examples/wallet/app/components/RestoreWalletFromMnemonic.js @@ -0,0 +1,143 @@ +// @flow +import React, { useState } from 'react'; +import Form from 'react-bootstrap/Form'; +import Button from 'react-bootstrap/Button'; +import Container from 'react-bootstrap/Container'; +import Row from 'react-bootstrap/Row'; +import typeof { setAccountFromMnemonic as SetAccountFromMnemonic } from '../actions/account'; +import { isValidMnemonic } from '../utils/mnemonic'; + +type Props = { + setAccountFromMnemonic: SetAccountFromMnemonic +}; + +// FIXME: this has no error handling, neither while parsing the address +// nor when fetching the balance. +export default ({ setAccountFromMnemonic }: Props) => { + const checkIsValidMnemonicPhrase = function checkIsValidMnemonicPhrase() { + setIsMnemonicValid(isValidMnemonic(newMnemonicPhrase)); + }; + + const handleSubmitMnemonic = function handleSubmitMnemonic(event) { + event.preventDefault(); + if ( + isValidMnemonic(newMnemonicPhrase) && + checkValidPassword(password, confirmPassword) + ) { + return Promise.all([ + setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword) + ]); + } + setIsMnemonicValid(false); + }; + + const [isMnemonicValid, setIsMnemonicValid] = useState(true); + + const [newMnemonicPhrase, setNewMnemonicPhrase] = useState(''); + + const [newMnemonicPassword, setNewMnemonicPassword] = useState(''); + + const checkValidPassword = function checkValidPassword(pass, confirmation) { + if (!pass && !confirmation) return true; + if (pass.length < 8) { + setIsValidPassword(false); + return false; + } + setIsValidPassword(true); + + if (pass !== confirmation) { + setArePasswordAndConfirmationEqual(false); + return false; + } + setArePasswordAndConfirmationEqual(true); + return true; + }; + + const [ + arePasswordAndConfirmationEqual, + setArePasswordAndConfirmationEqual + ] = useState(true); + + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isValidPassword, setIsValidPassword] = useState(true); + + return ( + +
+ + Wallet Seed: + setNewMnemonicPhrase(event.target.value)} + onBlur={() => checkIsValidMnemonicPhrase()} + /> + + + Example: +
+ + decade panther require cruise robust mail gadget advice tonight + post inner snack + +
+ setNewMnemonicPassword(event.target.value)} + className="mt-3" + /> + + Create a password to store your settings securely in an encrypted + storage + + setPassword(event.target.value)} + /> + + setConfirmPassword(event.target.value)} + className="mt-3" + /> + +
+ + + + +
+
+ ); +}; diff --git a/examples/wallet/app/containers/RestoreWalletFromMnemonic.js b/examples/wallet/app/containers/RestoreWalletFromMnemonic.js new file mode 100644 index 0000000..8fd26e5 --- /dev/null +++ b/examples/wallet/app/containers/RestoreWalletFromMnemonic.js @@ -0,0 +1,14 @@ +// @flow +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import RestoreWalletFromMnemonic from '../components/RestoreWalletFromMnemonic'; +import { setAccount } from '../actions/account'; + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ setAccount }, dispatch); +} + +export default connect( + undefined, + mapDispatchToProps +)(RestoreWalletFromMnemonic); diff --git a/examples/wallet/app/pages/InputKeys.js b/examples/wallet/app/pages/InputKeys.js index e9ece0a..e0584dc 100644 --- a/examples/wallet/app/pages/InputKeys.js +++ b/examples/wallet/app/pages/InputKeys.js @@ -1,12 +1,11 @@ // @flow -import React, { useState } from 'react'; -import Form from 'react-bootstrap/Form'; -import Button from 'react-bootstrap/Button'; -import Container from 'react-bootstrap/Container'; +import React from 'react'; import Tabs from 'react-bootstrap/Tabs'; import Tab from 'react-bootstrap/Tab'; import Row from 'react-bootstrap/Row'; import RestoreWalletFromPrivateKey from '../components/RestoreWalletFromPrivateKey'; +import RestoreWalletFromMnemonic from '../components/RestoreWalletFromMnemonic'; + import typeof { setAccountFromMnemonic as SetAccountFromMnemonic, setAccount as SetAccount @@ -45,54 +44,9 @@ export default ({ setAccountFromMnemonic, setAccount }: Props) => {
- -
- - Wallet Seed: - setNewMnemonicPhrase(event.target.value)} - onBlur={() => checkIsValidMnemonicPhrase()} - /> - - - Example: -
- - decade panther require cruise robust mail gadget advice - tonight post inner snack - -
- setNewMnemonicPassword(event.target.value)} - className="mt-3" - /> -
- - {/* TODO: bind this button */} - - - -
-
+
); From decedd56f3f9ed81c17bd30809e3494464a826b6 Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Fri, 13 Dec 2019 11:47:12 -0300 Subject: [PATCH 06/24] Saving encrypted wallet function is added and ivoked with hardcoded password --- examples/wallet/app/actions/account.js | 5 +++-- examples/wallet/app/utils/storage.js | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/wallet/app/actions/account.js b/examples/wallet/app/actions/account.js index 06403d3..9475b01 100644 --- a/examples/wallet/app/actions/account.js +++ b/examples/wallet/app/actions/account.js @@ -32,7 +32,7 @@ import { getTransactions } from '../utils/nodeConnection'; import { isValidMnemonic, createSeedFromMnemonic } from '../utils/mnemonic'; -import { saveAccountInfoInLocalStorage } from '../utils/storage'; +import { saveAccountInfoInDEN, saveSpendingPassword } from '../utils/storage'; import routes from '../constants/routes.json'; @@ -94,7 +94,8 @@ const initializeKeysAndRedirect = ( ...keys }); if (saveAccount) { - saveAccountInfoInLocalStorage(keys); + saveSpendingPassword('manteca'); + saveAccountInfoInDEN('manteca', keys); } return dispatch(updateAccountTransactionsAndState()); diff --git a/examples/wallet/app/utils/storage.js b/examples/wallet/app/utils/storage.js index 57d526f..b745fb9 100644 --- a/examples/wallet/app/utils/storage.js +++ b/examples/wallet/app/utils/storage.js @@ -7,6 +7,7 @@ import { } from './decrypt'; const WALLET_SPEDING_PASSWORD_KEY = 'wallet.spending.pwd'; +const WALLET_ENCRYPTED_KEYS = 'wallet.encrypted.account.info'; export function saveSpendingPassword(spendingPassword: string): void { const spendingHash = computeBlake2bHexWithSecret(spendingPassword); @@ -26,10 +27,17 @@ export function isSpedingPasswordCreated(): boolean { return false; } -export function saveAccountInfoInLocalStorage(keys: AccountKeys): void { - Object.keys(keys).forEach(key => localStorage.setItem(key, keys[key])); +export function saveAccountInfoInDEN( + spendingPassword: ?string, + keys: AccountKeys +): void { + const plainTextAccountInfo = JSON.stringify(keys); + const spedingPwd: string = !spendingPassword ? '' : spendingPassword; + const encryptedTextAccountInfo = aesEncrypt(spedingPwd, plainTextAccountInfo); + localStorage.setItem(WALLET_ENCRYPTED_KEYS, encryptedTextAccountInfo); } +// eslint-disable-next-line flowtype/space-after-type-colon export function readAccountKeysFromLocalStorage(): ?AccountKeys { if (localStorage.getItem('privateKey')) { return { From b94f937c04c54f0fbbf81e1843524de87be307a8 Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Mon, 16 Dec 2019 12:31:00 -0300 Subject: [PATCH 07/24] speding password input on restore key is added --- examples/wallet/app/actions/account.js | 39 +++++++++++-------- .../components/RestoreWalletFromMnemonic.js | 2 +- .../components/RestoreWalletFromPrivateKey.js | 4 +- examples/wallet/app/utils/storage.js | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/examples/wallet/app/actions/account.js b/examples/wallet/app/actions/account.js index 9475b01..3ec8e6e 100644 --- a/examples/wallet/app/actions/account.js +++ b/examples/wallet/app/actions/account.js @@ -41,15 +41,20 @@ export const SET_KEYS = 'SET_KEYS'; export const PRIVATE_KEY_ERROR = 'privateKeyError'; export const ACCOUNT_STATE_ERROR = 'accountStateError'; -export function setAccount(privateKey: string): Thunk { +export function setAccount( + privateKey: string, + spendingPassword: ?string +): Thunk { return function setAccountThunk(dispatch) { - return getAccountFromPrivateKey(privateKey).then( - curry(initializeKeysAndRedirect)(dispatch) + return getAccountFromPrivateKey(privateKey).then(keys => + curry(initializeKeysAndRedirect)(dispatch, keys, spendingPassword) ); }; } -export function setAccountFromPrivateKey(privateKey: string): Thunk { +export function setAccountFromPrivateKey( + privateKey: string +): Thunk { return function setAccountFromPrivateKeyThunk(dispatch) { getAccountFromPrivateKey(privateKey) .then(loadedPrivateKey => { @@ -66,7 +71,7 @@ export function setAccountFromPrivateKey(privateKey: string): Thunk { }; } -export function updateAccountTransactionsAndState(): Thunk { +export function updateAccountTransactionsAndState(): Thunk { return function updateAccountTransactionsAndStateThunk(dispatch) { return Promise.all([ dispatch(updateAccountTransactions()), @@ -85,31 +90,31 @@ export function updateAccountTransactionsAndState(): Thunk { * @param {boolean} saveAccount If true, the keys are stored in the local storage */ const initializeKeysAndRedirect = ( - dispatch: Dispatch, + dispatch: Dispatch, keys: AccountKeys, - saveAccount?: boolean = true + spendingPassword: ?string = '' ) => { dispatch({ type: SET_KEYS, ...keys }); - if (saveAccount) { - saveSpendingPassword('manteca'); - saveAccountInfoInDEN('manteca', keys); - } + + saveSpendingPassword(spendingPassword); + saveAccountInfoInDEN(spendingPassword, keys); return dispatch(updateAccountTransactionsAndState()); }; export function setAccountFromMnemonic( mnemonicPhrase: string, - mnemonicPassword?: string -) { + mnemonicPassword: string, + spendingPassword: ?string +): Thunk { if (isValidMnemonic(mnemonicPhrase)) { const seed = createSeedFromMnemonic(mnemonicPhrase, mnemonicPassword); return function setAccountThunk(dispatch) { - return getAccountFromSeed(seed).then( - curry(initializeKeysAndRedirect)(dispatch) + return getAccountFromSeed(seed).then(keys => + curry(initializeKeysAndRedirect)(dispatch, keys, spendingPassword) ); }; } @@ -122,7 +127,7 @@ export type SetAccountStateAction = { } & AccountState; export const SET_ACCOUNT_STATE = 'SET_ACCOUNT_STATE'; -export function updateAccountState(): Thunk { +export function updateAccountState(): Thunk { return function updateAccountStateThunk(dispatch, getState) { const { identifier }: { identifier: Identifier } = getState().account; if (!identifier) { @@ -154,7 +159,7 @@ export type SetTransactionsAction = { }; export const SET_TRANSACTIONS = 'SET_TRANSACTIONS'; -export function updateAccountTransactions(): Thunk { +export function updateAccountTransactions(): Thunk { return function updateAccountTransactionsThunk(dispatch, getState) { const { address }: { address: Address } = getState().account; if (!address) { diff --git a/examples/wallet/app/components/RestoreWalletFromMnemonic.js b/examples/wallet/app/components/RestoreWalletFromMnemonic.js index 9ca2b61..9f27829 100644 --- a/examples/wallet/app/components/RestoreWalletFromMnemonic.js +++ b/examples/wallet/app/components/RestoreWalletFromMnemonic.js @@ -25,7 +25,7 @@ export default ({ setAccountFromMnemonic }: Props) => { checkValidPassword(password, confirmPassword) ) { return Promise.all([ - setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword) + setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword, password) ]); } setIsMnemonicValid(false); diff --git a/examples/wallet/app/components/RestoreWalletFromPrivateKey.js b/examples/wallet/app/components/RestoreWalletFromPrivateKey.js index dc6e8bf..0b3608f 100644 --- a/examples/wallet/app/components/RestoreWalletFromPrivateKey.js +++ b/examples/wallet/app/components/RestoreWalletFromPrivateKey.js @@ -18,7 +18,7 @@ export default ({ setAccount }: Props) => { ) { event.preventDefault(); if (checkValidPassword(password, confirmPassword)) { - setAccount(newPrivateKey); + setAccount(newPrivateKey, password); } }; @@ -98,7 +98,7 @@ export default ({ setAccount }: Props) => { className="text-danger" hidden={arePasswordAndConfirmationEqual} > - password and confirmation must be the same. + Password and confirmation must be the same. diff --git a/examples/wallet/app/utils/storage.js b/examples/wallet/app/utils/storage.js index b745fb9..15333cc 100644 --- a/examples/wallet/app/utils/storage.js +++ b/examples/wallet/app/utils/storage.js @@ -9,7 +9,7 @@ import { const WALLET_SPEDING_PASSWORD_KEY = 'wallet.spending.pwd'; const WALLET_ENCRYPTED_KEYS = 'wallet.encrypted.account.info'; -export function saveSpendingPassword(spendingPassword: string): void { +export function saveSpendingPassword(spendingPassword: ?string = ''): void { const spendingHash = computeBlake2bHexWithSecret(spendingPassword); localStorage.setItem(WALLET_SPEDING_PASSWORD_KEY, spendingHash); } From 689b791134ef7fdd31095511136bada38afa73e9 Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Wed, 18 Dec 2019 11:18:43 -0300 Subject: [PATCH 08/24] Unlock wallet screen is added to unlock with spending password --- examples/wallet/app/Routes.js | 2 + examples/wallet/app/actions/account.js | 14 ++- examples/wallet/app/actions/router.js | 11 +- .../components/RestoreWalletFromMnemonic.js | 101 +++++++++++------- .../components/RestoreWalletFromPrivateKey.js | 88 +++++++++------ examples/wallet/app/constants/routes.json | 3 +- .../wallet/app/containers/UnlockWallet.js | 14 +++ examples/wallet/app/pages/UnlockWallet.js | 58 ++++++++++ examples/wallet/app/reducers/account.js | 8 ++ examples/wallet/app/reducers/types.js | 5 + examples/wallet/app/utils/storage.js | 24 +++-- 11 files changed, 243 insertions(+), 85 deletions(-) create mode 100644 examples/wallet/app/containers/UnlockWallet.js create mode 100644 examples/wallet/app/pages/UnlockWallet.js diff --git a/examples/wallet/app/Routes.js b/examples/wallet/app/Routes.js index 5764ab0..e23aa8f 100644 --- a/examples/wallet/app/Routes.js +++ b/examples/wallet/app/Routes.js @@ -10,6 +10,7 @@ import ChooseRestoreOrImport from './containers/ChooseRestoreOrImport'; import Delegate from './pages/Delegate'; import Index from './containers/Index'; import InputKeys from './containers/InputKeys'; +import UnlockWallet from './containers/UnlockWallet'; export default () => ( @@ -19,6 +20,7 @@ export default () => ( + { return function setAccountFromPrivateKeyThunk(dispatch) { - getAccountFromPrivateKey(privateKey) + return getAccountFromPrivateKey(privateKey) .then(loadedPrivateKey => { dispatch({ type: SET_KEYS, diff --git a/examples/wallet/app/actions/router.js b/examples/wallet/app/actions/router.js index b38a19d..f186597 100644 --- a/examples/wallet/app/actions/router.js +++ b/examples/wallet/app/actions/router.js @@ -5,14 +5,17 @@ import type { Address } from '../models'; import routes from '../constants/routes'; import { setAccountFromPrivateKey } from './account'; -import { readAccountKeysFromLocalStorage } from '../utils/storage'; - -export const SET_KEYS = 'SET_KEYS'; +import { + isSpedingPasswordCreated, + readAccountKeysFromDEN +} from '../utils/storage'; // eslint-disable-next-line import/prefer-default-export export const redirectToFirstAppPage = () => { return (dispatch: Dispatch, getState: GetState) => { - const accountKeys = readAccountKeysFromLocalStorage(); + if (isSpedingPasswordCreated()) return dispatch(push(routes.UNLOCK_WALLET)); + + const accountKeys = readAccountKeysFromDEN('manteca'); if (accountKeys) { return dispatch(setAccountFromPrivateKey(accountKeys.privateKey)); } diff --git a/examples/wallet/app/components/RestoreWalletFromMnemonic.js b/examples/wallet/app/components/RestoreWalletFromMnemonic.js index 9f27829..79d8533 100644 --- a/examples/wallet/app/components/RestoreWalletFromMnemonic.js +++ b/examples/wallet/app/components/RestoreWalletFromMnemonic.js @@ -20,18 +20,41 @@ export default ({ setAccountFromMnemonic }: Props) => { const handleSubmitMnemonic = function handleSubmitMnemonic(event) { event.preventDefault(); - if ( - isValidMnemonic(newMnemonicPhrase) && - checkValidPassword(password, confirmPassword) - ) { + if (mustCreateSpendingPassword) { + if ( + isValidMnemonic(newMnemonicPhrase) && + checkValidPassword(password, confirmPassword) + ) { + return Promise.all([ + setAccountFromMnemonic( + newMnemonicPhrase, + newMnemonicPassword, + password + ) + ]); + } + } + if (isValidMnemonic(newMnemonicPhrase)) { return Promise.all([ - setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword, password) + setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword, '') ]); } + setIsMnemonicValid(false); }; + const handleCheckCreateSpendingPassword = function handleCheckCreateSpendingPassword( + evt + ) { + setMustCreateSpendingPassword(evt.target.checked); + setHiddenSpendingPassword(!evt.target.checked); + }; + const [isMnemonicValid, setIsMnemonicValid] = useState(true); + const [hiddenSpendingPassword, setHiddenSpendingPassword] = useState(false); + const [mustCreateSpendingPassword, setMustCreateSpendingPassword] = useState( + true + ); const [newMnemonicPhrase, setNewMnemonicPhrase] = useState(''); @@ -97,37 +120,43 @@ export default ({ setAccountFromMnemonic }: Props) => { onChange={event => setNewMnemonicPassword(event.target.value)} className="mt-3" /> - - Create a password to store your settings securely in an encrypted - storage - - setPassword(event.target.value)} - /> - - setConfirmPassword(event.target.value)} - className="mt-3" - /> - + + handleCheckCreateSpendingPassword(event)} + /> + + + + +
+ ); +}; diff --git a/examples/wallet/app/reducers/account.js b/examples/wallet/app/reducers/account.js index f4adad0..28e8cbb 100644 --- a/examples/wallet/app/reducers/account.js +++ b/examples/wallet/app/reducers/account.js @@ -1,6 +1,7 @@ // @flow import sortBy from 'lodash/sortBy'; import { + SET_SPENDING_PASSWORD, SET_KEYS, SET_ACCOUNT_STATE, SEND_TRANSACTION, @@ -12,6 +13,7 @@ import type { SendFundsAction, SetAccountStateAction, SetTransactionsAction, + SetKeysWithSpendingPasswordAction, SendStakeDelegation } from '../actions/account'; import type { Account } from './types'; @@ -21,6 +23,7 @@ export default function account( state: Account, // eslint-disable-next-line flowtype/space-after-type-colon action: + | SetKeysWithSpendingPasswordAction | SetKeysAction | SetAccountStateAction | SendFundsAction @@ -31,6 +34,11 @@ export default function account( return { transactions: [] }; } switch (action.type) { + case SET_SPENDING_PASSWORD: + return Object.assign({}, state, { + walletId: action.walletId, + spendingPassword: action.spendingPassword + }); case SET_KEYS: return { ...state, diff --git a/examples/wallet/app/reducers/types.js b/examples/wallet/app/reducers/types.js index bd4d444..1df901f 100644 --- a/examples/wallet/app/reducers/types.js +++ b/examples/wallet/app/reducers/types.js @@ -37,6 +37,11 @@ export type AccountKeys = { identifier: Identifier }; +export type SpendingPassword = { + walletId: ?string, + spendingPassword: ?string +}; + export type AccountState = { balance: Balance, counter: Counter, diff --git a/examples/wallet/app/utils/storage.js b/examples/wallet/app/utils/storage.js index 15333cc..003a0c1 100644 --- a/examples/wallet/app/utils/storage.js +++ b/examples/wallet/app/utils/storage.js @@ -1,9 +1,10 @@ // @flow import type { AccountKeys } from '../reducers/types'; -import type { Address, Identifier, PrivateKey } from '../models'; import { computeBlake2bHexWithSecret, - isBlake2HashHexWithSecretOk + isBlake2HashHexWithSecretOk, + aesEncrypt, + aesDecrypt } from './decrypt'; const WALLET_SPEDING_PASSWORD_KEY = 'wallet.spending.pwd'; @@ -38,13 +39,18 @@ export function saveAccountInfoInDEN( } // eslint-disable-next-line flowtype/space-after-type-colon -export function readAccountKeysFromLocalStorage(): ?AccountKeys { - if (localStorage.getItem('privateKey')) { - return { - address: (localStorage.getItem('address'): Address), - identifier: (localStorage.getItem('identifier'): Identifier), - privateKey: (localStorage.getItem('privateKey'): PrivateKey) - }; +export function readAccountKeysFromDEN( + spendingPassword: ?string +): ?AccountKeys { + const encryptedHex = localStorage.getItem(WALLET_ENCRYPTED_KEYS); + try { + if (encryptedHex && encryptedHex.length > 0) { + const plainText = aesDecrypt(spendingPassword, encryptedHex); + const accountKeys = JSON.parse(plainText); + return accountKeys; + } + } catch (e) { + console.error('There was an error unlocking wallet', e.toString()); } return undefined; } From 892d5432a74abc4e9b4ed6644f4f8a191b33788b Mon Sep 17 00:00:00 2001 From: dmernies-ioh Date: Wed, 18 Dec 2019 16:21:17 -0300 Subject: [PATCH 09/24] Unlocking wallet function are renamed --- .../wallet/app/components/RestoreWalletFromMnemonic.js | 8 ++++++-- .../wallet/app/components/RestoreWalletFromPrivateKey.js | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/wallet/app/components/RestoreWalletFromMnemonic.js b/examples/wallet/app/components/RestoreWalletFromMnemonic.js index 79d8533..6885de3 100644 --- a/examples/wallet/app/components/RestoreWalletFromMnemonic.js +++ b/examples/wallet/app/components/RestoreWalletFromMnemonic.js @@ -23,7 +23,7 @@ export default ({ setAccountFromMnemonic }: Props) => { if (mustCreateSpendingPassword) { if ( isValidMnemonic(newMnemonicPhrase) && - checkValidPassword(password, confirmPassword) + checkValidSpendingPassword(password, confirmPassword) ) { return Promise.all([ setAccountFromMnemonic( @@ -60,7 +60,10 @@ export default ({ setAccountFromMnemonic }: Props) => { const [newMnemonicPassword, setNewMnemonicPassword] = useState(''); - const checkValidPassword = function checkValidPassword(pass, confirmation) { + const checkValidSpendingPassword = function checkValidSpendingPassword( + pass, + confirmation + ) { if (!pass && !confirmation) return true; if (pass.length < 8) { setIsValidPassword(false); @@ -126,6 +129,7 @@ export default ({ setAccountFromMnemonic }: Props) => { label="Create a password to store your settings securely in an encrypted storage" onChange={event => handleCheckCreateSpendingPassword(event)} + checked={mustCreateSpendingPassword} /> From 5c020dbdc202bdb2630c3cf9fb36f6fd96ec0876 Mon Sep 17 00:00:00 2001 From: dmerniestic1987 Date: Thu, 19 Dec 2019 12:02:26 -0300 Subject: [PATCH 10/24] Cleaning unused code from routes and nodeConection --- examples/wallet/app/actions/router.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/wallet/app/actions/router.js b/examples/wallet/app/actions/router.js index f186597..7715803 100644 --- a/examples/wallet/app/actions/router.js +++ b/examples/wallet/app/actions/router.js @@ -14,11 +14,6 @@ import { export const redirectToFirstAppPage = () => { return (dispatch: Dispatch, getState: GetState) => { if (isSpedingPasswordCreated()) return dispatch(push(routes.UNLOCK_WALLET)); - - const accountKeys = readAccountKeysFromDEN('manteca'); - if (accountKeys) { - return dispatch(setAccountFromPrivateKey(accountKeys.privateKey)); - } const { account: { address } }: { account: { address: Address } } = getState(); From 0112de94e35e6ff879008618b46c5f5d2400f53a Mon Sep 17 00:00:00 2001 From: dmerniestic1987 Date: Thu, 2 Jan 2020 12:08:59 -0300 Subject: [PATCH 11/24] Corrections by code review --- examples/explorer/.eslintrc | 2 +- examples/wallet/app/Routes.js | 2 +- examples/wallet/app/actions/account.js | 85 ++++++++++++------- .../components/RestoreWalletFromMnemonic.js | 63 +++++--------- .../components/RestoreWalletFromPrivateKey.js | 39 ++------- examples/wallet/app/containers/InputKeys.js | 11 --- .../containers/RestoreWalletFromMnemonic.js | 4 +- examples/wallet/app/pages/InputKeys.js | 44 ++-------- examples/wallet/app/pages/UnlockWallet.js | 7 +- examples/wallet/app/pages/UnlockWallet.scss | 4 + examples/wallet/app/reducers/types.js | 4 +- examples/wallet/app/utils/decrypt.js | 3 +- examples/wallet/app/utils/storage.js | 4 +- 13 files changed, 101 insertions(+), 171 deletions(-) delete mode 100644 examples/wallet/app/containers/InputKeys.js create mode 100644 examples/wallet/app/pages/UnlockWallet.scss diff --git a/examples/explorer/.eslintrc b/examples/explorer/.eslintrc index a44d541..8dd7fb2 100644 --- a/examples/explorer/.eslintrc +++ b/examples/explorer/.eslintrc @@ -15,7 +15,7 @@ }, "rules": { "func-names": "error", - "new-cap": "off", + "new-cap": ["off", { "capIsNewExceptionPattern": "^Aesjs\.." }], "arrow-parens": "off", "consistent-return": "off", "comma-dangle": "off", diff --git a/examples/wallet/app/Routes.js b/examples/wallet/app/Routes.js index e23aa8f..4e96b62 100644 --- a/examples/wallet/app/Routes.js +++ b/examples/wallet/app/Routes.js @@ -9,7 +9,7 @@ import Settings from './pages/Settings'; import ChooseRestoreOrImport from './containers/ChooseRestoreOrImport'; import Delegate from './pages/Delegate'; import Index from './containers/Index'; -import InputKeys from './containers/InputKeys'; +import InputKeys from './pages/InputKeys'; import UnlockWallet from './containers/UnlockWallet'; export default () => ( diff --git a/examples/wallet/app/actions/account.js b/examples/wallet/app/actions/account.js index 84a0f29..423074d 100644 --- a/examples/wallet/app/actions/account.js +++ b/examples/wallet/app/actions/account.js @@ -16,9 +16,7 @@ import type { Delegation, Identifier, TransactionHash, - Transaction, - Balance, - Counter + Transaction } from '../models'; import { updateNodeSettings } from './nodeSettings'; import { @@ -34,7 +32,7 @@ import { } from '../utils/nodeConnection'; import { isValidMnemonic, createSeedFromMnemonic } from '../utils/mnemonic'; import { - saveAccountInfoInDEN, + saveEncryptedAccountInfo, saveSpendingPassword, readAccountKeysFromDEN } from '../utils/storage'; @@ -46,8 +44,7 @@ export type SetKeysWithSpendingPasswordAction = { type: 'SET_SPENDING_PASSWORD' } & SpendingPassword; export const SET_KEYS = 'SET_KEYS'; -export const PRIVATE_KEY_ERROR = 'privateKeyError'; -export const ACCOUNT_STATE_ERROR = 'accountStateError'; +export const SET_SPENDING_PASSWORD = 'SET_SPENDING_PASSWORD'; export function setAccount( privateKey: string, @@ -60,32 +57,49 @@ export function setAccount( }; } -export function setAccountFromPrivateKey( - privateKey: string +export function setKeysWithSpendingPassword( + spendingPassword: ?string = '' ): Thunk { - return function setAccountFromPrivateKeyThunk(dispatch) { - return getAccountFromPrivateKey(privateKey) - .then(loadedPrivateKey => { - dispatch({ - type: SET_KEYS, - ...loadedPrivateKey - }); - return dispatch(updateAccountTransactionsAndState()); - }) - .catch(error => { - console.error(error); - throw new Error(PRIVATE_KEY_ERROR); + return function setKeysWithSpendingPasswordThunk(dispatch) { + const accountKeys = readAccountKeysFromDEN(spendingPassword); + if (accountKeys) { + const spendingPasswordKeys = { + walletId: 'wallet01', + spendingPassword + }; + dispatch({ + type: SET_SPENDING_PASSWORD, + ...spendingPasswordKeys }); + + return getAccountFromPrivateKey(accountKeys.privateKey).then(keys => + curry(initializeKeysAndRedirect)(dispatch, keys, spendingPassword) + ); + } + throw new Error('Invalid password'); }; } -export function updateAccountTransactionsAndState(): Thunk { - return function updateAccountTransactionsAndStateThunk(dispatch) { - return Promise.all([ - dispatch(updateAccountTransactions()), - dispatch(updateNodeSettings()), - dispatch(updateAccountState()) - ]).then(() => dispatch(push(routes.WALLET))); +export function setAccountFromPrivateKey( + privateKey: string +): Thunk { + return function setAccountFromPrivateKeyThunk(dispatch) { + return getAccountFromPrivateKey(privateKey).then(loadedPrivateKey => { + dispatch({ + type: SET_KEYS, + ...loadedPrivateKey + }); + return Promise.all([ + dispatch(updateAccountTransactions()), + dispatch(updateNodeSettings()), + dispatch(updateAccountState()) + ]) + .then(() => dispatch(push(routes.WALLET))) + .catch(error => { + console.log(error); + dispatch(push(routes.INPUT_KEYS)); + }); + }); }; } @@ -108,9 +122,18 @@ const initializeKeysAndRedirect = ( }); saveSpendingPassword(spendingPassword); - saveAccountInfoInDEN(spendingPassword, keys); + saveEncryptedAccountInfo(spendingPassword, keys); - return dispatch(updateAccountTransactionsAndState()); + return Promise.all([ + dispatch(updateAccountTransactions()), + dispatch(updateNodeSettings()), + dispatch(updateAccountState()) + ]) + .then(() => dispatch(push(routes.WALLET))) + .catch(error => { + console.log(error); + dispatch(push(routes.WALLET)); + }); }; export function setAccountFromMnemonic( @@ -155,7 +178,7 @@ export function updateAccountState(): Thunk { // TODO: display a notification or something .catch(() => { console.error('there was an error fetching account info'); - return Promise.reject(new Error(ACCOUNT_STATE_ERROR)); + return Promise.reject(); }) ); }; @@ -187,7 +210,7 @@ export function updateAccountTransactions(): Thunk { // TODO: display a notification or something .catch(() => { console.error('there was an error fetching transactions'); - return Promise.reject(new Error(ACCOUNT_STATE_ERROR)); + return Promise.reject(); }) ); }; diff --git a/examples/wallet/app/components/RestoreWalletFromMnemonic.js b/examples/wallet/app/components/RestoreWalletFromMnemonic.js index 6885de3..46e98ad 100644 --- a/examples/wallet/app/components/RestoreWalletFromMnemonic.js +++ b/examples/wallet/app/components/RestoreWalletFromMnemonic.js @@ -11,8 +11,6 @@ type Props = { setAccountFromMnemonic: SetAccountFromMnemonic }; -// FIXME: this has no error handling, neither while parsing the address -// nor when fetching the balance. export default ({ setAccountFromMnemonic }: Props) => { const checkIsValidMnemonicPhrase = function checkIsValidMnemonicPhrase() { setIsMnemonicValid(isValidMnemonic(newMnemonicPhrase)); @@ -20,41 +18,20 @@ export default ({ setAccountFromMnemonic }: Props) => { const handleSubmitMnemonic = function handleSubmitMnemonic(event) { event.preventDefault(); - if (mustCreateSpendingPassword) { - if ( - isValidMnemonic(newMnemonicPhrase) && - checkValidSpendingPassword(password, confirmPassword) - ) { - return Promise.all([ - setAccountFromMnemonic( - newMnemonicPhrase, - newMnemonicPassword, - password - ) - ]); - } - } if (isValidMnemonic(newMnemonicPhrase)) { - return Promise.all([ - setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword, '') - ]); + if (checkValidSpendingPassword(password, confirmPassword)) { + return setAccountFromMnemonic( + newMnemonicPhrase, + newMnemonicPassword, + password + ); + } + } else { + setIsMnemonicValid(false); } - - setIsMnemonicValid(false); - }; - - const handleCheckCreateSpendingPassword = function handleCheckCreateSpendingPassword( - evt - ) { - setMustCreateSpendingPassword(evt.target.checked); - setHiddenSpendingPassword(!evt.target.checked); }; const [isMnemonicValid, setIsMnemonicValid] = useState(true); - const [hiddenSpendingPassword, setHiddenSpendingPassword] = useState(false); - const [mustCreateSpendingPassword, setMustCreateSpendingPassword] = useState( - true - ); const [newMnemonicPhrase, setNewMnemonicPhrase] = useState(''); @@ -118,21 +95,17 @@ export default ({ setAccountFromMnemonic }: Props) => { setNewMnemonicPassword(event.target.value)} className="mt-3" /> - - handleCheckCreateSpendingPassword(event)} - checked={mustCreateSpendingPassword} - /> - -