From 861c585d2b1976a18b33ae0b66ba32bf034a450a Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Fri, 23 Sep 2022 15:53:13 +0200 Subject: [PATCH] feat(@desktop/keycard): adding wallet account using `Authenticate` flow Fixes: #7509 --- src/app/modules/main/controller.nim | 5 +- .../wallet_section/accounts/controller.nim | 32 +++- .../wallet_section/accounts/io_interface.nim | 12 ++ .../main/wallet_section/accounts/module.nim | 19 +++ .../main/wallet_section/accounts/view.nim | 12 ++ .../keycard_popup/controller.nim | 11 +- .../internal/biometrics_pin_invalid_state.nim | 1 + .../internal/enter_pin_state.nim | 2 + .../keycard_popup/io_interface.nim | 22 ++- src/app_service/service/accounts/service.nim | 2 - .../src/StatusQ/Controls/StatusInput.qml | 4 +- .../Wallet/popups/AddAccountModal.qml | 157 +++++++++--------- ui/app/AppLayouts/Wallet/stores/RootStore.qml | 12 ++ .../shared/popups/keycard/KeycardPopup.qml | 3 +- .../popups/keycard/states/KeycardInit.qml | 4 +- vendor/status-go | 2 +- 16 files changed, 211 insertions(+), 89 deletions(-) diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 530d2de9e7..212d6fa41f 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -240,8 +240,11 @@ proc init*(self: Controller) = self.authenticateUserFlowRequestedBy.len == 0: return self.delegate.onSharedKeycarModuleFlowTerminated(args.lastStepInTheCurrentFlow) + var password = args.password + if password.len == 0 and args.keyUid.len > 0: + password = args.keyUid let data = SharedKeycarModuleArgs(uniqueIdentifier: self.authenticateUserFlowRequestedBy, - data: args.data, + password: password, keyUid: args.keyUid, txR: args.txR, txS: args.txS, diff --git a/src/app/modules/main/wallet_section/accounts/controller.nim b/src/app/modules/main/wallet_section/accounts/controller.nim index 86300075a9..e872c2a4c9 100644 --- a/src/app/modules/main/wallet_section/accounts/controller.nim +++ b/src/app/modules/main/wallet_section/accounts/controller.nim @@ -2,20 +2,29 @@ import io_interface import ../../../../../app_service/service/wallet_account/service as wallet_account_service import ../../../../../app_service/service/accounts/service as accounts_service +import ../../../../global/global_singleton +import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module + +import ../../../../core/eventemitter + +const UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER* = "WalletSection-AccountsModule" + type Controller* = ref object of RootObj delegate: io_interface.AccessInterface + events: EventEmitter walletAccountService: wallet_account_service.Service accountsService: accounts_service.Service proc newController*( delegate: io_interface.AccessInterface, - walletAccountService: wallet_account_service.Service + events: EventEmitter, walletAccountService: wallet_account_service.Service, accountsService: accounts_service.Service ): Controller = result = Controller() result.delegate = delegate + result.events = events result.walletAccountService = walletAccountService result.accountsService = accountsService @@ -23,7 +32,11 @@ proc delete*(self: Controller) = discard proc init*(self: Controller) = - discard + self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args): + let args = SharedKeycarModuleArgs(e) + if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER: + return + self.delegate.onUserAuthenticated(args.password) proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = return self.walletAccountService.getWalletAccounts() @@ -56,3 +69,18 @@ proc validSeedPhrase*(self: Controller, seedPhrase: string): bool = let err = self.accountsService.validateMnemonic(seedPhrase) return err.len == 0 +proc loggedInUserUsesBiometricLogin*(self: Controller): bool = + if(not defined(macosx)): + return false + let value = singletonInstance.localAccountSettings.getStoreToKeychainValue() + if (value != LS_VALUE_STORE): + return false + return true + +proc getLoggedInAccount*(self: Controller): AccountDto = + return self.accountsService.getLoggedInAccount() + +proc authenticateUser*(self: Controller, keyUid = "") = + let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER, + keyUid: keyUid) + self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) diff --git a/src/app/modules/main/wallet_section/accounts/io_interface.nim b/src/app/modules/main/wallet_section/accounts/io_interface.nim index f39ff5c687..58968844b5 100644 --- a/src/app/modules/main/wallet_section/accounts/io_interface.nim +++ b/src/app/modules/main/wallet_section/accounts/io_interface.nim @@ -47,4 +47,16 @@ method viewDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") method validSeedPhrase*(self: AccessInterface, value: string): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method authenticateUser*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method onUserAuthenticated*(self: AccessInterface, password: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method loggedInUserUsesBiometricLogin*(self: AccessInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method isProfileKeyPairMigrated*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/accounts/module.nim b/src/app/modules/main/wallet_section/accounts/module.nim index b31012ced9..94ef31a316 100644 --- a/src/app/modules/main/wallet_section/accounts/module.nim +++ b/src/app/modules/main/wallet_section/accounts/module.nim @@ -172,3 +172,22 @@ method getDerivedAddressForPrivateKey*(self: Module, privateKey: string) = method validSeedPhrase*(self: Module, value: string): bool = return self.controller.validSeedPhrase(value) + +method loggedInUserUsesBiometricLogin*(self: Module): bool = + return self.controller.loggedInUserUsesBiometricLogin() + +method isProfileKeyPairMigrated*(self: Module): bool = + return self.controller.getLoggedInAccount().keycardPairing.len > 0 + +method authenticateUser*(self: Module) = + if self.isProfileKeyPairMigrated(): + let keyUid = singletonInstance.userProfile.getKeyUid() + self.controller.authenticateUser(keyUid) + else: + self.controller.authenticateUser() + +method onUserAuthenticated*(self: Module, password: string) = + if password.len > 0: + self.view.userAuthenticaionSuccess(password) + else: + self.view.userAuthentiactionFail() diff --git a/src/app/modules/main/wallet_section/accounts/view.nim b/src/app/modules/main/wallet_section/accounts/view.nim index 4ecbee234b..b917509fef 100644 --- a/src/app/modules/main/wallet_section/accounts/view.nim +++ b/src/app/modules/main/wallet_section/accounts/view.nim @@ -277,3 +277,15 @@ QtObject: proc validSeedPhrase*(self: View, value: string): bool {.slot.} = return self.delegate.validSeedPhrase(value) + + proc userAuthenticaionSuccess*(self: View, password: string) {.signal.} + proc userAuthentiactionFail*(self: View) {.signal.} + + proc authenticateUser*(self: View) {.slot.} = + self.delegate.authenticateUser() + + proc loggedInUserUsesBiometricLogin*(self: View): bool {.slot.} = + return self.delegate.loggedInUserUsesBiometricLogin() + + proc isProfileKeyPairMigrated*(self: View): bool {.slot.} = + return self.delegate.isProfileKeyPairMigrated() \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/controller.nim b/src/app/modules/shared_modules/keycard_popup/controller.nim index dc4a78f2e4..a9f6c22d0b 100644 --- a/src/app/modules/shared_modules/keycard_popup/controller.nim +++ b/src/app/modules/shared_modules/keycard_popup/controller.nim @@ -44,6 +44,7 @@ type tmpKeyUidWhichIsBeingAuthenticating: string tmpKeyUidWhichIsBeingUnlocking: string tmpUsePinFromBiometrics: bool + tmpOfferToStoreUpdatedPinToKeychain: bool tmpKeycardUid: string proc newController*(delegate: io_interface.AccessInterface, @@ -125,7 +126,7 @@ proc init*(self: Controller) = if args.uniqueIdentifier != self.uniqueIdentifier: return self.connectKeycardReponseSignal() - self.delegate.onUserAuthenticated(args.data) + self.delegate.onUserAuthenticated(args.password) self.connectionIds.add(handlerId) proc getKeycardData*(self: Controller): string = @@ -170,6 +171,12 @@ proc setPinMatch*(self: Controller, value: bool) = proc getPinMatch*(self: Controller): bool = return self.tmpPinMatch +proc setOfferToStoreUpdatedPinToKeychain*(self: Controller, value: bool) = + self.tmpOfferToStoreUpdatedPinToKeychain = value + +proc offerToStoreUpdatedPinToKeychain*(self: Controller): bool = + return self.tmpOfferToStoreUpdatedPinToKeychain + proc setPassword*(self: Controller, value: string) = self.tmpPassword = value @@ -307,7 +314,7 @@ proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) = var data = SharedKeycarModuleFlowTerminatedArgs(uniqueIdentifier: self.uniqueIdentifier, lastStepInTheCurrentFlow: lastStepInTheCurrentFlow) if lastStepInTheCurrentFlow: - data.data = self.tmpPassword + data.password = self.tmpPassword data.keyUid = flowEvent.keyUid data.txR = flowEvent.txSignature.r data.txS = flowEvent.txSignature.s diff --git a/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_invalid_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_invalid_state.nim index 19531b6812..d5c548b230 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_invalid_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_invalid_state.nim @@ -15,6 +15,7 @@ method executePrimaryCommand*(self: BiometricsPinInvalidState, controller: Contr method executeSecondaryCommand*(self: BiometricsPinInvalidState, controller: Controller) = if self.flowType == FlowType.Authentication: controller.setUsePinFromBiometrics(true) + controller.setOfferToStoreUpdatedPinToKeychain(true) method getNextSecondaryState*(self: BiometricsPinInvalidState, controller: Controller): State = if self.flowType == FlowType.Authentication: diff --git a/src/app/modules/shared_modules/keycard_popup/internal/enter_pin_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/enter_pin_state.nim index b8811a4300..3345abfa27 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/enter_pin_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/enter_pin_state.nim @@ -98,6 +98,8 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke return createState(StateType.MaxPinRetriesReached, self.flowType, nil) if keycardFlowType == ResponseTypeValueKeycardFlowResult: if keycardEvent.error.len == 0: + if controller.offerToStoreUpdatedPinToKeychain(): + controller.tryToStoreDataToKeychain(controller.getPin()) controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) return nil if self.flowType == FlowType.DisplayKeycardContent: diff --git a/src/app/modules/shared_modules/keycard_popup/io_interface.nim b/src/app/modules/shared_modules/keycard_popup/io_interface.nim index 0d82e676bd..dc1bffa1b9 100644 --- a/src/app/modules/shared_modules/keycard_popup/io_interface.nim +++ b/src/app/modules/shared_modules/keycard_popup/io_interface.nim @@ -8,13 +8,33 @@ const SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED* = "sharedKeycarModuleFlowTer const SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER* = "sharedKeycarModuleAuthenticateUser" const SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED* = "sharedKeycarModuleUserAuthenticated" +## Authentication in the app is a global thing and may be used from any part of the app. How to achieve that... it's enough just to send +## `SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER` signal with properly set `SharedKeycarModuleAuthenticationArgs` and props there: +## -- `uniqueIdentifier` - some unique string, for the readability usually name of the module which needs authentication, +## -- in case of non keycard user (regular) user that's enough, +## -- in case of keycard user we want to authenticate it with a card that his profile is migrated to, that means apart of `uniqueIdentifier` +## we need to set `keyUid` as well, +## -- in case we want to sign a transaction for a certain wallet's account, then apart of `uniqueIdentifier` and `keyUid`of a key pair that +## account belongs to, we need to set and `bip44Path` and `txHash` +## +## `SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER` will be handled in the `mainModule` (shared keycard popup module will be run) and as a +## result, when authentication gets done `SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED` signal with properly set `SharedKeycarModuleArgs` +## and props there will be emitted: +## -- `uniqueIdentifier` - will be the same as one used for running authentication process +## -- in case of success of a regular user authentication `password` will be sent, otherwise it will be empty +## -- in case of success of a keycard user authentication `keyUid` will be sent, otherwise it will be empty +## -- in case of success of a signing a transaction `keyUid`, `txR` and `txS` will be sent, otherwise it will be empty +## +## TLDR: when you need to authenticate user, from the module where it's needed you have to send `SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER` +## signal to run authentication process and connect to `SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED` signal to get the results of it. + type SharedKeycarModuleBaseArgs* = ref object of Args uniqueIdentifier*: string type SharedKeycarModuleArgs* = ref object of SharedKeycarModuleBaseArgs - data*: string + password*: string keyUid*: string txR*: string txS*: string diff --git a/src/app_service/service/accounts/service.nim b/src/app_service/service/accounts/service.nim index b53871a616..3c47f5ea2d 100644 --- a/src/app_service/service/accounts/service.nim +++ b/src/app_service/service/accounts/service.nim @@ -590,8 +590,6 @@ proc verifyAccountPassword*(self: Service, account: string, password: string): b proc convertToKeycardAccount*(self: Service, keyUid: string, password: string): bool = try: - self.setKeyStoreDir(keyUid) - var accountDataJson = %* { "name": self.getLoggedInAccount().name, "key-uid": keyUid diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml index c62e89dd60..cbfcc53610 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusInput.qml @@ -244,11 +244,11 @@ Item { This function resets the text input validation and text. */ function reset() { + statusBaseInput.text = "" + root.errorMessage = "" statusBaseInput.valid = false statusBaseInput.dirty = false statusBaseInput.pristine = true - statusBaseInput.text = "" - root.errorMessage = "" } property string _previousText: text diff --git a/ui/app/AppLayouts/Wallet/popups/AddAccountModal.qml b/ui/app/AppLayouts/Wallet/popups/AddAccountModal.qml index 8f2d3a4710..6716ad17de 100644 --- a/ui/app/AppLayouts/Wallet/popups/AddAccountModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/AddAccountModal.qml @@ -24,8 +24,6 @@ StatusModal { property int minPswLen: 10 readonly property int marginBetweenInputs: 38 - readonly property alias passwordValidationError: d.passwordValidationError - property var emojiPopup: null header.title: qsTr("Generate an account") @@ -33,14 +31,6 @@ StatusModal { signal afterAddAccount() - Timer { - id: waitTimer - - interval: 1000 - running: false - onTriggered: d.getDerivedAddressList() - } - Connections { target: emojiPopup enabled: root.opened @@ -51,16 +41,15 @@ StatusModal { } Connections { - target: RootStore - enabled: root.opened - - function onDerivedAddressesListChanged() { - d.isPasswordCorrect = RootStore.derivedAddressesError.length === 0 + target: walletSectionAccounts + onUserAuthenticaionSuccess: { + validationError.text = "" + d.password = password + d.getDerivedAddressList() } - - function onDerivedAddressesErrorChanged() { - if(Utils.isInvalidPasswordMessage(RootStore.derivedAddressesError)) - d.passwordValidationError = qsTr("Password must be at least %n character(s) long", "", root.minPswLen); + onUserAuthentiactionFail: { + d.password = "" + validationError.text = qsTr("An authentication failed") } } @@ -70,33 +59,27 @@ StatusModal { readonly property int numOfItems: 100 readonly property int pageNumber: 1 - property string passwordValidationError: "" - property bool isPasswordCorrect: false + property string password: "" + property int selectedAccountType: SelectGeneratedAccount.AddAccountType.GenerateNew + readonly property bool authenticationNeeded: d.selectedAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly && + d.password === "" + + + function getDerivedAddressList() { - if(advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase + if(d.selectedAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase && !!advancedSelection.expandableItem.path && !!advancedSelection.expandableItem.mnemonicText) { RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText, advancedSelection.expandableItem.path, numOfItems, pageNumber) } else if(!!advancedSelection.expandableItem.path && !!advancedSelection.expandableItem.derivedFromAddress - && (passwordInput.text.length > 0)) { - RootStore.getDerivedAddressList(passwordInput.text, advancedSelection.expandableItem.derivedFromAddress, + && (d.password.length > 0)) { + RootStore.getDerivedAddressList(d.password, advancedSelection.expandableItem.derivedFromAddress, advancedSelection.expandableItem.path, numOfItems, pageNumber) } } - function showPasswordError(errMessage) { - if (errMessage) { - if (Utils.isInvalidPasswordMessage(errMessage)) { - d.passwordValidationError = qsTr("Wrong password") - scroll.contentY = -scroll.padding - } else { - console.warn(`Unhandled error case. Status-go message: ${errMessage}`) - } - } - } - function generateNewAccount() { // TODO the loading doesn't work because the function freezes the view. Might need to use threads nextButton.loading = true @@ -107,19 +90,19 @@ StatusModal { let errMessage = "" - switch(advancedSelection.expandableItem.addAccountType) { + switch(d.selectedAccountType) { case SelectGeneratedAccount.AddAccountType.GenerateNew: - errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, + errMessage = RootStore.generateNewAccount(d.password, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji, advancedSelection.expandableItem.completePath, advancedSelection.expandableItem.derivedFromAddress) break case SelectGeneratedAccount.AddAccountType.ImportSeedPhrase: - errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text, + errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, d.password, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji, advancedSelection.expandableItem.completePath) break case SelectGeneratedAccount.AddAccountType.ImportPrivateKey: - errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text, + errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, d.password, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji) break case SelectGeneratedAccount.AddAccountType.WatchOnly: @@ -131,23 +114,33 @@ StatusModal { nextButton.loading = false if (errMessage) { - d.showPasswordError(errMessage) + console.warn(`Unhandled error case. Status-go message: ${errMessage}`) } else { root.afterAddAccount() root.close() } } + + function nextButtonClicked() { + if (d.authenticationNeeded) { + d.password = "" + RootStore.authenticateUser() + } + else { + d.generateNewAccount() + } + } } onOpened: { accountNameInput.input.asset.emoji = StatusQUtils.Emoji.getRandomEmoji(StatusQUtils.Emoji.size.verySmall) colorSelectionGrid.selectedColorIndex = Math.floor(Math.random() * colorSelectionGrid.model.length) - passwordInput.forceActiveFocus(Qt.MouseFocusReason) + accountNameInput.input.edit.forceActiveFocus() } onClosed: { - d.passwordValidationError = "" - passwordInput.text = "" + d.password = "" + validationError.text = "" accountNameInput.reset() advancedSelection.expanded = false advancedSelection.reset() @@ -169,34 +162,15 @@ StatusModal { spacing: Style.current.halfPadding topPadding: 20 - // To-Do Password hidden option not supported in StatusQ StatusInput - Item { + StatusBaseText { + id: validationError + visible: text !== "" width: parent.width - height: passwordInput.height - visible: advancedSelection.expandableItem.addAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly - Input { - id: passwordInput - anchors.fill: parent - - placeholderText: qsTr("Enter your password...") - label: qsTr("Password") - textField.echoMode: TextInput.Password - validationError: d.passwordValidationError - textField.objectName: "accountModalPassword" - inputLabel.font.pixelSize: 15 - inputLabel.font.weight: Font.Normal - onTextChanged: { - d.isPasswordCorrect = false - d.passwordValidationError = "" - waitTimer.restart() - } - onKeyPressed: { - if(event.key === Qt.Key_Tab) { - accountNameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason) - event.accepted = true - } - } - } + height: 16 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 12 + color: Style.current.danger + wrapMode: TextEdit.Wrap } StatusInput { @@ -206,6 +180,7 @@ StatusModal { input.isIconSelectable: true input.asset.color: colorSelectionGrid.selectedColor ? colorSelectionGrid.selectedColor : Theme.palette.directColor1 input.leftPadding: Style.current.padding + enabled: !d.authenticationNeeded onIconClicked: { root.emojiPopup.open() root.emojiPopup.emojiSize = StatusQUtils.Emoji.size.verySmall @@ -231,6 +206,7 @@ StatusModal { StatusColorSelectorGrid { id: colorSelectionGrid anchors.horizontalCenter: parent.horizontalCenter + enabled: !d.authenticationNeeded titleText: qsTr("color").toUpperCase() } @@ -262,7 +238,15 @@ StatusModal { return } } - Component.onCompleted: advancedSelection.isValid = Qt.binding(() => isValid) + + onAddAccountTypeChanged: { + d.selectedAccountType = addAccountType + } + + Component.onCompleted: { + d.selectedAccountType = addAccountType + advancedSelection.isValid = Qt.binding(() => isValid) + } } } } @@ -272,21 +256,42 @@ StatusModal { StatusButton { id: nextButton - text: loading ? qsTr("Loading...") : qsTr("Add account") + text: { + if (d.authenticationNeeded) { + return qsTr("Authenticate") + } + if (loading) { + return qsTr("Loading...") + } + return qsTr("Add account") + } + enabled: { + if (d.authenticationNeeded) { + return true + } if (loading) { return false } + return accountNameInput.text !== "" && advancedSelection.isValid + } - return (advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly || d.isPasswordCorrect) - && accountNameInput.text !== "" && advancedSelection.isValid + icon.name: { + if (d.authenticationNeeded) { + if (RootStore.loggedInUserUsesBiometricLogin()) + return "touch-id" + if (RootStore.isProfileKeyPairMigrated()) + return "keycard" + return "password" + } + return "" } highlighted: focus - Keys.onReturnPressed: d.generateNewAccount() - onClicked : d.generateNewAccount() + Keys.onReturnPressed: d.nextButtonClicked() + onClicked : d.nextButtonClicked() } ] } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 6dd54352c3..57ef21bba1 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -222,4 +222,16 @@ QtObject { function getNextSelectableDerivedAddressIndex() { return walletSectionAccounts.getNextSelectableDerivedAddressIndex() } + + function authenticateUser() { + walletSectionAccounts.authenticateUser() + } + + function loggedInUserUsesBiometricLogin() { + return walletSectionAccounts.loggedInUserUsesBiometricLogin() + } + + function isProfileKeyPairMigrated() { + return walletSectionAccounts.isProfileKeyPairMigrated() + } } diff --git a/ui/imports/shared/popups/keycard/KeycardPopup.qml b/ui/imports/shared/popups/keycard/KeycardPopup.qml index 912b682e84..5168bd0662 100644 --- a/ui/imports/shared/popups/keycard/KeycardPopup.qml +++ b/ui/imports/shared/popups/keycard/KeycardPopup.qml @@ -439,10 +439,11 @@ StatusModal { root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || - root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty) return qsTr("Use PIN") + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid) + return qsTr("Update PIN") } } if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.unlockKeycard) { diff --git a/ui/imports/shared/popups/keycard/states/KeycardInit.qml b/ui/imports/shared/popups/keycard/states/KeycardInit.qml index 7af9163c05..96134b1d87 100644 --- a/ui/imports/shared/popups/keycard/states/KeycardInit.qml +++ b/ui/imports/shared/popups/keycard/states/KeycardInit.qml @@ -826,7 +826,9 @@ Item { } PropertyChanges { target: message - text: "" + text: qsTr("The PIN length doesn't match Keycard's PIN length") + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.baseColor1 } } ] diff --git a/vendor/status-go b/vendor/status-go index 698c32f3e3..d89c0c8d9e 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 698c32f3e3684dd5918b8f38aa55fc568e1e7639 +Subproject commit d89c0c8d9e333dc9b0c4ea36fd9c49c09a9b7d19