Skip to content

Commit

Permalink
[DDW-893] Implement wallet recovery phrase verification (#1565)
Browse files Browse the repository at this point in the history
* [DDW-893] UI Init

* [DDW-893] CHANGELOG

* [DDW-893] UI progress

* [DDW-893] UI - progress

* [DDW-893] Wallet Settings page UI and logic

* [DDW-893] Lint

* [DDW-893] Dialogs - init

* [DDW-893] Dialogs - containers and buttons

* [DDW-893] Dialogs - basic styling

* [DDW-893] Dialogs - basic styling

* [DDW-893] Dialogs - intl

* [DDW-893] Dialogs - translations manager

* [DDW-893] Dialogs - update Storybook

* [DDW-893] Dialogs - Change 'safetyAgreement' logic

* [DDW-893] Styling adjustments

* [DDW-893] Correct button naming

* [DDW-893] Remove unused dummy data

* [DDW-893] Change local wallet data logic location

* [DDW-893] updates mnemonicsConfirmationDate

* [DDW-893] Move wallet status logic

* [DDW-893] Sidebar notification

* [DDW-893] Integrate new API endpoint

* [DDW-893] Wallets list notification

* [DDW-893] Implement actions and store logic

* [DDW-893] Flow errors

* [DDW-893] Flow errors

* [DDW-893] Translation manager

* [DDW-893] Introduce Recovery Phrase Autocomplete

* [DDW-893] Wallet Recovery Phrase validation and error messages

* [DDW-893] Prevent closing when entering mnemonics

* [DDW-893] Handles ElectronStore not available

* [DDW-893] Fixes broken Storybook story

* [DDW-893] Removes local data when deleting a wallet

* [DDW-893] Update get wallet id and balance API endpoint

* [DDW-893] Wallet nav notification

* [DDW-893] Replace isVerifying component inner state with request isExecuting flag

* [DDW-893] Styling adjustment

* [DDW-893] Support portal link

* [DDW-893] Adjustment

* [DDW-893] Styling adjustments

* [DDW-893] CSS Variables

* [DDW-893] Correct time until

* [DDW-893] Translation manager

* [DDW-893] Update CHANGELOG

* [DDW-893] Japanese translation

* [DDW-893] Japanese translation

* [DDW-893] Japanese translation

* [DDW-893] Japanese translation

* [DDW-893] Japanese translation

* [DDW-893] Bg color for OK status

* [DDW-893] Checkbox labels

* [DDW-893] Keeps button verifying state after request is finished

* [DDW-893] Removes dummy data from store

* [DDW-893] Removes unused styling

* [DDW-893] Fixes wrong dropdown positioning

* [DDW-893] Wallet LocalStorage as a class

* [DDW-893] Flow errors

* [DDW-893] Refactory - init

* [DDW-893] Adjustments

* [DDW-893] Refactory - progress

* [DDW-893] Refactory - progress

* [DDW-893] Restores nav notification

* [DDW-893] Updates wallet local after verifying

* [DDW-893] Removes local data when deleting a wallet

* [DDW-893] Reverts removing logs

* [DDW-893] Removes unfinished test file

* [DDW-893] E2E tests - WIP

* [DDW-893] E2E tests - WIP

* [DDW-893] Working E2E tests

* [DDW-893] Improves E2E tests

* [DDW-893] Translation manager

* [DDW-893] Fixes wrong font variable introduced in [DDW-757]

* [DDW-893] Automatic update message improvement

* [DDW-893] Code improvements

* [DDW-893] Fix broken E2E tests

* [DDW-893] Bump cardano-sl revision

* [DDW-893] Fix failing acceptance test

* [DDW-893] Replace notification threshold from 150 to 183 days

* [DDW-893] Better intl variable naming

* [DDW-893] English proofreading changes

* [DDW-893] Japanese translation
  • Loading branch information
thedanheller authored and nikolaglumac committed Sep 26, 2019
1 parent f61a207 commit ab87929
Show file tree
Hide file tree
Showing 69 changed files with 2,864 additions and 170 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog

### Features

- Implemented wallet recovery phrase verification ([PR 1565](https://github.com/input-output-hk/daedalus/pull/1565))
- Removed select dropdown arrow ([PR 1550](https://github.com/input-output-hk/daedalus/pull/1550))
- Implemented automated and manual update flows unification ([PR 1491](https://github.com/input-output-hk/daedalus/pull/1491))
- Updated behavior of system dialogs ([PR 1494](https://github.com/input-output-hk/daedalus/pull/1494))
Expand Down
6 changes: 3 additions & 3 deletions cardano-sl-src.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"url": "https://github.com/input-output-hk/cardano-sl",
"rev": "51ad7c0503b1c52a75a6eb36096c407934136468",
"date": "2019-08-10T03:35:47+00:00",
"sha256": "0cqaxk3k8i5z4q4b5na0pcln9fblglmn7900vp1xdzmw75pp1rrz",
"rev": "1a792d7cd0f0c93a0f0c28f66372bce3c3808dbd",
"date": "2019-09-25T010:53:54+00:00",
"sha256": "1vk71zn9bnkgkhgcyj59wzrp28crjwcd0lgnm013mhzpvxycgn61",
"fetchSubmodules": false
}
6 changes: 3 additions & 3 deletions features/node-update-notification.feature
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Feature: Node Update Notification
When I set next update version to "10"
And I set next application version to "15"
Then I should see the node update notification overlay
And Overlay should display "newer version" as available version and actions
And Overlay should display "a newer version" as available version and actions

Scenario: Application version and next update version match
When I set next application version to "15"
And I set next update version to "15"
Expand All @@ -31,4 +31,4 @@ Feature: Node Update Notification
Then I should see the node update notification overlay
And Overlay should display "0.14.0" as available version and actions
When I click the accept update button
Then Daedalus should quit
Then Daedalus should quit
24 changes: 16 additions & 8 deletions features/tests/e2e/helpers/wallets-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,25 @@ export const importWalletWithFunds = async (

const createWalletsAsync = async (table, context) => {
const result = await context.client.executeAsync((wallets, done) => {
const mnemonics = {};
window.Promise.all(
wallets.map(wallet =>
daedalus.api.ada.createWallet({
wallets.map(wallet => {
const mnemonic = daedalus.utils.crypto.generateMnemonic();
mnemonics[wallet.name] = mnemonic.split(' ');
return daedalus.api.ada.createWallet({
name: wallet.name,
mnemonic: daedalus.utils.crypto.generateMnemonic(),
mnemonic,
spendingPassword: wallet.password || null,
})
)
});
})
)
.then(() =>
daedalus.stores.wallets.walletsRequest
.execute()
.then(storeWallets =>
daedalus.stores.wallets
.refreshWalletsData()
.then(() => done(storeWallets))
.then(() => done({ storeWallets, mnemonics }))
.catch(error => done(error))
)
.catch(error => done(error))
Expand All @@ -119,9 +122,14 @@ const createWalletsAsync = async (table, context) => {
}, table);
// Add or set the wallets for this scenario
if (context.wallets != null) {
context.wallets.push(...result.value);
context.wallets.push(...result.value.storeWallets);
} else {
context.wallets = result.value;
context.wallets = result.value.storeWallets;
}
if (context.mnemonics != null) {
context.mnemonics.push(...result.value.mnemonics);
} else {
context.mnemonics = result.value.mnemonics;
}
};

Expand Down
2 changes: 0 additions & 2 deletions features/tests/e2e/steps/node-update-notification-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ When(
this.client,
SELECTORS.newAppVersionInfo
);

const [currentAppVersionInfo] = await getVisibleTextsForSelector(
this.client,
SELECTORS.currentAppVersionInfo
);

expect(newAppVersionInfo.replace('v ', '')).to.equal(nextVersion);
expect(currentAppVersionInfo.replace('v ', '')).to.equal(currentAppVersion);
this.client.waitForVisible('.AutomaticUpdateNotification_acceptButton');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Given, When, Then } from 'cucumber';
import { expect } from 'chai';
import { navigateTo } from '../helpers/route-helpers';
import {
waitUntilWaletNamesEqual,
getNameOfActiveWalletInSidebar,
} from '../helpers/wallets-helpers';

const SETTINGS_PAGE_STATUS_SELECTOR = '.WalletRecoveryPhrase_validationStatus';
const SETTINGS_PAGE_BUTTON_SELECTOR = `${SETTINGS_PAGE_STATUS_SELECTOR} .WalletRecoveryPhrase_validationStatusButton`;
const DIALOG_SELECTOR = '.Dialog_dialogWrapper';
const DIALOG_CHECKBOX_SELECTOR = `${DIALOG_SELECTOR} .SimpleCheckbox_check`;
const DIALOG_CONTINUE_BUTTON_SELECTOR = `${DIALOG_SELECTOR} .SimpleButton_root`;
const DIALOG_SUCCESSFUL_SELECTOR = '.verification-successful';
const DIALOG_UNSUCCESSFUL_SELECTOR = '.verification-unsuccessful';
const DIALOG_VERIFY_AGAIN_BUTTON_SELECTOR = `${DIALOG_SELECTOR} button.attention`;
const DIALOG_CLOSE_BUTTON_SELECTOR = `${DIALOG_SELECTOR} .DialogCloseButton_component`;
const walletName = 'Wallet';

Given(
'the last recovery phrase veryfication was done {int} days ago',
async function(daysAgo) {
await this.client.executeAsync((days, done) => {
const { id } = daedalus.stores.wallets.active;
const date = new Date();
date.setDate(date.getDate() - days);
const recoveryPhraseVerificationDate = date.toISOString();
const { updateWalletLocalData } = daedalus.actions.wallets;
updateWalletLocalData.once(done);
updateWalletLocalData.trigger({
id,
recoveryPhraseVerificationDate,
});
}, daysAgo);
}
);

Then(
'I should see a {string} recovery phrase veryfication feature',
async function(status) {
const statusClassname = `${SETTINGS_PAGE_STATUS_SELECTOR}${status}`;
return await this.client.waitForVisible(statusClassname);
}
);

When(/^I click the recovery phrase veryfication button$/, function() {
return this.waitAndClick(SETTINGS_PAGE_BUTTON_SELECTOR);
});

When(/^I click the checkbox and Continue button$/, function() {
this.waitAndClick(DIALOG_CHECKBOX_SELECTOR);
return this.waitAndClick(DIALOG_CONTINUE_BUTTON_SELECTOR);
});

When(/^I enter the recovery phrase mnemonics correctly$/, async function() {
const recoveryPhrase = this.mnemonics[walletName].slice();
await this.client.executeAsync((recoveryPhrase, done) => {
const { checkRecoveryPhrase } = daedalus.actions.walletBackup;
checkRecoveryPhrase.once(done);
checkRecoveryPhrase.trigger({
recoveryPhrase,
});
}, recoveryPhrase);
});

When(/^I enter the recovery phrase mnemonics incorrectly$/, async function() {
const incorrectRecoveryPhrase = [...this.mnemonics[walletName]];
incorrectRecoveryPhrase[0] = 'wrong';
await this.client.executeAsync((recoveryPhrase, done) => {
const { checkRecoveryPhrase } = daedalus.actions.walletBackup;
checkRecoveryPhrase.once(done);
checkRecoveryPhrase.trigger({
recoveryPhrase,
});
}, incorrectRecoveryPhrase);
});

When(/^I should see the confirmation dialog$/, async function() {
return this.client.waitForVisible(DIALOG_SUCCESSFUL_SELECTOR);
});

When(/^I should see the error dialog$/, async function() {
return this.client.waitForVisible(DIALOG_UNSUCCESSFUL_SELECTOR);
});

When(/^I should not see any dialog$/, async function() {
return this.client.waitForVisible(DIALOG_SELECTOR, null, true);
});
When(/^I click the Verify again button$/, async function() {
return this.waitAndClick(DIALOG_VERIFY_AGAIN_BUTTON_SELECTOR);
});
When(/^I click the close button$/, async function() {
return this.waitAndClick(DIALOG_CLOSE_BUTTON_SELECTOR);
});
32 changes: 32 additions & 0 deletions features/wallet-settings-recovery-phrase-verification.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@e2e
Feature: Wallet Settings - Recovery Phrase Verification

Background:
Given I have completed the basic setup
And I have the following wallets:
| name |
| Wallet |

Scenario: Recovery phrase correctly verified
Given the last recovery phrase veryfication was done 400 days ago
And I am on the "Wallet" wallet "settings" screen
Then I should see a "Notification" recovery phrase veryfication feature
When I click the recovery phrase veryfication button
And I click the checkbox and Continue button
And I enter the recovery phrase mnemonics correctly
Then I should see the confirmation dialog
When I click the checkbox and Continue button
Then I should not see any dialog
And I should see a "Ok" recovery phrase veryfication feature

Scenario: Recovery phrase incorrectly verified
Given the last recovery phrase veryfication was done 200 days ago
And I am on the "Wallet" wallet "settings" screen
Then I should see a "Warning" recovery phrase veryfication feature
When I click the recovery phrase veryfication button
And I click the checkbox and Continue button
And I enter the recovery phrase mnemonics incorrectly
Then I should see the error dialog
When I click the close button
Then I should not see any dialog

7 changes: 6 additions & 1 deletion source/renderer/app/actions/wallet-backup-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import Action from './lib/Action';

export default class WalletBackupActions {
startWalletBackup: Action<any> = new Action();
initiateWalletBackup: Action<{ recoveryPhrase: string[] }> = new Action();
initiateWalletBackup: Action<{
recoveryPhrase: Array<string>,
}> = new Action();
acceptPrivacyNoticeForWalletBackup: Action<any> = new Action();
continueToRecoveryPhraseForWalletBackup: Action<any> = new Action();
addWordToWalletBackupVerification: Action<{
Expand All @@ -18,4 +20,7 @@ export default class WalletBackupActions {
restartWalletBackup: Action<any> = new Action();
cancelWalletBackup: Action<any> = new Action();
finishWalletBackup: Action<any> = new Action();
// Recovery phrase confirmation dialog actions
checkRecoveryPhrase: Action<{ recoveryPhrase: Array<string> }> = new Action();
resetRecoveryPhraseCheck: Action<any> = new Action();
}
2 changes: 2 additions & 0 deletions source/renderer/app/actions/wallets-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ export default class WalletsActions {
closeCertificateGeneration: Action<any> = new Action();
setCertificateTemplate: Action<{ selectedTemplate: string }> = new Action();
finishCertificate: Action<any> = new Action();
updateWalletLocalData: Action<any> = new Action();
updateRecoveryPhraseVerificationDate: Action<any> = new Action();
}
36 changes: 36 additions & 0 deletions source/renderer/app/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { createWallet } from './wallets/requests/createWallet';
import { restoreWallet } from './wallets/requests/restoreWallet';
import { updateWallet } from './wallets/requests/updateWallet';
import { getWalletUtxos } from './wallets/requests/getWalletUtxos';
import { getWalletIdAndBalance } from './wallets/requests/getWalletIdAndBalance';

// utility functions
import {
Expand Down Expand Up @@ -118,6 +119,7 @@ import type {
AdaWallet,
AdaWallets,
WalletUtxos,
WalletIdAndBalance,
CreateWalletRequest,
DeleteWalletRequest,
RestoreWalletRequest,
Expand All @@ -129,6 +131,8 @@ import type {
ImportWalletFromFileRequest,
UpdateWalletRequest,
GetWalletUtxosRequest,
GetWalletIdAndBalanceRequest,
GetWalletIdAndBalanceResponse,
} from './wallets/types';

// Common errors
Expand Down Expand Up @@ -873,6 +877,36 @@ export default class AdaApi {
}
};

getWalletIdAndBalance = async (
request: GetWalletIdAndBalanceRequest
): Promise<WalletIdAndBalance> => {
const { recoveryPhrase, getBalance } = request;
Logger.debug('AdaApi::getWalletIdAndBalance called', {
parameters: { getBalance },
});
try {
const response: GetWalletIdAndBalanceResponse = await getWalletIdAndBalance(
this.config,
{
recoveryPhrase,
getBalance,
}
);
Logger.debug('AdaApi::getWalletIdAndBalance success', { response });
const { walletId, balance } = response;
return {
walletId,
balance:
balance !== null // If balance is "null" it means we didn't fetch it - getBalance was false
? new BigNumber(balance).dividedBy(LOVELACES_PER_ADA)
: null,
};
} catch (error) {
Logger.error('AdaApi::getWalletIdAndBalance error', { error });
throw new GenericApiError();
}
};

testReset = async (): Promise<void> => {
Logger.debug('AdaApi::testReset called');
try {
Expand Down Expand Up @@ -1035,6 +1069,7 @@ const _createWalletFromServerData = action(
hasSpendingPassword,
spendingPasswordLastUpdate,
syncState,
createdAt,
} = data;

return new Wallet({
Expand All @@ -1046,6 +1081,7 @@ const _createWalletFromServerData = action(
passwordUpdateDate: new Date(`${spendingPasswordLastUpdate}Z`),
syncState,
isLegacy: false,
createdAt,
});
}
);
Expand Down
Loading

0 comments on commit ab87929

Please sign in to comment.