Skip to content

Commit

Permalink
Merge pull request #493 from Bit-Nation/feature/accounts-password-rec…
Browse files Browse the repository at this point in the history
…overy

[accounts] Password recovery - issue 480
  • Loading branch information
seland committed Aug 31, 2018
2 parents de34a67 + 73e0ea3 commit af54da6
Show file tree
Hide file tree
Showing 20 changed files with 267 additions and 20 deletions.
3 changes: 3 additions & 0 deletions __tests__/src/sagas/accounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
checkPinCodeSaga,
listenForDatabaseUpdates,
loginActionHandler,
validateMnemonicWithAccountActionHandler,
logout,
saveEditingAccount,
saveCreatingAccount,
Expand All @@ -21,6 +22,7 @@ import {
CHECK_PASSWORD,
CHECK_PIN_CODE,
LOGIN,
VALIDATE_MNEMONIC_WITH_ACCOUNT,
LOGOUT, MNEMONIC_CONFIRMED,
SAVE_CREATING_ACCOUNT,
SAVE_PASSWORD,
Expand All @@ -37,6 +39,7 @@ test('rootSaga', () => {
takeEvery(START_ACCOUNT_CREATION, startAccountCreation),
takeEvery(START_RESTORE_ACCOUNT_USING_MNEMONIC, startRestoreAccountUsingMnemonic),
takeEvery(LOGIN, loginActionHandler),
takeEvery(VALIDATE_MNEMONIC_WITH_ACCOUNT, validateMnemonicWithAccountActionHandler),
takeEvery(LOGOUT, logout),
takeEvery(SAVE_EDITING_ACCOUNT, saveEditingAccount),
takeEvery(ACCOUNTS_LIST_UPDATED, updateSignedProfile),
Expand Down
8 changes: 8 additions & 0 deletions __tests__/src/sagas/accounts/sagas.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
saveEditingAccount as saveEditingAccountSaga, savePasswordSaga, savePinCodeSaga, startAccountCreation,
startRestoreAccountUsingMnemonic, saveCreatingAccount as saveCreatingAccountSaga, currentAccountBasedUpdate,
startAccountUpdateListening, saveMnemonicConfirmed, getAccounts,
validateMnemonicWithAccount as validateMnemonicWithAccountSaga, validateMnemonicWithAccountActionHandler,
} from '../../../../src/sagas/accounts/sagas';
import defaultDB, { buildRandomPathDatabase } from '../../../../src/services/database';
import { convertFromDatabase, convertToDatabase } from '../../../../src/utils/mapping/account';
Expand All @@ -20,6 +21,7 @@ import {
accountListUpdated, changeCreatingAccountField, checkPassword, checkPinCode, CURRENT_ACCOUNT_ID_CHANGED,
currentAccountIdChanged, login,
loginTaskUpdated, mnemonicConfirmed, PERFORM_DEFERRED_LOGIN, saveCreatingAccount, savePassword, savePinCode,
validateMnemonicWithAccount,
} from '../../../../src/actions/accounts';
import TaskBuilder from '../../../../src/utils/asyncTask';
import AccountsService from '../../../../src/services/accounts';
Expand Down Expand Up @@ -206,6 +208,12 @@ test('loginActionHandler', () => {
expect(loginActionHandler(actionMock).next().value).toEqual(call(loginSaga, { accountId: 'ID' }, 'PASSWORD', false));
});

test('validateMnemonicWithAccountActionHandler', () => {
const mockCallback = jest.fn();
const actionMock = validateMnemonicWithAccount('ID', mockCallback);
expect(validateMnemonicWithAccountActionHandler(actionMock).next().value).toEqual(call(validateMnemonicWithAccountSaga, { accountId: 'ID' }, mockCallback));
});

describe('login', () => {
test('login to existing account using account id', () => {
const gen = cloneableGenerator(loginSaga)({ accountId: 'ID' }, 'PASSWORD');
Expand Down
4 changes: 4 additions & 0 deletions __tests__/src/screens/Key/Enter/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ ShallowWrapper {
"props": Object {
"changeMnemonic": [Function],
"enteredMnemonic": null,
"isOnResetPassProcess": false,
"isVerification": false,
"mnemonicConfirmed": [Function],
"mnemonicValid": null,
Expand Down Expand Up @@ -77,6 +78,7 @@ ShallowWrapper {
},
"testingModeActive": false,
"validateMnemonic": [Function],
"validateMnemonicWithAccount": [Function],
},
"ref": null,
"rendered": null,
Expand All @@ -90,6 +92,7 @@ ShallowWrapper {
"props": Object {
"changeMnemonic": [Function],
"enteredMnemonic": null,
"isOnResetPassProcess": false,
"isVerification": false,
"mnemonicConfirmed": [Function],
"mnemonicValid": null,
Expand Down Expand Up @@ -129,6 +132,7 @@ ShallowWrapper {
},
"testingModeActive": false,
"validateMnemonic": [Function],
"validateMnemonicWithAccount": [Function],
},
"ref": null,
"rendered": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ShallowWrapper {
onCancel={[MockFunction]}
onSubmit={[MockFunction]}
shouldShowCancel={false}
shouldShowForget={false}
store={
Object {
"clearActions": [Function],
Expand Down Expand Up @@ -109,6 +110,7 @@ ShallowWrapper {
title="OK"
/>
</View>,
false,
],
"style": Object {
"alignContent": "flex-start",
Expand Down Expand Up @@ -214,6 +216,7 @@ ShallowWrapper {
},
"type": [Function],
},
false,
],
"type": [Function],
},
Expand Down Expand Up @@ -281,6 +284,7 @@ ShallowWrapper {
title="OK"
/>
</View>,
false,
],
"style": Object {
"alignContent": "flex-start",
Expand Down Expand Up @@ -386,6 +390,7 @@ ShallowWrapper {
},
"type": [Function],
},
false,
],
"type": [Function],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ShallowWrapper {
onCancel={[MockFunction]}
onSubmit={[MockFunction]}
shouldShowCancel={false}
shouldShowForget={false}
store={
Object {
"clearActions": [Function],
Expand Down Expand Up @@ -109,6 +110,7 @@ ShallowWrapper {
title="OK"
/>
</View>,
false,
],
"style": Object {
"alignContent": "flex-start",
Expand Down Expand Up @@ -214,6 +216,7 @@ ShallowWrapper {
},
"type": [Function],
},
false,
],
"type": [Function],
},
Expand Down Expand Up @@ -281,6 +284,7 @@ ShallowWrapper {
title="OK"
/>
</View>,
false,
],
"style": Object {
"alignContent": "flex-start",
Expand Down Expand Up @@ -386,6 +390,7 @@ ShallowWrapper {
},
"type": [Function],
},
false,
],
"type": [Function],
},
Expand Down
16 changes: 16 additions & 0 deletions src/actions/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Mnemonic } from '../types/Mnemonic';
export type AccountsListUpdatedAction = { +type: 'ACCOUNTS_LIST_UPDATED', accounts: Array<Account> };
export type CurrentAccountIdChangedAction = { +type: 'CURRENT_ACCOUNT_ID_CHANGED', currentAccountId: string | null };
export type LoginAction = { +type: 'LOGIN', accountId: string, password: string, deferred: boolean };
export type ValidateMnemonicWithAccountAction = { +type: 'VALIDATE_MNEMONIC_WITH_ACCOUNT', +accountId: string, +callback: (success: boolean) => void };
export type PerformDeferredLoginAction = { +type: 'PERFORM_DEFERRED_LOGIN' };
export type LoginTaskUpdatedAction = { +type: 'LOGIN_TASK_UPDATED', loginTask: AsyncTask<void> };
export type LogoutAction = { +type: 'LOGOUT' };
Expand Down Expand Up @@ -40,6 +41,7 @@ export type Action =
export const ACCOUNTS_LIST_UPDATED = 'ACCOUNTS_LIST_UPDATED';
export const CURRENT_ACCOUNT_ID_CHANGED = 'CURRENT_ACCOUNT_ID_CHANGED';
export const LOGIN = 'LOGIN';
export const VALIDATE_MNEMONIC_WITH_ACCOUNT = 'VALIDATE_MNEMONIC_WITH_ACCOUNT';
export const LOGIN_TASK_UPDATED = 'LOGIN_TASK_UPDATED';
export const LOGOUT = 'LOGOUT';
export const START_ACCOUNT_CREATION = 'START_ACCOUNT_CREATION';
Expand Down Expand Up @@ -95,6 +97,20 @@ export function login(accountId: string, password: string, deferred: boolean = f
};
}

/**
* @desc Action creator for an action that is called to perform a validate account with mnemonic.
* @param {string} accountId Id of account to login to.
* @param {function} callback Callback that is called with true if check is successful and false otherwise.
* @return {ValidateMnemonicWithAccountAction} An action.
*/
export function validateMnemonicWithAccount(accountId: string, callback: (boolean) => void): ValidateMnemonicWithAccountAction {
return {
type: VALIDATE_MNEMONIC_WITH_ACCOUNT,
accountId,
callback,
};
}

/**
* @desc Action creator for an action to perform login that was deferred. That is used to not store password while creating account.
* @return {PerformDeferredLoginAction} An action.
Expand Down
5 changes: 5 additions & 0 deletions src/global/Styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,11 @@ const styles = {
textAlign: 'center',
},

forgetButton: {
backgroundColor: 'transparent', alignSelf: 'stretch', borderRadius: 0,
},
forgetButtonText: { color: '#58595B', fontSize: 15, fontWeight: 'bold' },

};

export default styles;
13 changes: 12 additions & 1 deletion src/global/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
"subtitle": "",
"confirm": "$t(common.ok)"
},
"passwordRecoveryFailed": {
"title": "Entered private key does not match the account.",
"subtitle": "",
"confirm": "$t(common.ok)"
},
"walletRequired": {
"title": "No wallet",
"subtitle": "You need to setup a wallet first",
Expand Down Expand Up @@ -204,6 +209,11 @@
"title": "Success",
"subtitle": "Your transaction has been successfully sent",
"confirm": "$t(common.ok)"
},
"successResetPassword": {
"title": "Success",
"subtitle": "Your password has been successfully changed",
"confirm": "$t(common.ok)"
}
},
"screens": {
Expand Down Expand Up @@ -543,7 +553,8 @@
"password": {
"enterInstruction": "Enter password:",
"createInstruction": "Enter new password:",
"verifyInstruction": "Enter password again:"
"verifyInstruction": "Enter password again:",
"forgetInstruction": "Forgot Password?"
},
"confirmTransaction": {
"title": "Confirm Transaction",
Expand Down
3 changes: 3 additions & 0 deletions src/sagas/accounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { all, call, takeEvery } from 'redux-saga/effects';
import {
listenForDatabaseUpdates,
loginActionHandler,
validateMnemonicWithAccountActionHandler,
logout,
checkPasswordSaga,
checkPinCodeSaga,
Expand All @@ -14,6 +15,7 @@ import {
} from './sagas';
import {
LOGIN,
VALIDATE_MNEMONIC_WITH_ACCOUNT,
LOGOUT,
CHECK_PASSWORD,
CHECK_PIN_CODE,
Expand All @@ -35,6 +37,7 @@ export default function* rootSaga() {
takeEvery(START_ACCOUNT_CREATION, startAccountCreation),
takeEvery(START_RESTORE_ACCOUNT_USING_MNEMONIC, startRestoreAccountUsingMnemonic),
takeEvery(LOGIN, loginActionHandler),
takeEvery(VALIDATE_MNEMONIC_WITH_ACCOUNT, validateMnemonicWithAccountActionHandler),
takeEvery(LOGOUT, logout),
takeEvery(SAVE_EDITING_ACCOUNT, saveEditingAccount),
takeEvery(ACCOUNTS_LIST_UPDATED, updateSignedProfile),
Expand Down
32 changes: 31 additions & 1 deletion src/sagas/accounts/sagas.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
CheckPasswordAction,
CheckPinCodeAction,
LoginAction, MnemonicConfirmedAction,
ValidateMnemonicWithAccountAction,
SaveCreatingAccountAction,
SavePasswordAction,
SavePinCodeAction,
Expand Down Expand Up @@ -155,7 +156,6 @@ export function* updateSignedProfile(): Generator<*, *, *> {
}
}


/**
* @desc Performs preparation for account creation, e.g. clean settings.
* @return {void}
Expand Down Expand Up @@ -238,6 +238,36 @@ export function* login(userInfo: ({ accountId: string, accountStore?: string }),
yield put(fetchAllChats());
}

/**
* @desc Valid mnemonic with account choice to login
* @param {ValidateMnemonicWithAccountAction} action An action
* @return {void}
*/
export function* validateMnemonicWithAccountActionHandler(action: ValidateMnemonicWithAccountAction): Generator<*, *, *> {
yield call(validateMnemonicWithAccount, { accountId: action.accountId }, action.callback);
}

/**
* @desc Valid mnemonic with account choice to login
* @param {*} userInfo Either object containing account id or account store to log in.
* @param {function} callback Function that is called when that information is valid mnemonic.
* @return {void}
*/
export function* validateMnemonicWithAccount(userInfo: ({ accountId: string }), callback: (success: boolean) => void): Generator<*, *, *> {
const { accountId } = userInfo;
const account: DBAccount = yield call(getAccount, accountId);
const { accountStore } = account;
const profile = retrieveProfileFromAccount(convertFromDatabase(account));
try {
const { key: { enteredMnemonic } } = yield select();
const isValid = yield call(AccountsService.validateMnemonicWithAccount, accountStore, profile, enteredMnemonic);
yield call(callback, isValid);
} catch (error) {
console.log('--> ERROR Login: ', error);
yield call(callback, false);
}
}

/**
* @desc Performs a logout.
* @return {void}
Expand Down
1 change: 1 addition & 0 deletions src/screens/Accounts/AccountAccessContainer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class AccountAccessContainer extends NavigatorComponent<Props & Actions & Accoun
...screen('ENTER_PASSCODE_SCREEN'),
passProps: {
accountId: id,
onForget: () => AccountsScreen.onForgetPasswordAccount(this.props.navigator, id),
onCancel: () => this.props.navigator.dismissModal(),
},
});
Expand Down

0 comments on commit af54da6

Please sign in to comment.