-
Notifications
You must be signed in to change notification settings - Fork 14
Wallet local storage with spending password #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
06b76d9
a547967
d76a264
17ab19a
e42bc53
decedd5
b94f937
689b791
892d543
5c020db
0112de9
53d6a6f
8c9a28f
f4e53a9
797fabb
9815315
4cecbb8
853a5e8
0342b9e
ecc2841
75a13f1
1761414
86215c3
86d49cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,47 +30,38 @@ import { | |
getTransactions | ||
} from '../utils/nodeConnection'; | ||
import { isValidMnemonic, createSeedFromMnemonic } from '../utils/mnemonic'; | ||
import { saveAccountInfoInLocalStorage } from '../utils/storage'; | ||
import { | ||
saveEncryptedAccountInfo, | ||
readEncryptedAccountInfo | ||
} from '../utils/storage'; | ||
|
||
import routes from '../constants/routes.json'; | ||
|
||
export type SetKeysAction = { type: 'SET_KEYS' } & AccountKeys; | ||
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, | ||
unlockWalletPassword: string | ||
): Thunk<SetKeysAction> { | ||
return function setAccountThunk(dispatch) { | ||
return getAccountFromPrivateKey(privateKey).then( | ||
curry(initializeKeysAndRedirect)(dispatch) | ||
return getAccountFromPrivateKey(privateKey).then(keys => | ||
curry(initializeKeysAndRedirect)(dispatch, keys, unlockWalletPassword) | ||
); | ||
}; | ||
} | ||
|
||
export function setAccountFromPrivateKey(privateKey: string): Thunk { | ||
return function setAccountFromPrivateKeyThunk(dispatch) { | ||
getAccountFromPrivateKey(privateKey) | ||
.then(loadedPrivateKey => { | ||
dispatch({ | ||
type: SET_KEYS, | ||
...loadedPrivateKey | ||
}); | ||
return dispatch(updateAccountTransactionsAndState()); | ||
}) | ||
.catch(error => { | ||
console.error(error); | ||
throw new Error(PRIVATE_KEY_ERROR); | ||
}); | ||
}; | ||
} | ||
|
||
export function updateAccountTransactionsAndState(): Thunk { | ||
return function updateAccountTransactionsAndStateThunk(dispatch) { | ||
return Promise.all([ | ||
dispatch(updateAccountTransactions()), | ||
dispatch(updateNodeSettings()), | ||
dispatch(updateAccountState()) | ||
]).then(() => dispatch(push(routes.WALLET))); | ||
export function setKeysWithUnlockWalletPassword( | ||
unlockWalletPassword: string = '' | ||
): Thunk<SetKeysAction> { | ||
return function setKeysWithUnlockWalletPasswordThunk(dispatch) { | ||
const accountKeys = readEncryptedAccountInfo(unlockWalletPassword); | ||
if (accountKeys) { | ||
return getAccountFromPrivateKey(accountKeys.privateKey).then(keys => | ||
curry(initializeKeysAndRedirect)(dispatch, keys, unlockWalletPassword) | ||
); | ||
} | ||
throw new Error('Invalid password'); | ||
}; | ||
} | ||
|
||
|
@@ -83,30 +74,36 @@ export function updateAccountTransactionsAndState(): Thunk { | |
* @param {boolean} saveAccount If true, the keys are stored in the local storage | ||
*/ | ||
const initializeKeysAndRedirect = ( | ||
dispatch: Dispatch<SetKeysAction>, | ||
dispatch: Dispatch, | ||
keys: AccountKeys, | ||
saveAccount?: boolean = true | ||
unlockWalletPassword: string = '' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this hints the parameter is optional when it's not |
||
) => { | ||
dispatch({ | ||
type: SET_KEYS, | ||
...keys | ||
}); | ||
if (saveAccount) { | ||
saveAccountInfoInLocalStorage(keys); | ||
} | ||
|
||
return dispatch(updateAccountTransactionsAndState()); | ||
saveEncryptedAccountInfo(unlockWalletPassword, keys); | ||
|
||
return Promise.all([ | ||
dispatch(updateAccountTransactions()), | ||
dispatch(updateNodeSettings()), | ||
dispatch(updateAccountState()) | ||
]) | ||
.catch(console.error) | ||
.then(() => dispatch(push(routes.WALLET))); | ||
}; | ||
|
||
export function setAccountFromMnemonic( | ||
mnemonicPhrase: string, | ||
mnemonicPassword?: string | ||
) { | ||
mnemonicPassword: string, | ||
jpcapurro-atixlabs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unlockWalletPassword: string | ||
): Thunk<SetKeysAction> { | ||
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, unlockWalletPassword) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either go with:
or:
or change the function signature to receive the keys last, and do:
It's confusing to curry a function and execute it right away. It might lead a reader to infer the function receives even more parameters and'll be executed later on. |
||
); | ||
}; | ||
} | ||
|
@@ -119,7 +116,7 @@ export type SetAccountStateAction = { | |
} & AccountState; | ||
export const SET_ACCOUNT_STATE = 'SET_ACCOUNT_STATE'; | ||
|
||
export function updateAccountState(): Thunk { | ||
export function updateAccountState(): Thunk<SetAccountStateAction> { | ||
return function updateAccountStateThunk(dispatch, getState) { | ||
const { identifier }: { identifier: Identifier } = getState().account; | ||
if (!identifier) { | ||
|
@@ -139,7 +136,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(); | ||
}) | ||
); | ||
}; | ||
|
@@ -151,7 +148,7 @@ export type SetTransactionsAction = { | |
}; | ||
export const SET_TRANSACTIONS = 'SET_TRANSACTIONS'; | ||
|
||
export function updateAccountTransactions(): Thunk { | ||
export function updateAccountTransactions(): Thunk<SetAccountStateAction> { | ||
return function updateAccountTransactionsThunk(dispatch, getState) { | ||
const { address }: { address: Address } = getState().account; | ||
if (!address) { | ||
|
@@ -171,7 +168,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(); | ||
}) | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// @flow | ||
import React, { useState } from 'react'; | ||
import Form from 'react-bootstrap/Form'; | ||
import PropTypes from 'prop-types'; | ||
|
||
CreateUnlockWalletPassword.propTypes = { | ||
setValidCreateUnlockWalletPassword: PropTypes.func | ||
}; | ||
export default function CreateUnlockWalletPassword({ | ||
setValidCreateUnlockWalletPassword | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on the called component, the function to be called on a change or other event should be called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here, it should be called |
||
}) { | ||
const checkValidUnlockWalletPassword = function checkValidUnlockWalletPassword( | ||
pass | ||
) { | ||
setIsValidPassword(true); | ||
if (!pass) { | ||
setIsValidPassword(false); | ||
return false; | ||
} | ||
if (pass.length < 8) { | ||
setIsValidPassword(false); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is the return value of this function ever used? |
||
} | ||
}; | ||
|
||
const checkValidUnlockWalletConfirmation = function checkValidUnlockWalletConfirmation( | ||
unlockWalletPassword, | ||
confirmation | ||
) { | ||
setArePasswordAndConfirmationEqual(true); | ||
let isValidUnlockPassword = true; | ||
if (!confirmation || confirmation.length < 8) { | ||
isValidUnlockPassword = false; | ||
} | ||
|
||
if (unlockWalletPassword !== confirmation) { | ||
setArePasswordAndConfirmationEqual(false); | ||
isValidUnlockPassword = false; | ||
} | ||
|
||
setValidCreateUnlockWalletPassword( | ||
unlockWalletPassword, | ||
isValidUnlockPassword | ||
); | ||
return isValidUnlockPassword; | ||
}; | ||
|
||
const [ | ||
arePasswordAndConfirmationEqual, | ||
setArePasswordAndConfirmationEqual | ||
] = useState(true); | ||
|
||
const [password, setPassword] = useState(''); | ||
const [confirmPassword, setConfirmPassword] = useState(''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all this variables and hooks could be typed via flow |
||
const [isValidPassword, setIsValidPassword] = useState(true); | ||
|
||
return ( | ||
<Form.Group> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why nest the form groups? there should be one group per input AFAIK: https://react-bootstrap.netlify.com/components/forms/ |
||
<Form.Label className="mt-5">Unlock wallet:</Form.Label> | ||
<Form.Group> | ||
<Form.Control | ||
type="password" | ||
id="password" | ||
name="password" | ||
placeholder="New password (min 8 chars)" | ||
value={password} | ||
isInvalid={!isValidPassword} | ||
onChange={event => setPassword(event.target.value)} | ||
onBlur={() => checkValidUnlockWalletPassword(password)} | ||
/> | ||
<Form.Control.Feedback type="invalid"> | ||
The password must have at least 8 chars. | ||
</Form.Control.Feedback> | ||
<Form.Control | ||
type="password" | ||
name="confirmPassword" | ||
id="confirmPassword" | ||
placeholder="Confirm password" | ||
value={confirmPassword} | ||
isInvalid={!arePasswordAndConfirmationEqual} | ||
onChange={event => setConfirmPassword(event.target.value)} | ||
onBlur={() => | ||
checkValidUnlockWalletConfirmation(password, confirmPassword) | ||
} | ||
className="mt-3" | ||
/> | ||
<Form.Text> | ||
This key allows you to unlock your wallet every time you start it and | ||
to keep your account data in a more secure way. | ||
</Form.Text> | ||
<Form.Control.Feedback type="invalid"> | ||
password and confirmation must be the same. | ||
</Form.Control.Feedback> | ||
</Form.Group> | ||
</Form.Group> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessary to generate the AccountKeys object again, since the AccountKeys object is stored already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this wasn't addressed yet