From d35344834e3748394607871e42d976d67dca3f4d Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Wed, 23 Mar 2022 15:51:39 -0400 Subject: [PATCH] fix(onboarding): show warning when trying to re-encrypt existing account Fixes #5029 --- .../modules/startup/onboarding/controller.nim | 12 +++--- .../startup/onboarding/io_interface.nim | 4 +- src/app/modules/startup/onboarding/module.nim | 8 ++-- src/app/modules/startup/onboarding/view.nim | 15 ++++--- .../service/accounts/dto/accounts.nim | 6 +++ src/app_service/service/accounts/service.nim | 43 +++++++++++-------- .../Onboarding/views/ConfirmPasswordView.qml | 30 +++++++++++++ .../Onboarding/views/ExistingKeyView.qml | 28 ++++++++++-- ui/imports/utils/Constants.qml | 2 + 9 files changed, 110 insertions(+), 38 deletions(-) diff --git a/src/app/modules/startup/onboarding/controller.nim b/src/app/modules/startup/onboarding/controller.nim index 6637ee4fd1..d015eb3219 100644 --- a/src/app/modules/startup/onboarding/controller.nim +++ b/src/app/modules/startup/onboarding/controller.nim @@ -37,7 +37,7 @@ proc init*(self: Controller) = self.events.on(SignalType.NodeLogin.event) do(e:Args): let signal = NodeSignal(e) if signal.event.error != "": - self.delegate.setupAccountError() + self.delegate.setupAccountError(signal.event.error) proc getGeneratedAccounts*(self: Controller): seq[GeneratedAccountDto] = return self.accountsService.generatedAccounts() @@ -53,18 +53,20 @@ proc setDisplayName*(self: Controller, displayName: string) = self.displayName = displayName proc storeSelectedAccountAndLogin*(self: Controller, password: string) = - if(not self.accountsService.setupAccount(self.selectedAccountId, password, self.displayName)): - self.delegate.setupAccountError() + let error = self.accountsService.setupAccount(self.selectedAccountId, password, self.displayName) + if error != "": + self.delegate.setupAccountError(error) proc validateMnemonic*(self: Controller, mnemonic: string): string = return self.accountsService.validateMnemonic(mnemonic) proc importMnemonic*(self: Controller, mnemonic: string) = - if(self.accountsService.importMnemonic(mnemonic)): + let error = self.accountsService.importMnemonic(mnemonic) + if(error == ""): self.selectedAccountId = self.getImportedAccount().id self.delegate.importAccountSuccess() else: - self.delegate.importAccountError() + self.delegate.importAccountError(error) method getPasswordStrengthScore*(self: Controller, password, userName: string): int = return self.generalService.getPasswordStrengthScore(password, userName) diff --git a/src/app/modules/startup/onboarding/io_interface.nim b/src/app/modules/startup/onboarding/io_interface.nim index a71cd444de..7e98bb4825 100644 --- a/src/app/modules/startup/onboarding/io_interface.nim +++ b/src/app/modules/startup/onboarding/io_interface.nim @@ -12,10 +12,10 @@ method load*(self: AccessInterface) {.base.} = method isLoaded*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") -method setupAccountError*(self: AccessInterface) {.base.} = +method setupAccountError*(self: AccessInterface, error: string) {.base.} = raise newException(ValueError, "No implementation available") -method importAccountError*(self: AccessInterface) {.base.} = +method importAccountError*(self: AccessInterface, error: string) {.base.} = raise newException(ValueError, "No implementation available") method importAccountSuccess*(self: AccessInterface) {.base.} = diff --git a/src/app/modules/startup/onboarding/module.nim b/src/app/modules/startup/onboarding/module.nim index 6d2dc86f39..7679601e3a 100644 --- a/src/app/modules/startup/onboarding/module.nim +++ b/src/app/modules/startup/onboarding/module.nim @@ -61,8 +61,8 @@ method setDisplayName*(self: Module, displayName: string) = method storeSelectedAccountAndLogin*(self: Module, password: string) = self.controller.storeSelectedAccountAndLogin(password) -method setupAccountError*(self: Module) = - self.view.setupAccountError() +method setupAccountError*(self: Module, error: string) = + self.view.setupAccountError(error) method getImportedAccount*(self: Module): GeneratedAccountDto = return self.controller.getImportedAccount() @@ -73,8 +73,8 @@ method validateMnemonic*(self: Module, mnemonic: string): string = method importMnemonic*(self: Module, mnemonic: string) = self.controller.importMnemonic(mnemonic) -method importAccountError*(self: Module) = - self.view.importAccountError() +method importAccountError*(self: Module, error: string) = + self.view.importAccountError(error) method importAccountSuccess*(self: Module) = self.view.importAccountSuccess() diff --git a/src/app/modules/startup/onboarding/view.nim b/src/app/modules/startup/onboarding/view.nim index 0e70bfa72c..aa0ed13b97 100644 --- a/src/app/modules/startup/onboarding/view.nim +++ b/src/app/modules/startup/onboarding/view.nim @@ -76,10 +76,10 @@ QtObject: proc storeSelectedAccountAndLogin*(self: View, password: string) {.slot.} = self.delegate.storeSelectedAccountAndLogin(password) - proc accountSetupError*(self: View) {.signal.} + proc accountSetupError*(self: View, error: string) {.signal.} - proc setupAccountError*(self: View) = - self.accountSetupError() + proc setupAccountError*(self: View, error: string) = + self.accountSetupError(error) proc validateMnemonic*(self: View, mnemonic: string): string {.slot.} = return self.delegate.validateMnemonic(mnemonic) @@ -87,15 +87,18 @@ QtObject: proc importMnemonic*(self: View, mnemonic: string) {.slot.} = self.delegate.importMnemonic(mnemonic) - proc accountImportError*(self: View) {.signal.} + proc accountImportError*(self: View, error: string) {.signal.} - proc importAccountError*(self: View) = + proc importAccountError*(self: View, error: string) = # In QML we can connect to this signal and notify a user # before refactoring we didn't have this signal - self.accountImportError() + self.accountImportError(error) + + proc accountImportSuccess*(self: View) {.signal.} proc importAccountSuccess*(self: View) = self.importedAccountChanged() + self.accountImportSuccess() proc getPasswordStrengthScore*(self: View, password: string, userName: string): int {.slot.} = return self.delegate.getPasswordStrengthScore(password, userName) diff --git a/src/app_service/service/accounts/dto/accounts.nim b/src/app_service/service/accounts/dto/accounts.nim index 153b35e8c3..92028ec262 100644 --- a/src/app_service/service/accounts/dto/accounts.nim +++ b/src/app_service/service/accounts/dto/accounts.nim @@ -47,3 +47,9 @@ proc toAccountDto*(jsonObj: JsonNode): AccountDto = if(jsonObj.getProp("images", imagesObj) and imagesObj.kind == JArray): for imgObj in imagesObj: result.images.add(toImage(imgObj)) + +proc contains*(accounts: seq[AccountDto], keyUid: string): bool = + for account in accounts: + if (account.keyUid == keyUid): + return true + return false \ No newline at end of file diff --git a/src/app_service/service/accounts/service.nim b/src/app_service/service/accounts/service.nim index 4164779eb2..51758929c7 100644 --- a/src/app_service/service/accounts/service.nim +++ b/src/app_service/service/accounts/service.nim @@ -18,11 +18,13 @@ logScope: topics = "accounts-service" const PATHS = @[PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET] +const ACCOUNT_ALREADY_EXISTS_ERROR = "account already exists" type Service* = ref object of RootObj fleetConfiguration: FleetConfiguration generatedAccounts: seq[GeneratedAccountDto] + accounts: seq[AccountDto] loggedInAccount: AccountDto importedAccount: GeneratedAccountDto isFirstTimeAccountLogin: bool @@ -98,21 +100,21 @@ proc openedAccounts*(self: Service): seq[AccountDto] = try: let response = status_account.openedAccounts(main_constants.STATUSGODIR) - let accounts = map(response.result.getElems(), proc(x: JsonNode): AccountDto = toAccountDto(x)) + self.accounts = map(response.result.getElems(), proc(x: JsonNode): AccountDto = toAccountDto(x)) - return accounts + return self.accounts except Exception as e: error "error: ", procName="openedAccounts", errName = e.name, errDesription = e.msg proc storeDerivedAccounts(self: Service, accountId, hashedPassword: string, paths: seq[string]): DerivedAccounts = - try: - let response = status_account.storeDerivedAccounts(accountId, hashedPassword, paths) - result = toDerivedAccounts(response.result) + let response = status_account.storeDerivedAccounts(accountId, hashedPassword, paths) - except Exception as e: - error "error: ", procName="storeDerivedAccounts", errName = e.name, errDesription = e.msg + if response.result.contains("error"): + raise newException(Exception, response.result["error"].getStr) + + result = toDerivedAccounts(response.result) proc saveAccountAndLogin(self: Service, hashedPassword: string, account, subaccounts, settings, config: JsonNode): AccountDto = @@ -250,7 +252,7 @@ proc getDefaultNodeConfig*(self: Service, installationId: string): JsonNode = # TODO: commented since it's not necessary (we do the connections thru C bindings). Enable it thru an option once status-nodes are able to be configured in desktop # result["ListenAddr"] = if existsEnv("STATUS_PORT"): newJString("0.0.0.0:" & $getEnv("STATUS_PORT")) else: newJString("0.0.0.0:30305") -proc setupAccount*(self: Service, accountId, password, displayName: string): bool = +proc setupAccount*(self: Service, accountId, password, displayName: string): string = try: let installationId = $genUUID() let accountDataJson = self.getAccountDataForAccountId(accountId, displayName) @@ -262,36 +264,41 @@ proc setupAccount*(self: Service, accountId, password, displayName: string): boo nodeConfigJson.isNil): let description = "at least one json object is not prepared well" error "error: ", procName="setupAccount", errDesription = description - return false + return description let hashedPassword = hashString(password) discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS) - self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson, subaccountDataJson, settingsJson, - nodeConfigJson) - - return self.getLoggedInAccount.isValid() + self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson, + subaccountDataJson, settingsJson, nodeConfigJson) + if self.getLoggedInAccount.isValid(): + return "" + else: + return "logged in account is not valid" except Exception as e: error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg - return false + return e.msg -proc importMnemonic*(self: Service, mnemonic: string): bool = +proc importMnemonic*(self: Service, mnemonic: string): string = try: let response = status_account.multiAccountImportMnemonic(mnemonic) self.importedAccount = toGeneratedAccountDto(response.result) + if (self.accounts.contains(self.importedAccount.keyUid)): + return ACCOUNT_ALREADY_EXISTS_ERROR + let responseDerived = status_account.deriveAccounts(self.importedAccount.id, PATHS) self.importedAccount.derivedAccounts = toDerivedAccounts(responseDerived.result) self.importedAccount.alias= generateAliasFromPk(self.importedAccount.derivedAccounts.whisper.publicKey) self.importedAccount.identicon = generateIdenticonFromPk(self.importedAccount.derivedAccounts.whisper.publicKey) - return self.importedAccount.isValid() - + if (not self.importedAccount.isValid()): + return "imported account is not valid" except Exception as e: error "error: ", procName="importMnemonic", errName = e.name, errDesription = e.msg - return false + return e.msg proc login*(self: Service, account: AccountDto, password: string): string = try: diff --git a/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml b/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml index fa5882155a..45aedf6f75 100644 --- a/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml +++ b/ui/app/AppLayouts/Onboarding/views/ConfirmPasswordView.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.3 import shared.controls 1.0 import shared 1.0 @@ -130,6 +131,35 @@ OnboardingBasePage { pause.start(); } } + + Connections { + target: onboardingModule + onAccountSetupError: { + if (error === Constants.existingAccountError) { + importLoginError.title = qsTr("Keys for this account already exist") + importLoginError.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase") + } else { + //% "Login failed" + importLoginError.title = qsTrId("login-failed") + //% "Login failed. Please re-enter your password and try again." + importLoginError.text = qsTrId("login-failed.-please-re-enter-your-password-and-try-again.") + } + importLoginError.open() + } + } + + MessageDialog { + id: importLoginError + //% "Login failed" + title: qsTrId("login-failed") + //% "Login failed. Please re-enter your password and try again." + text: qsTrId("login-failed.-please-re-enter-your-password-and-try-again.") + icon: StandardIcon.Critical + standardButtons: StandardButton.Ok + onVisibilityChanged: { + submitBtn.loading = false + } + } } } diff --git a/ui/app/AppLayouts/Onboarding/views/ExistingKeyView.qml b/ui/app/AppLayouts/Onboarding/views/ExistingKeyView.qml index 1add29eca8..5d3c598de1 100644 --- a/ui/app/AppLayouts/Onboarding/views/ExistingKeyView.qml +++ b/ui/app/AppLayouts/Onboarding/views/ExistingKeyView.qml @@ -1,5 +1,6 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 +import QtQuick.Dialogs 1.3 import utils 1.0 @@ -19,14 +20,35 @@ Item { enterSeedPhraseModal.open() } + Connections { + target: onboardingModule + onAccountImportError: { + if (error === Constants.existingAccountError) { + importSeedError.title = qsTr("Keys for this account already exist") + importSeedError.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase") + } else { + importSeedError.title = qsTr("Error importing seed") + importSeedError.text = error + } + importSeedError.open() + } + onAccountImportSuccess: { + enterSeedPhraseModal.wentNext = true + enterSeedPhraseModal.close() + recoverySuccessModal.open() + } + } + MessageDialog { + id: importSeedError + icon: StandardIcon.Critical + standardButtons: StandardButton.Ok + } + EnterSeedPhraseModal { property bool wentNext: false id: enterSeedPhraseModal onConfirmSeedClick: function (mnemonic) { - wentNext = true - enterSeedPhraseModal.close() OnboardingStore.importMnemonic(mnemonic) - recoverySuccessModal.open() } onClosed: function () { if (!wentNext) { diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 0c7f2828c9..40ec6c4731 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -247,4 +247,6 @@ QtObject { } readonly property bool isCppApp: typeof cppApp !== "undefined" ? cppApp : false + + readonly property string existingAccountError: "account already exists" }