From a8edce5801afdd11023e1572ef391424c7d831ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Thu, 18 Jan 2018 10:49:53 +0100 Subject: [PATCH] Add IsUnlocked, and simple application reducer --- .flowconfig | 1 + src/actions/accounts.js | 2 +- src/actions/settings.js | 2 +- src/components/IsUnlocked/index.js | 100 +++++++++++++++++++++++++++++ src/components/SettingsPage.js | 13 ++-- src/components/Wrapper.js | 42 +++++++----- src/helpers/db.js | 39 +++++------ src/middlewares/db.js | 14 +--- src/reducers/application.js | 32 +++++++++ src/reducers/index.js | 4 ++ src/renderer/index.js | 8 ++- 11 files changed, 199 insertions(+), 58 deletions(-) create mode 100644 src/components/IsUnlocked/index.js create mode 100644 src/reducers/application.js diff --git a/.flowconfig b/.flowconfig index 57869f79..21130eb8 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,4 +1,5 @@ [ignore] +/node_modules/bcryptjs/src/bower.json [include] diff --git a/src/actions/accounts.js b/src/actions/accounts.js index 9789a0a4..cf68dbb1 100644 --- a/src/actions/accounts.js +++ b/src/actions/accounts.js @@ -13,5 +13,5 @@ export const addAccount: AddAccount = payload => ({ type FetchAccounts = () => { type: string } export const fetchAccounts: FetchAccounts = () => ({ type: 'FETCH_ACCOUNTS', - payload: db.accounts(), + payload: db('accounts'), }) diff --git a/src/actions/settings.js b/src/actions/settings.js index 14e6c11b..ebcf3dbd 100644 --- a/src/actions/settings.js +++ b/src/actions/settings.js @@ -13,5 +13,5 @@ export const saveSettings: SaveSettings = payload => ({ type FetchSettings = () => { type: string } export const fetchSettings: FetchSettings = () => ({ type: 'FETCH_SETTINGS', - payload: db.settings(), + payload: db('settings'), }) diff --git a/src/components/IsUnlocked/index.js b/src/components/IsUnlocked/index.js new file mode 100644 index 00000000..f0230d36 --- /dev/null +++ b/src/components/IsUnlocked/index.js @@ -0,0 +1,100 @@ +// @flow + +import React, { PureComponent } from 'react' +import { connect } from 'react-redux' +import bcrypt from 'bcryptjs' + +import type { MapStateToProps } from 'react-redux' +import type { Settings } from 'types/common' + +import get from 'lodash/get' + +import { setEncryptionKey } from 'helpers/db' + +import { fetchAccounts } from 'actions/accounts' +import { isLocked, unlock } from 'reducers/application' + +import Box from 'components/base/Box' +import Input from 'components/base/Input' + +type InputValue = { + password: string, +} + +type Props = { + fetchAccounts: Function, + isLocked: boolean, + render: Function, + settings: Settings, + unlock: Function, +} +type State = { + inputValue: InputValue, +} + +const mapStateToProps: MapStateToProps<*, *, *> = state => ({ + settings: state.settings, + isLocked: isLocked(state), +}) + +const mapDispatchToProps = { + fetchAccounts, + unlock, +} + +class IsUnlocked extends PureComponent { + state = { + inputValue: { + password: '', + }, + } + + handleChangeInput = (key: $Keys) => (value: $Values) => + this.setState(prev => ({ + inputValue: { + ...prev.inputValue, + [key]: value, + }, + })) + + handleSubmit = (e: SyntheticEvent) => { + e.preventDefault() + + const { settings, unlock, fetchAccounts } = this.props + const { inputValue } = this.state + + if (bcrypt.compareSync(inputValue.password, get(settings, 'password.value'))) { + setEncryptionKey('accounts', inputValue.password) + fetchAccounts() + unlock() + } + } + + render() { + const { inputValue } = this.state + const { isLocked, render } = this.props + + if (isLocked) { + return ( + +
+ + + +
+
+ ) + } + + return render() + } +} + +export default connect(mapStateToProps, mapDispatchToProps, null, { + pure: false, +})(IsUnlocked) diff --git a/src/components/SettingsPage.js b/src/components/SettingsPage.js index f07109c8..6bfb430a 100644 --- a/src/components/SettingsPage.js +++ b/src/components/SettingsPage.js @@ -8,6 +8,8 @@ import bcrypt from 'bcryptjs' import get from 'lodash/get' import set from 'lodash/set' +import { setEncryptionKey } from 'helpers/db' + import type { MapStateToProps } from 'react-redux' import type { Settings } from 'types/common' @@ -72,11 +74,12 @@ class SettingsPage extends PureComponent { }, } - if ( - get(inputValue, 'password.state') === true && - get(inputValue, 'password.value', '').trim() !== '' - ) { - settings.password.value = bcrypt.hashSync(get(inputValue, 'password.value'), 8) + const password = get(inputValue, 'password', {}) + + if (password.state === true && password.value.trim() !== '') { + settings.password.value = bcrypt.hashSync(password.value, 8) + + setEncryptionKey('accounts', password.value) } saveSettings(settings) diff --git a/src/components/Wrapper.js b/src/components/Wrapper.js index 85e9b81b..abcb3887 100644 --- a/src/components/Wrapper.js +++ b/src/components/Wrapper.js @@ -5,8 +5,8 @@ import { ipcRenderer } from 'electron' import { Route } from 'react-router' import { translate } from 'react-i18next' -import Box from 'components/base/Box' import * as modals from 'components/modals' +import Box from 'components/base/Box' import AccountPage from 'components/AccountPage' import DashboardPage from 'components/DashboardPage' @@ -14,6 +14,7 @@ import SettingsPage from 'components/SettingsPage' import UpdateNotifier from 'components/UpdateNotifier' import AppRegionDrag from 'components/AppRegionDrag' +import IsUnlocked from 'components/IsUnlocked' import SideBar from 'components/SideBar' import TopBar from 'components/TopBar' @@ -26,22 +27,29 @@ class Wrapper extends PureComponent<{}> { return ( - - - {Object.entries(modals).map(([name, ModalComponent]: [string, any]) => ( - - ))} - - - - - - - - - - - + + ( + + + + {Object.entries(modals).map(([name, ModalComponent]: [string, any]) => ( + + ))} + + + + + + + + + + + + + )} + /> ) } diff --git a/src/helpers/db.js b/src/helpers/db.js index d98a703a..aa0418a8 100644 --- a/src/helpers/db.js +++ b/src/helpers/db.js @@ -1,29 +1,24 @@ import Store from 'electron-store' -export default { - accounts: (accounts, options = {}) => { - const db = new Store({ - name: 'accounts', - defaults: {}, - ...options, - }) +const encryptionKey = {} - if (accounts) { - db.store = accounts - } +const store = type => + new Store({ + name: type, + defaults: {}, + encryptionKey: encryptionKey[type], + }) - return db.store - }, - settings: settings => { - const db = new Store({ - name: 'settings', - defaults: {}, - }) +export function setEncryptionKey(type, value) { + encryptionKey[type] = value +} + +export default (type, values) => { + const db = store(type) - if (settings) { - db.store = settings - } + if (values) { + db.store = values + } - return db.store - }, + return db.store } diff --git a/src/middlewares/db.js b/src/middlewares/db.js index 5f971853..b77e4e32 100644 --- a/src/middlewares/db.js +++ b/src/middlewares/db.js @@ -1,10 +1,9 @@ -import get from 'lodash/get' +/* eslint-disable consistent-return */ import db from 'helpers/db' import { getAccounts } from 'reducers/accounts' -// eslint-disable-next-line consistent-return export default store => next => action => { if (!action.type.startsWith('DB:')) { return next(action) @@ -18,13 +17,6 @@ export default store => next => action => { const state = getState() const { settings } = state - db.settings(settings) - - const optionsAccounts = {} - - if (get(settings, 'password.state') === true) { - optionsAccounts.encryptionKey = get(settings, 'password.value') - } - - db.accounts(getAccounts(state), optionsAccounts) + db('settings', settings) + db('accounts', getAccounts(state)) } diff --git a/src/reducers/application.js b/src/reducers/application.js new file mode 100644 index 00000000..8ee978cf --- /dev/null +++ b/src/reducers/application.js @@ -0,0 +1,32 @@ +// @flow + +import { handleActions, createAction } from 'redux-actions' + +import get from 'lodash/get' + +export type ApplicationState = {} + +const state: ApplicationState = {} + +const handlers = { + APPLICATION_SET_DATA: (state, { payload }: { payload: ApplicationState }) => ({ + ...state, + ...payload, + }), +} + +// Actions + +export const unlock = createAction('APPLICATION_SET_DATA', () => ({ isLocked: false })) +export const lock = createAction('APPLICATION_SET_DATA', () => ({ isLocked: true })) + +// Selectors + +export const isLocked = (state: Object) => + state.application.isLocked === undefined + ? get(state.settings, 'password.state', false) + : state.application.isLocked + +// Exporting reducer + +export default handleActions(handlers, state) diff --git a/src/reducers/index.js b/src/reducers/index.js index 4bcb1e4d..743439c1 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -5,12 +5,14 @@ import { routerReducer as router } from 'react-router-redux' import type { LocationShape } from 'react-router' +import application from './application' import accounts from './accounts' import devices from './devices' import modals from './modals' import settings from './settings' import update from './update' +import type { ApplicationState } from './application' import type { AccountsState } from './accounts' import type { DevicesState } from './devices' import type { ModalsState } from './modals' @@ -18,6 +20,7 @@ import type { SettingsState } from './settings' import type { UpdateState } from './update' export type State = { + application: ApplicationState, accounts: AccountsState, devices: DevicesState, modals: ModalsState, @@ -27,6 +30,7 @@ export type State = { } export default combineReducers({ + application, accounts, devices, modals, diff --git a/src/renderer/index.js b/src/renderer/index.js index 116f9119..16b14190 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -10,6 +10,7 @@ import events from 'renderer/events' import { fetchAccounts } from 'actions/accounts' import { fetchSettings } from 'actions/settings' +import { isLocked } from 'reducers/application' import App from 'components/App' @@ -22,7 +23,12 @@ const rootNode = document.getElementById('app') events(store) store.dispatch(fetchSettings()) -store.dispatch(fetchAccounts()) + +const state = store.getState() || {} + +if (!isLocked(state)) { + store.dispatch(fetchAccounts()) +} function r(Comp) { if (rootNode) {