Skip to content

Commit

Permalink
handle state decrption error (#1699)
Browse files Browse the repository at this point in the history
  • Loading branch information
faboweb authored and fedekunze committed Dec 5, 2018
1 parent 988fa4f commit 2f5a9f1
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 69 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- [\#1640](https://github.com/cosmos/voyager/issues/1640) Fixed an error that prevented the search bar to be displayed using `Ctrl+F` @fedekunze
- Fixed testnet config build script @faboweb
- [\#1677](https://github.com/cosmos/voyager/issues/1677) Fixed inconstistent status colors on proposals @fedekunze
- [\#1687](https://github.com/cosmos/voyager/issues/1687) Removing cached state if decrypting fails. @faboweb
- [\#1662](https://github.com/cosmos/voyager/issues/1662) Fixed wrong node version in readme @faboweb

## [0.10.7] - 2018-10-10
Expand Down
72 changes: 41 additions & 31 deletions app/src/renderer/vuex/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import Raven from "raven-js"
Vue.use(Vuex)

export default (opts = {}) => {
// provide commit and dispatch to tests
opts.commit = (...args) => store.commit(...args)
opts.dispatch = (...args) => store.dispatch(...args)

const store = new Vuex.Store({
getters,
// strict: true,
Expand Down Expand Up @@ -98,39 +100,47 @@ function getStorageKey(state) {
}

function loadPersistedState({ state, commit }, { password }) {
const cachedState = localStorage.getItem(getStorageKey(state))
const storageKey = getStorageKey(state)
const cachedState = localStorage.getItem(storageKey)
if (cachedState) {
const bytes = CryptoJS.AES.decrypt(cachedState, password)
const plaintext = bytes.toString(CryptoJS.enc.Utf8)

// Replace the state object with the stored state
let oldState = JSON.parse(plaintext)
_.merge(state, oldState, {
// set loading indicators to false
transactions: {
loaded: true,
loading: false
},
wallet: {
loaded: true,
loading: false
},
delegates: {
loaded: true,
loading: false
},
proposals: {
loaded: true,
loading: false
}
})
this.replaceState(state)
try {
const bytes = CryptoJS.AES.decrypt(cachedState, password)
const plaintext = bytes.toString(CryptoJS.enc.Utf8)

// add all delegates the user has bond with already to the cart
state.delegates.delegates
.filter(d => state.delegation.committedDelegates[d.operator_address])
.forEach(d => {
commit(`addToCart`, d)
// Replace the state object with the stored state
let oldState = JSON.parse(plaintext)
_.merge(state, oldState, {
// set loading indicators to false
transactions: {
loaded: true,
loading: false
},
wallet: {
loaded: true,
loading: false
},
delegates: {
loaded: true,
loading: false
},
proposals: {
loaded: true,
loading: false
}
})
this.replaceState(state)

// add all delegates the user has bond with already to the cart
state.delegates.delegates
.filter(d => state.delegation.committedDelegates[d.operator_address])
.forEach(d => {
commit(`addToCart`, d)
})
} catch (error) {
console.error(`Decrypting the state failed, removing cached state.`)
Raven.captureException(error)
// if decrypting the state fails, we cleanup
localStorage.removeItem(storageKey)
}
}
}
72 changes: 34 additions & 38 deletions test/unit/specs/store/store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@ import lcdClientMock from "renderer/connectors/lcdClientMock.js"
describe(`Store`, () => {
let store

beforeEach(() => {
beforeEach(async () => {
node.queryAccount = () => new Promise(() => {}) // make balances not return
node.txs = () => new Promise(() => {}) // make txs not return
store = Store({ node })
localStorage.setItem(
`store_test-net_` + lcdClientMock.addresses[0],
undefined
)
})

// DEFAULT

it(`should persist balances et al if the user is logged in`, async () => {
jest.useFakeTimers()
await store.dispatch(`setLastHeader`, {
height: 42,
Expand All @@ -27,6 +23,11 @@ describe(`Store`, () => {
account: `default`,
password: `1234567890`
})
})

// DEFAULT

it(`should persist balances et al if the user is logged in`, async () => {
store.commit(`setWalletBalances`, [{ denom: `fabocoin`, amount: 42 }])
jest.runAllTimers() // updating is waiting if more updates coming in, this skips the waiting
expect(
Expand All @@ -35,11 +36,7 @@ describe(`Store`, () => {
})

it(`should not update cache if not logged in`, async () => {
jest.useFakeTimers()
await store.dispatch(`setLastHeader`, {
height: 42,
chain_id: `test-net`
})
await store.dispatch(`signOut`)
store.commit(`setWalletBalances`, [{ denom: `fabocoin`, amount: 42 }])
jest.runAllTimers() // updating is waiting if more updates coming in, this skips the waiting
expect(
Expand All @@ -48,15 +45,6 @@ describe(`Store`, () => {
})

it(`should restore balances et al after logging in`, async () => {
jest.useFakeTimers()
await store.dispatch(`setLastHeader`, {
height: 42,
chain_id: `test-net`
})
await store.dispatch(`signIn`, {
account: `default`,
password: `1234567890`
})
store.commit(`setWalletBalances`, [{ denom: `fabocoin`, amount: 42 }])
store.commit(`setWalletTxs`, [{}])
jest.runAllTimers() // updating is waiting if more updates coming in, this skips the waiting
Expand All @@ -71,15 +59,6 @@ describe(`Store`, () => {
})

it(`should restore delegates and put committed ones in the cart`, async () => {
jest.useFakeTimers()
await store.dispatch(`setLastHeader`, {
height: 42,
chain_id: `test-net`
})
await store.dispatch(`signIn`, {
account: `default`,
password: `1234567890`
})
store.commit(`setDelegates`, lcdClientMock.state.candidates)
store.commit(`setCommittedDelegation`, {
candidateId: lcdClientMock.validators[0],
Expand Down Expand Up @@ -120,15 +99,6 @@ describe(`Store`, () => {
})

it(`should throttle updating the store cache`, async () => {
jest.useFakeTimers()
await store.dispatch(`setLastHeader`, {
height: 42,
chain_id: `test-net`
})
await store.dispatch(`signIn`, {
account: `default`,
password: `1234567890`
})
store.commit(`setWalletBalances`, [{ denom: `fabocoin`, amount: 42 }])

// not updating yet, as it waits if there are more updates incoming
Expand All @@ -145,7 +115,6 @@ describe(`Store`, () => {

it(`should remove the cache if failing to encrypt the cache`, async () => {
jest.resetModules()
jest.useFakeTimers()
jest.doMock(`crypto-js`, () => ({
AES: {
encrypt: () => {
Expand All @@ -172,4 +141,31 @@ describe(`Store`, () => {
expect(spy).toHaveBeenCalled()
console.error.mockReset()
})

it(`should remove the cache if failing to decrypt the cache`, async () => {
jest.resetModules()
jest.useFakeTimers()
jest.doMock(`crypto-js`, () => ({
AES: {
decrypt: () => {
throw Error(`Failed to encrypt`)
}
}
}))
let Raven = require(`raven-js`)
// the error will be logged, which is confusing in the test output
jest.spyOn(console, `error`).mockImplementation(() => {})

let spy = jest.spyOn(Raven, `captureException`)
let opts = { node: { keys: { get: () => ({}) } } }
require(`renderer/vuex/store.js`).default(opts)

// the store key is store__null if neither the chain_id or the address is set
localStorage.setItem(`store__null`, `xxx`)
await opts.dispatch(`loadPersistedState`, { password: `123` })
jest.runAllTimers()

expect(spy).toHaveBeenCalled()
console.error.mockReset()
})
})

0 comments on commit 2f5a9f1

Please sign in to comment.