From 411b388aa63b95cbb0989b77e88d888842f02152 Mon Sep 17 00:00:00 2001 From: Matt Huggins Date: Mon, 12 Mar 2018 22:52:21 -0500 Subject: [PATCH 1/6] Fixed private key login to only permit private key or WIF (#854) --- __mocks__/neon-js.js | 5 +- __tests__/actions/authActions.test.js | 132 ++++++++++++++++++++++++++ app/actions/authActions.js | 8 +- 3 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 __tests__/actions/authActions.test.js diff --git a/__mocks__/neon-js.js b/__mocks__/neon-js.js index 89201baff..6fb3d64a4 100644 --- a/__mocks__/neon-js.js +++ b/__mocks__/neon-js.js @@ -87,7 +87,10 @@ neonjs.wallet = { Account: jest.fn(() => { return { address } }), getVerificationScriptFromPublicKey: jest.fn(() => scriptHash), isAddress: jest.fn(() => true), - isNEP2: jest.fn(() => true) + isNEP2: jest.fn(() => true), + isWIF: jest.fn(() => false), + isPrivateKey: jest.fn(() => false), + isPublicKey: jest.fn(() => false) } module.exports = neonjs diff --git a/__tests__/actions/authActions.test.js b/__tests__/actions/authActions.test.js new file mode 100644 index 000000000..eb3d297e4 --- /dev/null +++ b/__tests__/actions/authActions.test.js @@ -0,0 +1,132 @@ +import { wallet } from 'neon-js' + +import { wifLoginActions } from '../../app/actions/authActions' + +describe('authActions', () => { + describe('wifLoginActions', () => { + describe('request', () => { + const wif = 'KxB52D1FGe5xBn6YeezNwj7grhkHZxq7bv2tmaCPoT4rxApMwMvU' + const address = 'ASJQLBnhAs6fSgBv2R7KtRZjC8A9fAmcNW' + const privateKey = '1c7a992d0e68b7b23cb430ba596bd68cecde042410d81e9e95ee19dc1bcd739d' + + test('returns an action object', () => { + expect(wifLoginActions.request({ wif })).toEqual({ + batch: false, + type: 'AUTH/REQ/REQUEST', + meta: { + id: 'AUTH', + type: 'REQ/REQUEST' + }, + payload: { + fn: expect.any(Function) + } + }) + }) + + describe('with valid WIF', () => { + let wifMock, keyMock, accountMock + + beforeEach(() => { + wifMock = jest.spyOn(wallet, 'isWIF').mockImplementation((str) => false) + keyMock = jest.spyOn(wallet, 'isPrivateKey').mockImplementation((str) => true) + accountMock = jest.spyOn(wallet, 'Account').mockImplementation((str) => ({ WIF: wif, address })) + }) + + afterEach(() => { + wifMock.mockRestore() + keyMock.mockRestore() + accountMock.mockRestore() + }) + + test('returns authenticated account data', () => { + const request = wifLoginActions.request({ wif }) + expect(request.payload.fn({})).toEqual({ wif, address, isHardwareLogin: false }) + }) + }) + + describe('with valid private key', () => { + let wifMock, keyMock, accountMock + + beforeEach(() => { + wifMock = jest.spyOn(wallet, 'isWIF').mockImplementation((str) => false) + keyMock = jest.spyOn(wallet, 'isPrivateKey').mockImplementation((str) => true) + accountMock = jest.spyOn(wallet, 'Account').mockImplementation((str) => ({ WIF: wif, address })) + }) + + afterEach(() => { + wifMock.mockRestore() + keyMock.mockRestore() + accountMock.mockRestore() + }) + + test('returns authenticated account data', () => { + const request = wifLoginActions.request({ wif: privateKey }) + expect(request.payload.fn({})).toEqual({ wif, address, isHardwareLogin: false }) + }) + }) + + describe('with invalid private key', () => { + let wifMock, keyMock + + beforeEach(() => { + wifMock = jest.spyOn(wallet, 'isWIF').mockImplementation((str) => false) + keyMock = jest.spyOn(wallet, 'isPrivateKey').mockImplementation((str) => false) + }) + + afterEach(() => { + wifMock.mockRestore() + keyMock.mockRestore() + }) + + test('throws an error', () => { + const request = wifLoginActions.request({ wif }) + expect(() => request.payload.fn({})).toThrowError('That is not a valid private key') + }) + }) + }) + + describe('retry', () => { + const wif = 'KxB52D1FGe5xBn6YeezNwj7grhkHZxq7bv2tmaCPoT4rxApMwMvU' + + test('returns an action object', () => { + expect(wifLoginActions.retry({ wif })).toEqual({ + batch: false, + type: 'AUTH/REQ/RETRY', + meta: { + id: 'AUTH', + type: 'REQ/RETRY' + }, + payload: { + fn: expect.any(Function) + } + }) + }) + }) + + describe('cancel', () => { + test('returns an action object', () => { + expect(wifLoginActions.cancel()).toEqual({ + batch: false, + type: 'AUTH/REQ/CANCEL', + meta: { + id: 'AUTH', + type: 'REQ/CANCEL' + } + }) + }) + }) + + describe('reset', () => { + test('returns an action object', () => { + expect(wifLoginActions.reset()).toEqual({ + batch: false, + type: 'AUTH/REQ/RESET', + meta: { + id: 'AUTH', + type: 'REQ/RESET' + } + }) + }) + }) + }) +}) diff --git a/app/actions/authActions.js b/app/actions/authActions.js index 820255529..69d97ff4f 100644 --- a/app/actions/authActions.js +++ b/app/actions/authActions.js @@ -32,9 +32,13 @@ type AccountType = ?{ export const ID = 'AUTH' export const wifLoginActions = createRequestActions(ID, ({ wif }: WifLoginProps) => (state: Object): AccountType => { + if (!wallet.isWIF(wif) && !wallet.isPrivateKey(wif)) { + throw new Error('That is not a valid private key') + } + const account = new wallet.Account(wif) - return { wif, address: account.address, isHardwareLogin: false } + return { wif: account.WIF, address: account.address, isHardwareLogin: false } }) export const nep2LoginActions = createRequestActions(ID, ({ passphrase, encryptedWIF }: Nep2LoginProps) => async (state: Object): Promise => { @@ -51,7 +55,7 @@ export const nep2LoginActions = createRequestActions(ID, ({ passphrase, encrypte await upgradeNEP6AddAddresses(encryptedWIF, wif) - return { wif, address: account.address, isHardwareLogin: false } + return { wif: account.WIF, address: account.address, isHardwareLogin: false } }) export const ledgerLoginActions = createRequestActions(ID, ({ publicKey }: LedgerLoginProps) => (state: Object): AccountType => { From dceab62e54161c0e541c89efb032daad01610ca2 Mon Sep 17 00:00:00 2001 From: Matt Huggins Date: Tue, 13 Mar 2018 20:40:53 -0500 Subject: [PATCH 2/6] Fixed balance calculation for non-USD currencies (#857) --- __tests__/components/App.test.js | 14 +++++++++++++- app/actions/appActions.js | 2 -- app/containers/App/index.js | 13 +++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/__tests__/components/App.test.js b/__tests__/components/App.test.js index 4700687a6..dc8b46fcd 100644 --- a/__tests__/components/App.test.js +++ b/__tests__/components/App.test.js @@ -14,7 +14,19 @@ const initialState = { api: { APP: { batch: true, - mapping: ['NETWORK', 'PRICES', 'SETTINGS'] + mapping: ['ACCOUNTS', 'BLOCK_HEIGHT', 'SETTINGS'] + }, + ACCOUNTS: { + batch: false, + state: LOADED, + data: [], + loadedCount: 1 + }, + BLOCK_HEIGHT: { + batch: false, + state: LOADED, + data: 2000000, + loadedCount: 1 }, NETWORK: { batch: false, diff --git a/app/actions/appActions.js b/app/actions/appActions.js index b283d4c2f..49ad31034 100644 --- a/app/actions/appActions.js +++ b/app/actions/appActions.js @@ -2,7 +2,6 @@ import createBatchActions from '../util/api/createBatchActions' import accountsActions from './accountsActions' import blockHeightActions from './blockHeightActions' -import pricesActions from './pricesActions' import settingsActions from './settingsActions' export const ID = 'APP' @@ -10,6 +9,5 @@ export const ID = 'APP' export default createBatchActions(ID, { accounts: accountsActions, blockHeight: blockHeightActions, - prices: pricesActions, settings: settingsActions }) diff --git a/app/containers/App/index.js b/app/containers/App/index.js index 20fee1934..4c523ff7a 100644 --- a/app/containers/App/index.js +++ b/app/containers/App/index.js @@ -7,12 +7,14 @@ import appActions from '../../actions/appActions' import authActions from '../../actions/authActions' import accountActions from '../../actions/accountActions' import networkActions from '../../actions/networkActions' +import pricesActions from '../../actions/pricesActions' import withFetch from '../../hocs/api/withFetch' import withReload from '../../hocs/api/withReload' import withProgressComponents from '../../hocs/api/withProgressComponents' import withLoginRedirect from '../../hocs/auth/withLoginRedirect' import withLogoutRedirect from '../../hocs/auth/withLogoutRedirect' import withLogoutReset from '../../hocs/auth/withLogoutReset' +import withCurrencyData from '../../hocs/withCurrencyData' import withNetworkData from '../../hocs/withNetworkData' import alreadyLoaded from '../../hocs/api/progressStrategies/alreadyLoadedStrategy' import { checkVersion } from '../../modules/metadata' @@ -55,6 +57,17 @@ export default compose( strategy: alreadyLoaded }), + // Fetch prices data based based upon the selected currency. Reload data with the currency changes. + withCurrencyData(), + withFetch(pricesActions), + withReload(pricesActions, ['currency']), + withProgressComponents(pricesActions, { + [LOADING]: Loading, + [FAILED]: Failed + }, { + strategy: alreadyLoaded + }), + // Navigate to the home or dashboard when the user logs in or out. withLoginRedirect, withLogoutRedirect, From 3d143e876128d3f437027b3b6e88d200f3a2731b Mon Sep 17 00:00:00 2001 From: Yak Jun Xiang Date: Tue, 13 Mar 2018 19:05:53 -0700 Subject: [PATCH 3/6] Upgrade neon-js to 3.3.3 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 51dc83426..91b1e15e6 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "history": "4.7.2", "isomorphic-fetch": "2.2.1", "lodash": "4.17.4", - "neon-js": "git+https://github.com/cityofzion/neon-js.git#3.3.1", + "neon-js": "git+https://github.com/cityofzion/neon-js.git#3.3.3", "qrcode": "0.9.0", "raf": "3.4.0", "react": "16.1.1", diff --git a/yarn.lock b/yarn.lock index e79be818c..97d3d9f69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6555,9 +6555,9 @@ neo-async@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" -"neon-js@git+https://github.com/cityofzion/neon-js.git#3.3.1": - version "3.3.1" - resolved "git+https://github.com/cityofzion/neon-js.git#292f07b39a056b12497192e8af350338a8936e8c" +"neon-js@git+https://github.com/cityofzion/neon-js.git#3.3.3": + version "3.3.3" + resolved "git+https://github.com/cityofzion/neon-js.git#f962c255ef0790e4d64af3c825f3c2f44a9627f6" dependencies: axios "^0.17.1" bignumber.js "^5.0.0" From 2cdd25b13846dfbabc8883a82c97016149e31776 Mon Sep 17 00:00:00 2001 From: Matt Huggins Date: Tue, 13 Mar 2018 21:44:13 -0500 Subject: [PATCH 4/6] Added Concierge token scripthash (#859) --- app/core/constants.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/core/constants.js b/app/core/constants.js index ffbf7d86a..f86b912fd 100644 --- a/app/core/constants.js +++ b/app/core/constants.js @@ -76,7 +76,8 @@ export const TOKENS = { NRVE: 'a721d5893480260bd28ca1f395f2c465d0b5b1c2', IAM: '891daf0e1750a1031ebe23030828ad7781d874d6', ONT: 'ceab719b8baa2310f232ee0d277c061704541cfb', - THOR: '67a5086bac196b67d5fd20745b0dc9db4d2930ed' + THOR: '67a5086bac196b67d5fd20745b0dc9db4d2930ed', + CGE: '34579e4614ac1a7bd295372d3de8621770c76cdc' } export const ENDED_ICO_TOKENS = ['DBC', 'RPX', 'QLC', 'RHT'] From 6e4a45cd340284bcef3d5a53dc3710ea9014af8d Mon Sep 17 00:00:00 2001 From: Matt Huggins Date: Tue, 13 Mar 2018 22:44:09 -0500 Subject: [PATCH 5/6] Fixed repeat error notifications (#865) --- __tests__/sagas/requestSaga.test.js | 4 +++- app/hocs/withFailureNotification.js | 2 +- app/sagas/requestSaga.js | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/__tests__/sagas/requestSaga.test.js b/__tests__/sagas/requestSaga.test.js index 360ddc881..959f1089a 100644 --- a/__tests__/sagas/requestSaga.test.js +++ b/__tests__/sagas/requestSaga.test.js @@ -52,7 +52,9 @@ describe('requestSaga', () => { it('performs the action', () => { const request = call(requestState.fn, storeState, actionState.payload, sagaActions) - const response = request.CALL.fn(...request.CALL.args).next().value + const fn = request.CALL.fn(...request.CALL.args) + fn.next() // account for delay + const response = fn.next().value const result = response.CALL.fn(...response.CALL.args) expect(result).toEqual('my foo baz') }) diff --git a/app/hocs/withFailureNotification.js b/app/hocs/withFailureNotification.js index 50ef1463c..fbaa16f24 100644 --- a/app/hocs/withFailureNotification.js +++ b/app/hocs/withFailureNotification.js @@ -42,7 +42,7 @@ export default function withFailureNotification (actions: Actions, message: Mess class ErrorNotifier extends React.Component { componentWillReceiveProps (nextProps) { - if (hasError(nextProps) && (!hasError(this.props) || progressChangedToError(this.props, nextProps))) { + if (hasError(nextProps) && progressChangedToError(this.props, nextProps)) { const showErrorNotification = nextProps[NOTIFICATION_PROP] showErrorNotification({ message: nextProps[ERROR_PROP] }) } diff --git a/app/sagas/requestSaga.js b/app/sagas/requestSaga.js index 4edcabd47..965cbeb01 100644 --- a/app/sagas/requestSaga.js +++ b/app/sagas/requestSaga.js @@ -1,6 +1,6 @@ // @flow import { call, put, race, take } from 'redux-saga/effects' -import { type Saga } from 'redux-saga' +import { delay, type Saga } from 'redux-saga' import { actionMatcher } from '../util/api/matchers' import { @@ -24,6 +24,7 @@ type SagaActions = { export function createSagaActions (meta: ActionMeta): SagaActions { function * request (state: Object, payload: Payload, actions: SagaActions) { try { + yield delay(0) // allow request state to propagate const result = yield call(payload.fn, state) yield put(actions.success(result)) } catch (err) { From 72ca9741b39be06ea45686b1104dfcc3fbf23b17 Mon Sep 17 00:00:00 2001 From: Matt Huggins Date: Tue, 13 Mar 2018 22:49:43 -0500 Subject: [PATCH 6/6] Updated version to 0.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91b1e15e6..65781ed03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Neon", - "version": "0.2.1", + "version": "0.2.2", "main": "./main.js", "description": "Light wallet for NEO blockchain", "homepage": "https://github.com/CityOfZion/neon-wallet",