diff --git a/src/components/shared/transactionSummary/footer.js b/src/components/shared/transactionSummary/footer.js index fccd4b6523..4c781c0cf4 100644 --- a/src/components/shared/transactionSummary/footer.js +++ b/src/components/shared/transactionSummary/footer.js @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { PrimaryButton, SecondaryButton, TertiaryButton } from '@toolbox/buttons'; import PassphraseInput from '@toolbox/passphraseInput'; +import useSecondPassphrase from '@src/hooks/setSecondPassphrase'; import BoxFooter from '@toolbox/box/footer'; import styles from './transactionSummary.css'; @@ -23,7 +24,7 @@ const Actions = ({ )} {isMultisignature ? t('Sign') : confirmButton.label} @@ -34,12 +35,14 @@ const Actions = ({ const SecondPassInput = ({ t, secondPassphraseStored, inputStatus, setInputStatus, }) => { - const [secondPass, set2ndPass] = useState(''); + const [secondPass, set2ndPass] = useSecondPassphrase(); useEffect(() => { - if (secondPass) { - secondPassphraseStored(secondPass); + if (secondPass.error === 0) { + secondPassphraseStored(secondPass.data); setInputStatus('valid'); + } else if (secondPass.error > 0) { + setInputStatus('invalid'); } }, [secondPass]); diff --git a/src/hooks/setSecondPassphrase.js b/src/hooks/setSecondPassphrase.js new file mode 100644 index 0000000000..9fa2cfdd98 --- /dev/null +++ b/src/hooks/setSecondPassphrase.js @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { selectActiveTokenAccount } from '@store/selectors'; +import { extractPublicKey } from '@utils/account'; + +const empty2ndPass = { + data: '', + error: -1, + feedback: [], +}; + +const setSecondPassphrase = () => { + const [secondPass, set2ndPass] = useState(empty2ndPass); + const account = useSelector(selectActiveTokenAccount); + + const validate2ndPass = (data, error) => { + const messages = []; + if (error) { + messages.push(messages); + return messages; + } + + const secondPublicKey = account.keys.mandatoryKeys + .filter(item => item !== account.summary.publicKey); + const publicKey = extractPublicKey(data); + + // compare them + if (!secondPublicKey.length || publicKey !== secondPublicKey[0]) { + messages.push('This passphrase does not belong to your account.'); + } + return messages; + }; + + const setter = (data, error) => { + const feedback = validate2ndPass(data, error); + set2ndPass({ + data, + error: data === '' ? -1 : feedback.length, + feedback, + }); + }; + + return [secondPass, setter]; +}; + +export default setSecondPassphrase; diff --git a/src/hooks/setSecondPassphrase.test.js b/src/hooks/setSecondPassphrase.test.js new file mode 100644 index 0000000000..6104ce7f03 --- /dev/null +++ b/src/hooks/setSecondPassphrase.test.js @@ -0,0 +1,41 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useSelector } from 'react-redux'; +import setSecondPassphrase from './setSecondPassphrase'; +import accounts from '../../test/constants/accounts'; + +jest.mock('@store'); + +// const mockSelector = jest.fn(); +useSelector.mockReturnValue(accounts.secondPass); + +describe('setSecondPassphrase', () => { + it('Should return second passphrase with no error message', () => { + const result = renderHook(() => setSecondPassphrase()); + const [state, setState] = result.result.current; + expect(state.error).toBe(-1); + act(() => { + setState(accounts.secondPass.secondPass); + }); + expect(result.result.current[0].error).toBe(0); + }); + + it('Should return second passphrase with relevant error message', () => { + const result = renderHook(() => setSecondPassphrase()); + const [state, setState] = result.result.current; + expect(state.error).toBe(-1); + act(() => { + setState(accounts.genesis.passphrase); + }); + expect(result.result.current[0].error).toBe(1); + }); + + it('Should return second passphrase with an external error message, if provided', () => { + const result = renderHook(() => setSecondPassphrase()); + const [state, setState] = result.result.current; + expect(state.error).toBe(-1); + act(() => { + setState('wrong_pass', 'The pass is invalid'); + }); + expect(result.result.current[0].error).toBe(1); + }); +}); diff --git a/src/store/selectors.js b/src/store/selectors.js index 5815cbcaf9..e8e2eda24a 100644 --- a/src/store/selectors.js +++ b/src/store/selectors.js @@ -7,7 +7,16 @@ const selectBTCAddress = state => (state.account.info ? state.account.info.BTC.summary.address : undefined); const selectPublicKey = state => state.account.info[state.settings.token.active].publicKey; const selectTransactions = state => state.transactions; -const selectActiveTokenAccount = state => state.account.info[state.settings.token.active]; +const selectActiveTokenAccount = (state) => { + if (!state.account.info) { + return {}; + } + return { + ...state.account.info[state.settings.token.active], + passphrase: state.passphrase, + hwInfo: state.hwInfo, + }; +}; const selectAccountBalance = state => ( state.account.info ? state.account.info[state.settings.token.active].summary.balance : undefined); const selectBookmarks = state => state.bookmarks[state.settings.token.active];