diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index a33fd883ba..f18b723f20 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -246,7 +246,7 @@ proc init*(self: Controller) = self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED, data) self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_DISPLAY_POPUP) do(e: Args): - let args = SharedKeycarModuleArgs(e) + let args = SharedKeycarModuleBaseArgs(e) if args.uniqueIdentifier != UNIQUE_MAIN_MODULE_IDENTIFIER or self.authenticateUserFlowRequestedBy.len == 0: return diff --git a/src/app/modules/main/profile_section/keycard/controller.nim b/src/app/modules/main/profile_section/keycard/controller.nim index 5cc205d038..983513483c 100644 --- a/src/app/modules/main/profile_section/keycard/controller.nim +++ b/src/app/modules/main/profile_section/keycard/controller.nim @@ -34,7 +34,7 @@ proc init*(self: Controller) = self.delegate.onSharedKeycarModuleFlowTerminated(args.lastStepInTheCurrentFlow) self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_DISPLAY_POPUP) do(e: Args): - let args = SharedKeycarModuleArgs(e) + let args = SharedKeycarModuleBaseArgs(e) if args.uniqueIdentifier != UNIQUE_SETTING_KEYCARD_MODULE_IDENTIFIER: return self.delegate.onDisplayKeycardSharedModuleFlow() diff --git a/src/app/modules/shared_modules/keycard_popup/controller.nim b/src/app/modules/shared_modules/keycard_popup/controller.nim index c74309776b..2c7ce75416 100644 --- a/src/app/modules/shared_modules/keycard_popup/controller.nim +++ b/src/app/modules/shared_modules/keycard_popup/controller.nim @@ -33,12 +33,13 @@ type tmpPin: string tmpPinMatch: bool tmpPassword: string - tmpKeyUid: string tmpSelectedKeyPairIsProfile: bool - tmpSelectedKeyPairName: string + tmpSelectedKeyPairDto: KeyPairDto tmpSelectedKeyPairWalletPaths: seq[string] tmpSeedPhrase: string tmpSeedPhraseLength: int + tmpKeyUidWhichIsBeingAuthenticating: string + tmpUsePinFromBiometrics: bool proc newController*(delegate: io_interface.AccessInterface, uniqueIdentifier: string, @@ -64,6 +65,7 @@ proc newController*(delegate: io_interface.AccessInterface, result.tmpPinMatch = false result.tmpSeedPhraseLength = 0 result.tmpSelectedKeyPairIsProfile = false + result.tmpUsePinFromBiometrics = false proc serviceApplicable[T](service: T): bool = if not service.isNil: @@ -138,6 +140,12 @@ proc setPin*(self: Controller, value: string) = proc getPin*(self: Controller): string = return self.tmpPin +proc setUsePinFromBiometrics*(self: Controller, value: bool) = + self.tmpUsePinFromBiometrics = value + +proc usePinFromBiometrics*(self: Controller): bool = + return self.tmpUsePinFromBiometrics + proc setPinMatch*(self: Controller, value: bool) = self.tmpPinMatch = value @@ -150,8 +158,8 @@ proc setPassword*(self: Controller, value: string) = proc getPassword*(self: Controller): string = return self.tmpPassword -proc setKeyUid*(self: Controller, value: string) = - self.tmpKeyUid = value +proc getKeyUidWhichIsBeingAuthenticating*(self: Controller): string = + self.tmpKeyUidWhichIsBeingAuthenticating proc setSelectedKeyPairIsProfile*(self: Controller, value: bool) = self.tmpSelectedKeyPairIsProfile = value @@ -159,11 +167,14 @@ proc setSelectedKeyPairIsProfile*(self: Controller, value: bool) = proc getSelectedKeyPairIsProfile*(self: Controller): bool = return self.tmpSelectedKeyPairIsProfile -proc setSelectedKeyPairName*(self: Controller, value: string) = - self.tmpSelectedKeyPairName = value +proc setSelectedKeyPairDto*(self: Controller, keyPairDto: KeyPairDto) = + self.tmpSelectedKeyPairDto = keyPairDto -proc getSelectedKeyPairName*(self: Controller): string = - return self.tmpSelectedKeyPairName +proc getSelectedKeyPairDto*(self: Controller): KeyPairDto = + return self.tmpSelectedKeyPairDto + +proc setKeycardUid*(self: Controller, value: string) = + self.tmpSelectedKeyPairDto.keycardUid = value proc setSelectedKeyPairWalletPaths*(self: Controller, paths: seq[string]) = self.tmpSelectedKeyPairWalletPaths = paths @@ -186,16 +197,16 @@ proc validSeedPhrase*(self: Controller, seedPhrase: string): bool = let err = self.accountsService.validateMnemonic(seedPhrase) return err.len == 0 -proc seedPhraseRefersToLoggedInUser*(self: Controller, seedPhrase: string): bool = +proc seedPhraseRefersToSelectedKeyPair*(self: Controller, seedPhrase: string): bool = let acc = self.accountsService.createAccountFromMnemonic(seedPhrase) - return acc.keyUid == singletonInstance.userProfile.getAddress() + return acc.keyUid == self.tmpSelectedKeyPairDto.keyUid proc verifyPassword*(self: Controller, password: string): bool = return self.accountsService.verifyPassword(password) -proc convertToKeycardAccount*(self: Controller, password: string): bool = +proc convertSelectedKeyPairToKeycardAccount*(self: Controller, password: string): bool = singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW) - return self.accountsService.convertToKeycardAccount(self.tmpKeyUid, password) + return self.accountsService.convertToKeycardAccount(self.tmpSelectedKeyPairDto.keyUid, password) proc getLoggedInAccount*(self: Controller): AccountDto = return self.accountsService.getLoggedInAccount() @@ -231,15 +242,39 @@ proc runLoadAccountFlow*(self: Controller, factoryReset = false) = self.cancelCurrentFlow() self.keycardService.startLoadAccountFlow(factoryReset) +proc runSignFlow*(self: Controller, keyUid = "", bip44Path = "", txHash = "") = + ## For signing a transaction we need to provide a key uid of a keypair that an account we want to sign a transaction + ## for belongs to. If we're just doing an authentication for a logged in user, then default key uid is always the key + ## uid of the logged in user. + self.tmpKeyUidWhichIsBeingAuthenticating = keyUid + if self.tmpKeyUidWhichIsBeingAuthenticating.len == 0: + self.tmpKeyUidWhichIsBeingAuthenticating = singletonInstance.userProfile.getKeyUid() + self.cancelCurrentFlow() + self.keycardService.startSignFlow(bip44Path, txHash) + proc resumeCurrentFlowLater*(self: Controller) = self.keycardService.resumeCurrentFlowLater() proc readyToDisplayPopup*(self: Controller) = - self.events.emit(SignalSharedKeycarModuleDisplayPopup, Args()) + let data = SharedKeycarModuleBaseArgs(uniqueIdentifier: self.uniqueIdentifier) + self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_DISPLAY_POPUP, data) proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) = - let data = SharedKeycarModuleFlowTerminatedArgs(lastStepInTheCurrentFlow: lastStepInTheCurrentFlow) - self.events.emit(SignalSharedKeycarModuleFlowTerminated, data) + let (_, flowEvent) = self.getLastReceivedKeycardData() + var data = SharedKeycarModuleFlowTerminatedArgs(uniqueIdentifier: self.uniqueIdentifier, + lastStepInTheCurrentFlow: lastStepInTheCurrentFlow) + if lastStepInTheCurrentFlow: + data.data = self.tmpPassword + data.keyUid = flowEvent.keyUid + data.txR = flowEvent.txSignature.r + data.txS = flowEvent.txSignature.s + data.txV = flowEvent.txSignature.v + self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED, data) + +proc authenticateUser*(self: Controller) = + self.disconnectKeycardReponseSignal() + let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: self.uniqueIdentifier) + self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = if not serviceApplicable(self.walletAccountService): @@ -251,6 +286,26 @@ proc getBalanceForAddress*(self: Controller, address: string): float64 = return return self.walletAccountService.fetchBalanceForAddress(address) +proc addMigratedKeyPair*(self: Controller, keyPair: KeyPairDto): bool = + if not serviceApplicable(self.walletAccountService): + return + return self.walletAccountService.addMigratedKeyPair(keyPair) + +proc getAllMigratedKeyPairs*(self: Controller): seq[KeyPairDto] = + if not serviceApplicable(self.walletAccountService): + return + return self.walletAccountService.getAllMigratedKeyPairs() + +proc getMigratedKeyPairByKeyUid*(self: Controller, keyUid: string): seq[KeyPairDto] = + if not serviceApplicable(self.walletAccountService): + return + return self.walletAccountService.getMigratedKeyPairByKeyUid(keyUid) + +proc getSigningPhrase*(self: Controller): string = + if not serviceApplicable(self.settingsService): + return + return self.settingsService.getSigningPhrase() + proc enterKeycardPin*(self: Controller, pin: string) = self.keycardService.enterPin(pin) @@ -289,4 +344,14 @@ proc loggedInUserUsesBiometricLogin*(self: Controller): bool = let value = singletonInstance.localAccountSettings.getStoreToKeychainValue() if (value != LS_VALUE_STORE): return false - return true \ No newline at end of file + return true + +proc tryToObtainDataFromKeychain*(self: Controller) = + if(not self.loggedInUserUsesBiometricLogin()): + return + let loggedInAccount = self.getLoggedInAccount() + self.keychainService.tryToObtainData(loggedInAccount.name) + +proc tryToStoreDataToKeychain*(self: Controller, password: string) = + let loggedInAccount = self.getLoggedInAccount() + self.keychainService.storeData(loggedInAccount.name, password) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/biometrics_password_failed_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_password_failed_state.nim new file mode 100644 index 0000000000..3d8081f176 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_password_failed_state.nim @@ -0,0 +1,22 @@ +type + BiometricsPasswordFailedState* = ref object of State + +proc newBiometricsPasswordFailedState*(flowType: FlowType, backState: State): BiometricsPasswordFailedState = + result = BiometricsPasswordFailedState() + result.setup(flowType, StateType.BiometricsPasswordFailed, backState) + +proc delete*(self: BiometricsPasswordFailedState) = + self.State.delete + +method executePrimaryCommand*(self: BiometricsPasswordFailedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.tryToObtainDataFromKeychain() + +method getNextSecondaryState*(self: BiometricsPasswordFailedState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + return createState(StateType.EnterPassword, self.flowType, nil) + +method executeTertiaryCommand*(self: BiometricsPasswordFailedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_failed_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_failed_state.nim new file mode 100644 index 0000000000..3a5f501d79 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_failed_state.nim @@ -0,0 +1,31 @@ +type + BiometricsPinFailedState* = ref object of State + +proc newBiometricsPinFailedState*(flowType: FlowType, backState: State): BiometricsPinFailedState = + result = BiometricsPinFailedState() + result.setup(flowType, StateType.BiometricsPinFailed, backState) + +proc delete*(self: BiometricsPinFailedState) = + self.State.delete + +method executePrimaryCommand*(self: BiometricsPinFailedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.tryToObtainDataFromKeychain() + +method executeSecondaryCommand*(self: BiometricsPinFailedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setUsePinFromBiometrics(true) + +method getNextSecondaryState*(self: BiometricsPinFailedState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + return createState(StateType.EnterPin, self.flowType, nil) + +method executeTertiaryCommand*(self: BiometricsPinFailedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.setPin("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method resolveKeycardNextState*(self: BiometricsPinFailedState, keycardFlowType: string, keycardEvent: KeycardEvent, + controller: Controller): State = + return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller) \ No newline at end of file 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 new file mode 100644 index 0000000000..19531b6812 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_pin_invalid_state.nim @@ -0,0 +1,31 @@ +type + BiometricsPinInvalidState* = ref object of State + +proc newBiometricsPinInvalidState*(flowType: FlowType, backState: State): BiometricsPinInvalidState = + result = BiometricsPinInvalidState() + result.setup(flowType, StateType.BiometricsPinInvalid, backState) + +proc delete*(self: BiometricsPinInvalidState) = + self.State.delete + +method executePrimaryCommand*(self: BiometricsPinInvalidState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.tryToObtainDataFromKeychain() + +method executeSecondaryCommand*(self: BiometricsPinInvalidState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setUsePinFromBiometrics(true) + +method getNextSecondaryState*(self: BiometricsPinInvalidState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + return createState(StateType.EnterPin, self.flowType, nil) + +method executeTertiaryCommand*(self: BiometricsPinInvalidState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.setPin("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method resolveKeycardNextState*(self: BiometricsPinInvalidState, keycardFlowType: string, keycardEvent: KeycardEvent, + controller: Controller): State = + return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/biometrics_ready_to_sign_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_ready_to_sign_state.nim new file mode 100644 index 0000000000..546b251479 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/biometrics_ready_to_sign_state.nim @@ -0,0 +1,31 @@ +type + BiometricsReadyToSignState* = ref object of State + +proc newBiometricsReadyToSignState*(flowType: FlowType, backState: State): BiometricsReadyToSignState = + result = BiometricsReadyToSignState() + result.setup(flowType, StateType.BiometricsReadyToSign, backState) + +proc delete*(self: BiometricsReadyToSignState) = + self.State.delete + +method executePrimaryCommand*(self: BiometricsReadyToSignState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.tryToObtainDataFromKeychain() + +method executeSecondaryCommand*(self: BiometricsReadyToSignState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setUsePinFromBiometrics(true) + +method getNextSecondaryState*(self: BiometricsReadyToSignState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + return createState(StateType.EnterPin, self.flowType, nil) + +method executeTertiaryCommand*(self: BiometricsReadyToSignState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.setPin("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method resolveKeycardNextState*(self: BiometricsReadyToSignState, keycardFlowType: string, keycardEvent: KeycardEvent, + controller: Controller): State = + return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/enter_biometrics_password_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/enter_biometrics_password_state.nim new file mode 100644 index 0000000000..5c25c1ea72 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/enter_biometrics_password_state.nim @@ -0,0 +1,31 @@ +type + EnterBiometricsPasswordState* = ref object of State + success: bool + +proc newEnterBiometricsPasswordState*(flowType: FlowType, backState: State): EnterBiometricsPasswordState = + result = EnterBiometricsPasswordState() + result.setup(flowType, StateType.EnterBiometricsPassword, backState) + result.success = false + +proc delete*(self: EnterBiometricsPasswordState) = + self.State.delete + +method executePrimaryCommand*(self: EnterBiometricsPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + let password = controller.getPassword() + self.success = controller.verifyPassword(password) + if self.success: + controller.tryToStoreDataToKeychain(password) + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + else: + controller.setKeycardData("wrong-pass") + +method getNextPrimaryState*(self: EnterBiometricsPasswordState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + if not self.success: + return createState(StateType.WrongBiometricsPassword, self.flowType, nil) + +method executeTertiaryCommand*(self: EnterBiometricsPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/enter_password_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/enter_password_state.nim new file mode 100644 index 0000000000..c11099e0d8 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/enter_password_state.nim @@ -0,0 +1,34 @@ +type + EnterPasswordState* = ref object of State + success: bool + +proc newEnterPasswordState*(flowType: FlowType, backState: State): EnterPasswordState = + result = EnterPasswordState() + result.setup(flowType, StateType.EnterPassword, backState) + result.success = false + +proc delete*(self: EnterPasswordState) = + self.State.delete + +method executePrimaryCommand*(self: EnterPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + let password = controller.getPassword() + self.success = controller.verifyPassword(password) + if self.success: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + else: + controller.setKeycardData("wrong-pass") + +method getNextPrimaryState*(self: EnterPasswordState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + if not self.success: + return createState(StateType.WrongPassword, self.flowType, nil) + +method executeSecondaryCommand*(self: EnterPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.tryToObtainDataFromKeychain() + +method executeTertiaryCommand*(self: EnterPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file 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 e190f0f579..f860f2852b 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 @@ -12,18 +12,25 @@ method getNextPrimaryState*(self: EnterPinState, controller: Controller): State if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: return createState(StateType.FactoryResetConfirmation, self.flowType, self) - return nil + if self.flowType == FlowType.Authentication: + if controller.getPin().len == PINLengthForStatusApp: + controller.enterKeycardPin(controller.getPin()) method executeSecondaryCommand*(self: EnterPinState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + if self.flowType == FlowType.Authentication: + controller.setUsePinFromBiometrics(false) + controller.tryToObtainDataFromKeychain() method executeTertiaryCommand*(self: EnterPinState, controller: Controller) = if self.flowType == FlowType.SetupNewKeycard or self.flowType == FlowType.FactoryReset: if controller.getPin().len == PINLengthForStatusApp: controller.enterKeycardPin(controller.getPin()) + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = @@ -33,7 +40,7 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke if self.flowType == FlowType.FactoryReset: if keycardFlowType == ResponseTypeValueEnterPIN and keycardEvent.error.len > 0 and - keycardEvent.error == RequestParamPIN: + keycardEvent.error == ErrorPIN: controller.setKeycardData($keycardEvent.pinRetries) if keycardEvent.pinRetries > 0: return createState(StateType.WrongPin, self.flowType, nil) @@ -51,7 +58,7 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke if self.flowType == FlowType.SetupNewKeycard: if keycardFlowType == ResponseTypeValueEnterPIN and keycardEvent.error.len > 0 and - keycardEvent.error == RequestParamPIN: + keycardEvent.error == ErrorPIN: controller.setKeycardData($keycardEvent.pinRetries) if keycardEvent.pinRetries > 0: return createState(StateType.WrongPin, self.flowType, nil) @@ -65,4 +72,25 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke return createState(StateType.MaxPinRetriesReached, self.flowType, nil) if keycardFlowType == ResponseTypeValueKeycardFlowResult: controller.setMetadataFromKeycard(keycardEvent.cardMetadata) - return createState(StateType.PinVerified, self.flowType, nil) \ No newline at end of file + return createState(StateType.PinVerified, self.flowType, nil) + if self.flowType == FlowType.Authentication: + if keycardFlowType == ResponseTypeValueEnterPIN and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorPIN: + controller.setKeycardData($keycardEvent.pinRetries) + if keycardEvent.pinRetries > 0: + if controller.loggedInUserUsesBiometricLogin() and not controller.usePinFromBiometrics(): + return createState(StateType.WrongKeychainPin, self.flowType, nil) + return createState(StateType.WrongPin, self.flowType, nil) + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueEnterPIN and + keycardEvent.error.len == 0: + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueEnterPUK and + keycardEvent.error.len == 0: + if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0: + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueKeycardFlowResult: + if keycardEvent.error.len == 0: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + return nil \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/enter_seed_phrase_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/enter_seed_phrase_state.nim index 6436d6f905..693b48628a 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/enter_seed_phrase_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/enter_seed_phrase_state.nim @@ -13,9 +13,7 @@ proc delete*(self: EnterSeedPhraseState) = method executePrimaryCommand*(self: EnterSeedPhraseState, controller: Controller) = if self.flowType == FlowType.SetupNewKeycard: self.verifiedSeedPhrase = controller.validSeedPhrase(controller.getSeedPhrase()) and - (not controller.getSelectedKeyPairIsProfile() or - controller.getSelectedKeyPairIsProfile() and - controller.seedPhraseRefersToLoggedInUser(controller.getSeedPhrase())) + controller.seedPhraseRefersToSelectedKeyPair(controller.getSeedPhrase()) if self.verifiedSeedPhrase: controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase()) else: @@ -38,5 +36,4 @@ method resolveKeycardNextState*(self: EnterSeedPhraseState, keycardFlowType: str if self.flowType == FlowType.SetupNewKeycard: if keycardFlowType == ResponseTypeValueKeycardFlowResult and keycardEvent.keyUid.len > 0: - controller.setKeyUid(keycardEvent.keyUid) return createState(StateType.MigratingKeyPair, self.flowType, nil) diff --git a/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim index d243064dd1..d574155ef2 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim @@ -18,6 +18,10 @@ method executePrimaryCommand*(self: InsertKeycardState, controller: Controller) self.flowType == FlowType.SetupNewKeycard: controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) +method executeTertiaryCommand*(self: InsertKeycardState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + method resolveKeycardNextState*(self: InsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = let state = ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller) diff --git a/src/app/modules/shared_modules/keycard_popup/internal/keycard_empty_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/keycard_empty_state.nim index a212348a7a..baf0bb618e 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/keycard_empty_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/keycard_empty_state.nim @@ -11,4 +11,8 @@ proc delete*(self: KeycardEmptyState) = method executePrimaryCommand*(self: KeycardEmptyState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method executeTertiaryCommand*(self: KeycardEmptyState, controller: Controller) = + if self.flowType == FlowType.Authentication: controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/keycard_inserted_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/keycard_inserted_state.nim index e560f4dba0..5d94441e4e 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/keycard_inserted_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/keycard_inserted_state.nim @@ -23,4 +23,8 @@ method getNextSecondaryState*(self: KeycardInsertedState, controller: Controller method executePrimaryCommand*(self: KeycardInsertedState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: - controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method executeTertiaryCommand*(self: KeycardInsertedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) diff --git a/src/app/modules/shared_modules/keycard_popup/internal/keycard_locked_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/keycard_locked_state.nim new file mode 100644 index 0000000000..b98668ade9 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/keycard_locked_state.nim @@ -0,0 +1,17 @@ +type + KeycardLockedState* = ref object of State + +proc newKeycardLockedState*(flowType: FlowType, backState: State): KeycardLockedState = + result = KeycardLockedState() + result.setup(flowType, StateType.KeycardLocked, backState) + +proc delete*(self: KeycardLockedState) = + self.State.delete + +method getNextPrimaryState*(self: KeycardLockedState, controller: Controller): State = + if self.flowType == FlowType.SetupNewKeycard: + return createState(StateType.FactoryResetConfirmation, self.flowType, self) + +method executeSecondaryCommand*(self: KeycardLockedState, controller: Controller) = + if self.flowType == FlowType.SetupNewKeycard: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/max_pin_retries_reached_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/max_pin_retries_reached_state.nim index b1059993c2..41fbcf7de6 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/max_pin_retries_reached_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/max_pin_retries_reached_state.nim @@ -11,10 +11,17 @@ proc delete*(self: MaxPinRetriesReachedState) = method getNextPrimaryState*(self: MaxPinRetriesReachedState, controller: Controller): State = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: + debug "Run Unlock Keycard flow... (not developed yet)" return createState(StateType.FactoryResetConfirmation, self.flowType, self) + if self.flowType == FlowType.Authentication: + debug "Run Unlock Keycard flow... (not developed yet)" return nil method executeSecondaryCommand*(self: MaxPinRetriesReachedState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: - controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method executeTertiaryCommand*(self: MaxPinRetriesReachedState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/migrating_key_pair_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/migrating_key_pair_state.nim index 57515eacc8..494ebf1850 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/migrating_key_pair_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/migrating_key_pair_state.nim @@ -10,19 +10,37 @@ proc newMigratingKeyPairState*(flowType: FlowType, backState: State): MigratingK proc delete*(self: MigratingKeyPairState) = self.State.delete -method executePrimaryCommand*(self: MigratingKeyPairState, controller: Controller) = +method procDoMigration(self: MigratingKeyPairState, controller: Controller) = if self.flowType == FlowType.SetupNewKeycard: - # Ran authentication popup and get pass from there... let password = controller.getPassword() - self.migrationSuccess = controller.verifyPassword(password) + controller.setPassword("") if controller.getSelectedKeyPairIsProfile(): - self.migrationSuccess = self.migrationSuccess and controller.convertToKeycardAccount(password) + self.migrationSuccess = controller.verifyPassword(password) + if not self.migrationSuccess: + return + let selectedKeyPairDto = controller.getSelectedKeyPairDto() + self.migrationSuccess = controller.addMigratedKeyPair(selectedKeyPairDto) if not self.migrationSuccess: return - controller.runStoreMetadataFlow(controller.getSelectedKeyPairName(), controller.getPin(), + if controller.getSelectedKeyPairIsProfile(): + self.migrationSuccess = self.migrationSuccess and controller.convertSelectedKeyPairToKeycardAccount(password) + if not self.migrationSuccess: + return + controller.runStoreMetadataFlow(selectedKeyPairDto.keypairName, controller.getPin(), controller.getSelectedKeyPairWalletPaths()) -method getNextPrimaryState*(self: MigratingKeyPairState, controller: Controller): State = +method executePrimaryCommand*(self: MigratingKeyPairState, controller: Controller) = + if self.flowType == FlowType.SetupNewKeycard: + if controller.getSelectedKeyPairIsProfile(): + controller.authenticateUser() + else: + self.procDoMigration(controller) + +method executeSecondaryCommand*(self: MigratingKeyPairState, controller: Controller) = + if self.flowType == FlowType.SetupNewKeycard: + self.procDoMigration(controller) + +method getNextSecondaryState*(self: MigratingKeyPairState, controller: Controller): State = if self.flowType == FlowType.SetupNewKeycard: if not self.migrationSuccess: return createState(StateType.KeyPairMigrateFailure, self.flowType, nil) diff --git a/src/app/modules/shared_modules/keycard_popup/internal/not_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/not_keycard_state.nim index f336b8c129..69478db1ea 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/not_keycard_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/not_keycard_state.nim @@ -11,4 +11,8 @@ proc delete*(self: NotKeycardState) = method executePrimaryCommand*(self: NotKeycardState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: - controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method executeTertiaryCommand*(self: NotKeycardState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/plugin_reader_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/plugin_reader_state.nim index 9441272d67..aa2a82d857 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/plugin_reader_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/plugin_reader_state.nim @@ -18,6 +18,10 @@ method executePrimaryCommand*(self: PluginReaderState, controller: Controller) = self.flowType == FlowType.SetupNewKeycard: controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) +method executeTertiaryCommand*(self: PluginReaderState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + method resolveKeycardNextState*(self: PluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = return ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/reading_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/reading_keycard_state.nim index 14257f3942..d7a025edb9 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/reading_keycard_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/reading_keycard_state.nim @@ -18,6 +18,10 @@ method executePrimaryCommand*(self: ReadingKeycardState, controller: Controller) self.flowType == FlowType.SetupNewKeycard: controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) +method executeTertiaryCommand*(self: ReadingKeycardState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + method getNextSecondaryState*(self: ReadingKeycardState, controller: Controller): State = let (flowType, flowEvent) = controller.getLastReceivedKeycardData() # this is used in case a keycard is not inserted in the moment when flow is run (we're animating an insertion) diff --git a/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim index 6e6af1ada3..5fefd85983 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim @@ -31,4 +31,5 @@ method resolveKeycardNextState*(self: RepeatPinState, keycardFlowType: string, k if keycardFlowType == ResponseTypeValueEnterMnemonic and keycardEvent.error.len > 0 and keycardEvent.error == ErrorLoadingKeys: + controller.setKeycardUid(keycardEvent.instanceUID) return createState(StateType.PinSet, self.flowType, nil) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/seed_phrase_enter_words_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/seed_phrase_enter_words_state.nim index ed62069a61..acaa5de029 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/seed_phrase_enter_words_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/seed_phrase_enter_words_state.nim @@ -26,6 +26,5 @@ method resolveKeycardNextState*(self: SeedPhraseEnterWordsState, keycardFlowType if self.flowType == FlowType.SetupNewKeycard: if keycardFlowType == ResponseTypeValueKeycardFlowResult and keycardEvent.keyUid.len > 0: - controller.setKeyUid(keycardEvent.keyUid) controller.removeMnemonic() return createState(StateType.MigratingKeyPair, self.flowType, nil) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/state.nim b/src/app/modules/shared_modules/keycard_popup/internal/state.nim index 306c6a69f8..1706239951 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/state.nim @@ -16,6 +16,7 @@ type StateType* {.pure.} = enum PinVerified = "PinVerified" EnterPin = "EnterPin" WrongPin = "WrongPin" + WrongKeychainPin = "WrongKeychainPin" MaxPinRetriesReached = "MaxPinRetriesReached" FactoryResetConfirmation = "FactoryResetConfirmation" FactoryResetConfirmationDisplayMetadata = "FactoryResetConfirmationDisplayMetadata" @@ -24,7 +25,9 @@ type StateType* {.pure.} = enum KeycardMetadataDisplay = "KeycardMetadataDisplay" KeycardEmpty = "KeycardEmpty" KeycardNotEmpty = "KeycardNotEmpty" + KeycardLocked = "KeycardLocked" NotKeycard = "NotKeycard" + WrongKeycard = "WrongKeycard" RecognizedKeycard = "RecognizedKeycard" SelectExistingKeyPair = "SelectExistingKeyPair" EnterSeedPhrase = "EnterSeedPhrase" @@ -34,6 +37,14 @@ type StateType* {.pure.} = enum KeyPairMigrateSuccess = "KeyPairMigrateSuccess" KeyPairMigrateFailure = "KeyPairMigrateFailure" MigratingKeyPair = "MigratingKeyPair" + EnterPassword = "EnterPassword" + WrongPassword = "WrongPassword" + BiometricsPasswordFailed = "BiometricsPasswordFailed" + BiometricsPinFailed = "BiometricsPinFailed" + BiometricsPinInvalid = "BiometricsPinInvalid" + EnterBiometricsPassword = "EnterBiometricsPassword" + WrongBiometricsPassword = "WrongBiometricsPassword" + BiometricsReadyToSign = "BiometricsReadyToSign" ## This is the base class for all state we may have in onboarding/login flow. diff --git a/src/app/modules/shared_modules/keycard_popup/internal/state_factory.nim b/src/app/modules/shared_modules/keycard_popup/internal/state_factory.nim index 0c0f76f402..d0264741e0 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/state_factory.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/state_factory.nim @@ -21,7 +21,13 @@ proc getPredefinedKeycardData*(currValue: string, value: PredefinedKeycardData, proc ensureReaderAndCardPresence*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State proc ensureReaderAndCardPresenceAndResolveNextState*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State +include biometrics_password_failed_state +include biometrics_pin_failed_state +include biometrics_pin_invalid_state +include biometrics_ready_to_sign_state include create_pin_state +include enter_biometrics_password_state +include enter_password_state include enter_pin_state include enter_seed_phrase_state include factory_reset_confirmation_displayed_metadata_state @@ -35,6 +41,7 @@ include keycard_empty_state include keycard_inserted_state include keycard_metadata_display_state include keycard_not_empty_state +include keycard_locked_state include max_pin_retries_reached_state include migrating_key_pair_state include not_keycard_state @@ -47,7 +54,11 @@ include repeat_pin_state include seed_phrase_display_state include seed_phrase_enter_words_state include select_existing_key_pair_state +include wrong_biometrics_password_state +include wrong_keycard_state +include wrong_password_state include wrong_pin_state +include wrong_keychain_pin_state include wrong_seed_phrase_state proc getPredefinedKeycardData*(currValue: string, value: PredefinedKeycardData, add: bool): string = @@ -67,8 +78,20 @@ proc getPredefinedKeycardData*(currValue: string, value: PredefinedKeycardData, return if add: $(value.int) else: "" proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State = + if stateToBeCreated == StateType.BiometricsPasswordFailed: + return newBiometricsPasswordFailedState(flowType, backState) + if stateToBeCreated == StateType.BiometricsPinFailed: + return newBiometricsPinFailedState(flowType, backState) + if stateToBeCreated == StateType.BiometricsPinInvalid: + return newBiometricsPinInvalidState(flowType, backState) + if stateToBeCreated == StateType.BiometricsReadyToSign: + return newBiometricsReadyToSignState(flowType, backState) if stateToBeCreated == StateType.CreatePin: return newCreatePinState(flowType, backState) + if stateToBeCreated == StateType.EnterBiometricsPassword: + return newEnterBiometricsPasswordState(flowType, backState) + if stateToBeCreated == StateType.EnterPassword: + return newEnterPasswordState(flowType, backState) if stateToBeCreated == StateType.EnterPin: return newEnterPinState(flowType, backState) if stateToBeCreated == StateType.EnterSeedPhrase: @@ -95,6 +118,8 @@ proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: St return newKeycardMetadataDisplayState(flowType, backState) if stateToBeCreated == StateType.KeycardNotEmpty: return newKeycardNotEmptyState(flowType, backState) + if stateToBeCreated == StateType.KeycardLocked: + return newKeycardLockedState(flowType, backState) if stateToBeCreated == StateType.MaxPinRetriesReached: return newMaxPinRetriesReachedState(flowType, backState) if stateToBeCreated == StateType.MigratingKeyPair: @@ -119,32 +144,41 @@ proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: St return newSeedPhraseEnterWordsState(flowType, backState) if stateToBeCreated == StateType.SelectExistingKeyPair: return newSelectExistingKeyPairState(flowType, backState) + if stateToBeCreated == StateType.WrongBiometricsPassword: + return newWrongBiometricsPasswordState(flowType, backState) + if stateToBeCreated == StateType.WrongKeycard: + return newWrongKeycardState(flowType, backState) + if stateToBeCreated == StateType.WrongPassword: + return newWrongPasswordState(flowType, backState) if stateToBeCreated == StateType.WrongPin: return newWrongPinState(flowType, backState) + if stateToBeCreated == StateType.WrongKeychainPin: + return newWrongKeychainPinState(flowType, backState) if stateToBeCreated == StateType.WrongSeedPhrase: return newWrongSeedPhraseState(flowType, backState) error "No implementation available for state ", state=stateToBeCreated proc ensureReaderAndCardPresence*(state: State, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = - ## Handling factory reset flow - if state.flowType == FlowType.FactoryReset: - if keycardFlowType == ResponseTypeValueKeycardFlowResult and - keycardEvent.error.len > 0 and - keycardEvent.error == ErrorConnection: - controller.resumeCurrentFlowLater() - if state.stateType == StateType.PluginReader: - return nil - return createState(StateType.PluginReader, state.flowType, nil) - if keycardFlowType == ResponseTypeValueInsertCard and - keycardEvent.error.len > 0 and - keycardEvent.error == ErrorConnection: - if state.stateType == StateType.InsertKeycard: - return nil - return createState(StateType.InsertKeycard, state.flowType, state) - if keycardFlowType == ResponseTypeValueCardInserted: - controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false)) - return createState(StateType.KeycardInserted, state.flowType, nil) + ## Handling factory reset or authentication flow + if state.flowType == FlowType.FactoryReset or + state.flowType == FlowType.Authentication: + if keycardFlowType == ResponseTypeValueKeycardFlowResult and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorConnection: + controller.resumeCurrentFlowLater() + if state.stateType == StateType.PluginReader: + return nil + return createState(StateType.PluginReader, state.flowType, nil) + if keycardFlowType == ResponseTypeValueInsertCard and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorConnection: + if state.stateType == StateType.InsertKeycard: + return nil + return createState(StateType.InsertKeycard, state.flowType, nil) + if keycardFlowType == ResponseTypeValueCardInserted: + controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false)) + return createState(StateType.KeycardInserted, state.flowType, nil) ## Handling setup new keycard flow if state.flowType == FlowType.SetupNewKeycard: @@ -206,6 +240,8 @@ proc ensureReaderAndCardPresenceAndResolveNextState*(state: State, keycardFlowTy return createState(StateType.NotKeycard, state.flowType, nil) if keycardEvent.error == ErrorHasKeys: return createState(StateType.KeycardNotEmpty, state.flowType, nil) + if keycardEvent.error == ErrorFreePairingSlots: + return createState(StateType.KeycardLocked, state.flowType, nil) if keycardFlowType == ResponseTypeValueEnterPIN: if controller.getCurrentKeycardServiceFlow() == KCSFlowType.GetMetadata: return createState(StateType.EnterPin, state.flowType, nil) @@ -228,4 +264,35 @@ proc ensureReaderAndCardPresenceAndResolveNextState*(state: State, keycardFlowTy keycardEvent.error == ErrorRequireInit: if state.stateType == StateType.SelectExistingKeyPair: return createState(StateType.RecognizedKeycard, state.flowType, state) - return createState(StateType.RecognizedKeycard, state.flowType, state.getBackState) \ No newline at end of file + return createState(StateType.RecognizedKeycard, state.flowType, state.getBackState) + + ## Handling authentiaction flow + if state.flowType == FlowType.Authentication: + if keycardFlowType == ResponseTypeValueSwapCard and + keycardEvent.error.len > 0: + if keycardEvent.error == ErrorNotAKeycard: + return createState(StateType.NotKeycard, state.flowType, nil) + if keycardEvent.error == ErrorNoKeys: + return createState(StateType.KeycardEmpty, state.flowType, nil) + if keycardFlowType == ResponseTypeValueEnterPIN: + if keycardEvent.keyUid == controller.getKeyUidWhichIsBeingAuthenticating(): + if controller.loggedInUserUsesBiometricLogin(): + if keycardEvent.error.len > 0 and + keycardEvent.error == ErrorPIN: + controller.setKeycardData($keycardEvent.pinRetries) + if keycardEvent.pinRetries > 0: + if not controller.usePinFromBiometrics(): + return createState(StateType.WrongKeychainPin, state.flowType, nil) + return createState(StateType.WrongPin, state.flowType, nil) + return createState(StateType.MaxPinRetriesReached, state.flowType, nil) + return createState(StateType.BiometricsReadyToSign, state.flowType, nil) + return createState(StateType.EnterPin, state.flowType, nil) + return createState(StateType.WrongKeycard, state.flowType, nil) + if keycardFlowType == ResponseTypeValueEnterPUK and + keycardEvent.error.len == 0: + if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0: + return createState(StateType.MaxPinRetriesReached, state.flowType, nil) + if keycardFlowType == ResponseTypeValueKeycardFlowResult: + if keycardEvent.error.len == 0: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + return nil \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_biometrics_password_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_biometrics_password_state.nim new file mode 100644 index 0000000000..4639950e3b --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_biometrics_password_state.nim @@ -0,0 +1,27 @@ +type + WrongBiometricsPasswordState* = ref object of State + success: bool + +proc newWrongBiometricsPasswordState*(flowType: FlowType, backState: State): WrongBiometricsPasswordState = + result = WrongBiometricsPasswordState() + result.setup(flowType, StateType.WrongBiometricsPassword, backState) + result.success = false + +proc delete*(self: WrongBiometricsPasswordState) = + self.State.delete + +method executePrimaryCommand*(self: WrongBiometricsPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setKeycardData("") + let password = controller.getPassword() + self.success = controller.verifyPassword(password) + if self.success: + controller.tryToStoreDataToKeychain(password) + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + else: + controller.setKeycardData("wrong-pass") + +method executeTertiaryCommand*(self: WrongBiometricsPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_keycard_state.nim new file mode 100644 index 0000000000..30840908ad --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_keycard_state.nim @@ -0,0 +1,13 @@ +type + WrongKeycardState* = ref object of State + +proc newWrongKeycardState*(flowType: FlowType, backState: State): WrongKeycardState = + result = WrongKeycardState() + result.setup(flowType, StateType.WrongKeycard, backState) + +proc delete*(self: WrongKeycardState) = + self.State.delete + +method executeTertiaryCommand*(self: WrongKeycardState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_keychain_pin_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_keychain_pin_state.nim new file mode 100644 index 0000000000..ca73912ce1 --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_keychain_pin_state.nim @@ -0,0 +1,42 @@ +type + WrongKeychainPinState* = ref object of State + +proc newWrongKeychainPinState*(flowType: FlowType, backState: State): WrongKeychainPinState = + result = WrongKeychainPinState() + result.setup(flowType, StateType.WrongKeychainPin, backState) + +proc delete*(self: WrongKeychainPinState) = + self.State.delete + +method getNextPrimaryState*(self: WrongKeychainPinState, controller: Controller): State = + if self.flowType == FlowType.Authentication: + if controller.getPin().len == PINLengthForStatusApp: + controller.enterKeycardPin(controller.getPin()) + return nil + +method executeTertiaryCommand*(self: WrongKeychainPinState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method resolveKeycardNextState*(self: WrongKeychainPinState, keycardFlowType: string, keycardEvent: KeycardEvent, + controller: Controller): State = + let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller) + if not state.isNil: + return state + if self.flowType == FlowType.Authentication: + if keycardFlowType == ResponseTypeValueEnterPIN and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorPIN: + controller.setKeycardData($keycardEvent.pinRetries) + if keycardEvent.pinRetries > 0: + return self + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueEnterPUK and + keycardEvent.error.len == 0: + if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0: + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueKeycardFlowResult: + if keycardEvent.error.len == 0: + controller.tryToStoreDataToKeychain(controller.getPin()) + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + return nil \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_password_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_password_state.nim new file mode 100644 index 0000000000..c5378df72d --- /dev/null +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_password_state.nim @@ -0,0 +1,30 @@ +type + WrongPasswordState* = ref object of State + success: bool + +proc newWrongPasswordState*(flowType: FlowType, backState: State): WrongPasswordState = + result = WrongPasswordState() + result.setup(flowType, StateType.WrongPassword, backState) + result.success = false + +proc delete*(self: WrongPasswordState) = + self.State.delete + +method executePrimaryCommand*(self: WrongPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setKeycardData("") + let password = controller.getPassword() + self.success = controller.verifyPassword(password) + if self.success: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + else: + controller.setKeycardData("wrong-pass") + +method executeSecondaryCommand*(self: WrongPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.tryToObtainDataFromKeychain() + +method executeTertiaryCommand*(self: WrongPasswordState, controller: Controller) = + if self.flowType == FlowType.Authentication: + controller.setPassword("") + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_pin_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_pin_state.nim index faaa994c1c..d1f98aeb83 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/wrong_pin_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_pin_state.nim @@ -12,18 +12,26 @@ method getNextPrimaryState*(self: WrongPinState, controller: Controller): State if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: return createState(StateType.FactoryResetConfirmation, self.flowType, self) + if self.flowType == FlowType.Authentication: + if controller.getPin().len == PINLengthForStatusApp: + controller.enterKeycardPin(controller.getPin()) return nil method executeSecondaryCommand*(self: WrongPinState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + if self.flowType == FlowType.Authentication: + controller.setUsePinFromBiometrics(false) + controller.tryToObtainDataFromKeychain() method executeTertiaryCommand*(self: WrongPinState, controller: Controller) = if self.flowType == FlowType.FactoryReset or self.flowType == FlowType.SetupNewKeycard: if controller.getPin().len == PINLengthForStatusApp: controller.enterKeycardPin(controller.getPin()) + if self.flowType == FlowType.Authentication: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) method resolveKeycardNextState*(self: WrongPinState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = @@ -33,7 +41,7 @@ method resolveKeycardNextState*(self: WrongPinState, keycardFlowType: string, ke if self.flowType == FlowType.FactoryReset: if keycardFlowType == ResponseTypeValueEnterPIN and keycardEvent.error.len > 0 and - keycardEvent.error == RequestParamPIN: + keycardEvent.error == ErrorPIN: controller.setKeycardData($keycardEvent.pinRetries) if keycardEvent.pinRetries > 0: return self @@ -48,7 +56,7 @@ method resolveKeycardNextState*(self: WrongPinState, keycardFlowType: string, ke if self.flowType == FlowType.SetupNewKeycard: if keycardFlowType == ResponseTypeValueEnterPIN and keycardEvent.error.len > 0 and - keycardEvent.error == RequestParamPIN: + keycardEvent.error == ErrorPIN: controller.setKeycardData($keycardEvent.pinRetries) if keycardEvent.pinRetries > 0: return self @@ -59,4 +67,20 @@ method resolveKeycardNextState*(self: WrongPinState, keycardFlowType: string, ke return createState(StateType.MaxPinRetriesReached, self.flowType, nil) if keycardFlowType == ResponseTypeValueKeycardFlowResult: controller.setMetadataFromKeycard(keycardEvent.cardMetadata) - return createState(StateType.PinVerified, self.flowType, nil) \ No newline at end of file + return createState(StateType.PinVerified, self.flowType, nil) + if self.flowType == FlowType.Authentication: + if keycardFlowType == ResponseTypeValueEnterPIN and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorPIN: + controller.setKeycardData($keycardEvent.pinRetries) + if keycardEvent.pinRetries > 0: + return self + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueEnterPUK and + keycardEvent.error.len == 0: + if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0: + return createState(StateType.MaxPinRetriesReached, self.flowType, nil) + if keycardFlowType == ResponseTypeValueKeycardFlowResult: + if keycardEvent.error.len == 0: + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + return nil \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim index 6d2ac4cd4d..e11ece8a26 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim @@ -17,7 +17,7 @@ method executePrimaryCommand*(self: WrongSeedPhraseState, controller: Controller controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false)) sleep(500) # just to shortly remove text on the UI side self.verifiedSeedPhrase = controller.validSeedPhrase(controller.getSeedPhrase()) and - controller.seedPhraseRefersToLoggedInUser(controller.getSeedPhrase()) + controller.seedPhraseRefersToSelectedKeyPair(controller.getSeedPhrase()) if self.verifiedSeedPhrase: controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase()) else: @@ -35,6 +35,5 @@ method resolveKeycardNextState*(self: WrongSeedPhraseState, keycardFlowType: str if self.flowType == FlowType.SetupNewKeycard: if keycardFlowType == ResponseTypeValueKeycardFlowResult and keycardEvent.keyUid.len > 0: - controller.setKeyUid(keycardEvent.keyUid) controller.setKeycardData(getPredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false)) return createState(StateType.MigratingKeyPair, self.flowType, nil) 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 b5238cec9c..b0e50beb23 100644 --- a/src/app/modules/shared_modules/keycard_popup/io_interface.nim +++ b/src/app/modules/shared_modules/keycard_popup/io_interface.nim @@ -66,7 +66,7 @@ method onTertiaryActionClicked*(self: AccessInterface) {.base.} = method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} = raise newException(ValueError, "No implementation available") -method runFlow*(self: AccessInterface, flowToRun: FlowType) {.base.} = +method runFlow*(self: AccessInterface, flowToRun: FlowType, keyUid = "", bip44Path = "", txHash = "") {.base.} = raise newException(ValueError, "No implementation available") method setPin*(self: AccessInterface, value: string) {.base.} = @@ -105,6 +105,17 @@ method migratingProfileKeyPair*(self: AccessInterface): bool {.base.} = method isProfileKeyPairMigrated*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") +method getSigningPhrase*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method onUserAuthenticated*(self: AccessInterface, password: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method keychainObtainedDataFailure*(self: AccessInterface, errorDescription: string, errorType: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method keychainObtainedDataSuccess*(self: AccessInterface, data: string) {.base.} = + raise newException(ValueError, "No implementation available") type DelegateInterface* = concept c diff --git a/src/app/modules/shared_modules/keycard_popup/models/key_pair_item.nim b/src/app/modules/shared_modules/keycard_popup/models/key_pair_item.nim index 685707c513..af0765fe9c 100644 --- a/src/app/modules/shared_modules/keycard_popup/models/key_pair_item.nim +++ b/src/app/modules/shared_modules/keycard_popup/models/key_pair_item.nim @@ -20,6 +20,8 @@ type type KeyPairItem* = ref object of RootObj pubKey: string + keyUid: string + locked: bool name: string image: string icon: string @@ -28,15 +30,18 @@ type accounts: seq[WalletAccountDetails] proc initKeyPairItem*( - pubKey: string, - name: string, - image: string, - icon: string, - pairType: KeyPairType, - derivedFrom: string + pubKey = "", + keyUid = "", + locked = false, + name = "", + image = "", + icon = "", + pairType = KeyPairType.Unknown, + derivedFrom = "" ): KeyPairItem = result = KeyPairItem() result.pubKey = pubKey + result.keyUid = keyUid result.name = name result.image = image result.icon = icon @@ -56,18 +61,38 @@ proc `$`*(self: KeyPairItem): string = proc pubKey*(self: KeyPairItem): string {.inline.} = self.pubKey +proc setPubKey*(self: KeyPairItem, value: string) {.inline.} = + self.pubKey = value + +proc keyUid*(self: KeyPairItem): string {.inline.} = + self.keyUid +proc setKeyUid*(self: KeyPairItem, value: string) {.inline.} = + self.keyUid = value + +proc locked*(self: KeyPairItem): bool {.inline.} = + self.locked +proc setLocked*(self: KeyPairItem, value: bool) {.inline.} = + self.locked = value proc name*(self: KeyPairItem): string {.inline.} = self.name +proc setName*(self: KeyPairItem, value: string) {.inline.} = + self.name = value proc image*(self: KeyPairItem): string {.inline.} = self.image +proc setImage*(self: KeyPairItem, value: string) {.inline.} = + self.image = value proc icon*(self: KeyPairItem): string {.inline.} = self.icon +proc setIcon*(self: KeyPairItem, value: string) {.inline.} = + self.icon = value proc pairType*(self: KeyPairItem): KeyPairType {.inline.} = self.pairType +proc setPairType*(self: KeyPairItem, value: KeyPairType) {.inline.} = + self.pairType = value proc derivedFrom*(self: KeyPairItem): string {.inline.} = self.derivedFrom diff --git a/src/app/modules/shared_modules/keycard_popup/models/key_pair_model.nim b/src/app/modules/shared_modules/keycard_popup/models/key_pair_model.nim index 136ee425aa..f852d51ae6 100644 --- a/src/app/modules/shared_modules/keycard_popup/models/key_pair_model.nim +++ b/src/app/modules/shared_modules/keycard_popup/models/key_pair_model.nim @@ -4,6 +4,7 @@ import key_pair_item type ModelRole {.pure.} = enum PubKey = UserRole + 1 + Locked Name Image Icon @@ -52,6 +53,7 @@ QtObject: method roleNames(self: KeyPairModel): Table[int, string] = { ModelRole.PubKey.int: "pubKey", + ModelRole.Locked.int: "locked", ModelRole.Name.int: "name", ModelRole.Image.int: "image", ModelRole.Icon.int: "icon", @@ -70,6 +72,8 @@ QtObject: case enumRole: of ModelRole.PubKey: result = newQVariant(item.pubKey) + of ModelRole.Locked: + result = newQVariant(item.locked) of ModelRole.Name: result = newQVariant(item.name) of ModelRole.Image: diff --git a/src/app/modules/shared_modules/keycard_popup/models/key_pair_selected_item.nim b/src/app/modules/shared_modules/keycard_popup/models/key_pair_selected_item.nim index 9571288c06..c911ec3673 100644 --- a/src/app/modules/shared_modules/keycard_popup/models/key_pair_selected_item.nim +++ b/src/app/modules/shared_modules/keycard_popup/models/key_pair_selected_item.nim @@ -13,10 +13,25 @@ QtObject: result.QObject.setup proc keyPairSelectedItemChanged*(self: KeyPairSelectedItem) {.signal.} + proc keyPairSelectedItemLockedChanged*(self: KeyPairSelectedItem) {.signal.} + proc keyPairSelectedItemIconChanged*(self: KeyPairSelectedItem) {.signal.} proc setItem*(self: KeyPairSelectedItem, item: KeyPairItem) = self.item = item self.keyPairSelectedItemChanged() + self.keyPairSelectedItemLockedChanged() + self.keyPairSelectedItemIconChanged() + + proc updateLockedState*(self: KeyPairSelectedItem, locked: bool) = + if self.item.isNil: + return + self.item.setLocked(locked) + if locked: + self.item.setIcon("lock") + else: + self.item.setIcon("keycard") + self.keyPairSelectedItemLockedChanged() + self.keyPairSelectedItemIconChanged() proc getPubKey*(self: KeyPairSelectedItem): string {.slot.} = if(self.item.isNil): @@ -26,6 +41,14 @@ QtObject: read = getPubKey notify = keyPairSelectedItemChanged + proc getLocked*(self: KeyPairSelectedItem): bool {.slot.} = + if(self.item.isNil): + return false + return self.item.locked() + QtProperty[bool] locked: + read = getLocked + notify = keyPairSelectedItemLockedChanged + proc getName*(self: KeyPairSelectedItem): string {.slot.} = if(self.item.isNil): return "" @@ -48,7 +71,7 @@ QtObject: return self.item.icon() QtProperty[string] icon: read = getIcon - notify = keyPairSelectedItemChanged + notify = keyPairSelectedItemIconChanged proc getPairType*(self: KeyPairSelectedItem): int {.slot.} = if(self.item.isNil): diff --git a/src/app/modules/shared_modules/keycard_popup/module.nim b/src/app/modules/shared_modules/keycard_popup/module.nim index 47905a6bcb..6bc3b2730f 100644 --- a/src/app/modules/shared_modules/keycard_popup/module.nim +++ b/src/app/modules/shared_modules/keycard_popup/module.nim @@ -48,6 +48,10 @@ proc newModule*[T](delegate: T, result.initialized = false result.authenticationPopupIsAlreadyRunning = false +## Forward declaration +proc updateKeyPairItemIfDataAreKnown[T](self: Module[T], address: string, item: var KeyPairItem): bool +proc generateRandomColor[T](self: Module[T]): string + method delete*[T](self: Module[T]) = self.view.delete self.viewVariant.delete @@ -104,6 +108,14 @@ method migratingProfileKeyPair*[T](self: Module[T]): bool = method isProfileKeyPairMigrated*[T](self: Module[T]): bool = return self.controller.getLoggedInAccount().keycardPairing.len > 0 +method getSigningPhrase*[T](self: Module[T]): string = + return self.controller.getSigningPhrase() + +proc authenticationKeyPairUpdate[T](self: Module[T], currFlowType: FlowType, nextStateType: StateType) = + ## special check only for authentication flow + if currFlowType == FlowType.Authentication: + self.view.setLockedPropForKeyPairForAuthentication(nextStateType == StateType.MaxPinRetriesReached) + method onBackActionClicked*[T](self: Module[T]) = let currStateObj = self.view.currentStateObj() if currStateObj.isNil: @@ -112,6 +124,7 @@ method onBackActionClicked*[T](self: Module[T]) = debug "sm_back_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType() currStateObj.executeBackCommand(self.controller) let backState = currStateObj.getBackState() + self.authenticationKeyPairUpdate(backState.flowType(), backState.stateType()) self.view.setCurrentState(backState) debug "sm_back_action - set state", setCurrFlow=backState.flowType(), newCurrState=backState.stateType() currStateObj.delete() @@ -126,6 +139,7 @@ method onPrimaryActionClicked*[T](self: Module[T]) = let nextState = currStateObj.getNextPrimaryState(self.controller) if nextState.isNil: return + self.authenticationKeyPairUpdate(nextState.flowType(), nextState.stateType()) self.view.setCurrentState(nextState) debug "sm_primary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() @@ -139,6 +153,7 @@ method onSecondaryActionClicked*[T](self: Module[T]) = let nextState = currStateObj.getNextSecondaryState(self.controller) if nextState.isNil: return + self.authenticationKeyPairUpdate(nextState.flowType(), nextState.stateType()) self.view.setCurrentState(nextState) debug "sm_secondary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() @@ -152,6 +167,7 @@ method onTertiaryActionClicked*[T](self: Module[T]) = let nextState = currStateObj.getNextTertiaryState(self.controller) if nextState.isNil: return + self.authenticationKeyPairUpdate(nextState.flowType(), nextState.stateType()) self.view.setCurrentState(nextState) debug "sm_tertiary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() @@ -164,6 +180,7 @@ method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEv let nextState = self.tmpLocalState.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller) if nextState.isNil: return + self.authenticationKeyPairUpdate(nextState.flowType(), nextState.stateType()) self.view.setCurrentState(nextState) self.controller.readyToDisplayPopup() debug "sm_on_keycard_response - from_local - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() @@ -172,10 +189,18 @@ method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEv let nextState = currStateObj.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller) if nextState.isNil: return + self.authenticationKeyPairUpdate(nextState.flowType(), nextState.stateType()) self.view.setCurrentState(nextState) debug "sm_on_keycard_response - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType() -proc prepareKeyPairsModel[T](self: Module[T]) = +proc buildKeyPairsList[T](self: Module[T]): seq[KeyPairItem] = + let keyPairMigrated = proc(keyUid: string): bool = + result = false + let migratedKeyPairs = self.controller.getAllMigratedKeyPairs() + for kp in migratedKeyPairs: + if kp.keyUid == keyUid: + return true + let findItemByDerivedFromAddress = proc(items: seq[KeyPairItem], address: string): KeyPairItem = if address.len == 0: return nil @@ -193,7 +218,7 @@ proc prepareKeyPairsModel[T](self: Module[T]) = let accounts = self.controller.getWalletAccounts() var items: seq[KeyPairItem] for a in accounts: - if a.isChat or a.walletType == WalletTypeWatch: + if a.isChat or a.walletType == WalletTypeWatch or keyPairMigrated(a.keyUid): continue var item = findItemByDerivedFromAddress(items, a.derivedfrom) if a.walletType == WalletTypeDefaultStatusAccount or a.walletType == WalletTypeGenerated: @@ -201,6 +226,8 @@ proc prepareKeyPairsModel[T](self: Module[T]) = continue if item.isNil: item = initKeyPairItem(pubKey = a.publicKey, + keyUid = a.keyUid, + locked = false, name = singletonInstance.userProfile.getName(), image = singletonInstance.userProfile.getIcon(), icon = "", @@ -216,6 +243,8 @@ proc prepareKeyPairsModel[T](self: Module[T]) = let diffImports = countOfKeyPairsForType(items, KeyPairType.SeedImport) if item.isNil: item = initKeyPairItem(pubKey = a.publicKey, + keyUid = a.keyUid, + locked = false, name = "Seed Phrase " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is image = "", icon = "key_pair_seed_phrase", @@ -228,43 +257,86 @@ proc prepareKeyPairsModel[T](self: Module[T]) = let diffImports = countOfKeyPairsForType(items, KeyPairType.PrivateKeyImport) if item.isNil: item = initKeyPairItem(pubKey = a.publicKey, + keyUid = a.keyUid, + locked = false, name = "Key " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is image = "", icon = "key_pair_private_key", - pairType = KeyPairType.SeedImport, + pairType = KeyPairType.PrivateKeyImport, derivedFrom = a.derivedfrom) items.add(item) item.addAccount(a.name, a.path, a.address, a.emoji, a.color, icon = "", balance = 0.0) continue if items.len == 0: debug "sm_there is no any key pair for the logged in user that is not already migrated to a keycard" - self.view.createKeyPairModel(items) + return items -method runFlow*[T](self: Module[T], flowToRun: FlowType) = +proc prepareKeyPairItemForAuthentication[T](self: Module[T], keyUid: string) = + var item = initKeyPairItem() + self.view.createKeyPairForAuthentication() + let items = self.buildKeyPairsList() + for it in items: + if it.keyUid == keyUid: + item = it + break + if item.name.len == 0: + error "sm_cannot find keypair among known keypairs for the given keyUid", keyUid=keyUid + item.setPubKey("") + item.setImage("") + item.setIcon("keycard") + item.setPairType(KeyPairType.Unknown) + self.view.setKeyPairForAuthentication(item) + +method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Path = "", txHash = "") = + ## In case of `Authentication` if we're signing a transaction we need to provide a key uid of a keypair that an account + ## we want to sign a transaction for belongs to. If we're just doing an authentication for a logged in user, then + ## default key uid is always the key uid of the logged in user. if flowToRun == FlowType.General: self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) error "sm_cannot run an general flow" return if not self.initialized: + self.initialized = true self.controller.init() if flowToRun == FlowType.FactoryReset: - self.prepareKeyPairsModel() + let items = self.buildKeyPairsList() + self.view.createKeyPairModel(items) self.tmpLocalState = newReadingKeycardState(flowToRun, nil) self.controller.runGetMetadataFlow() return if flowToRun == FlowType.SetupNewKeycard: - self.prepareKeyPairsModel() + let items = self.buildKeyPairsList() + self.view.createKeyPairModel(items) self.view.setCurrentState(newSelectExistingKeyPairState(flowToRun, nil)) self.controller.readyToDisplayPopup() return + if flowToRun == FlowType.Authentication: + self.controller.connectKeychainSignals() + if keyUid.len > 0: + self.prepareKeyPairItemForAuthentication(keyUid) + self.tmpLocalState = newReadingKeycardState(flowToRun, nil) + self.controller.runSignFlow(keyUid, bip44Path, txHash) + return + if self.controller.loggedInUserUsesBiometricLogin(): + self.controller.tryToObtainDataFromKeychain() + return + self.view.setCurrentState(newEnterPasswordState(flowToRun, nil)) + self.authenticationPopupIsAlreadyRunning = true + self.controller.readyToDisplayPopup() + return method setSelectedKeyPair*[T](self: Module[T], item: KeyPairItem) = var paths: seq[string] + var keyPairDto = KeyPairDto(keycardUid: "", # will be set during migration + keypairName: item.name, + keycardLocked: item.locked, + keyUid: item.keyUid) for a in item.accountsAsArr(): paths.add(a.path) + keyPairDto.accountsAddresses.add(a.address) self.controller.setSelectedKeyPairIsProfile(item.pairType == KeyPairType.Profile) - self.controller.setSelectedKeyPairName(item.name) self.controller.setSelectedKeyPairWalletPaths(paths) + self.controller.setSelectedKeyPairDto(keyPairDto) proc generateRandomColor[T](self: Module[T]): string = let r = rand(0 .. 255) @@ -286,6 +358,8 @@ proc updateKeyPairItemIfDataAreKnown[T](self: Module[T], address: string, item: method setKeyPairStoredOnKeycard*[T](self: Module[T], cardMetadata: CardMetadata) = var item = initKeyPairItem(pubKey = "", + keyUid = "", + locked = false, name = cardMetadata.name, image = "", icon = "keycard", @@ -300,4 +374,45 @@ method setKeyPairStoredOnKeycard*[T](self: Module[T], cardMetadata: CardMetadata item.addAccount(name = "", wa.path, wa.address, emoji = "", color = self.generateRandomColor(), icon = "wallet", balance) self.view.setKeyPairStoredOnKeycardIsKnown(knownKeyPair) self.view.setKeyPairStoredOnKeycard(item) - \ No newline at end of file + +method onUserAuthenticated*[T](self: Module[T], password: string) = + let currStateObj = self.view.currentStateObj() + if not currStateObj.isNil and currStateObj.flowType() == FlowType.SetupNewKeycard: + self.controller.setPassword(password) + self.onSecondaryActionClicked() + +method keychainObtainedDataFailure*[T](self: Module[T], errorDescription: string, errorType: string) = + let currStateObj = self.view.currentStateObj() + if currStateObj.isNil or + currStateObj.stateType() == StateType.EnterPassword or + currStateObj.stateType() == StateType.WrongPassword or + currStateObj.stateType() == StateType.BiometricsPasswordFailed: + self.view.setCurrentState(newBiometricsPasswordFailedState(FlowType.Authentication, nil)) + if not self.authenticationPopupIsAlreadyRunning: + self.authenticationPopupIsAlreadyRunning = true + self.controller.readyToDisplayPopup() + return + if not currStateObj.isNil: + self.view.setCurrentState(newBiometricsPinFailedState(FlowType.Authentication, nil)) + +method keychainObtainedDataSuccess*[T](self: Module[T], data: string) = + let currStateObj = self.view.currentStateObj() + if currStateObj.isNil or + currStateObj.stateType() == StateType.EnterPassword or + currStateObj.stateType() == StateType.WrongPassword or + currStateObj.stateType() == StateType.BiometricsPasswordFailed: + if self.controller.verifyPassword(data): + self.controller.setPassword(data) + self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) + else: + self.view.setCurrentState(newEnterBiometricsPasswordState(FlowType.Authentication, nil)) + if not self.authenticationPopupIsAlreadyRunning: + self.authenticationPopupIsAlreadyRunning = true + self.controller.readyToDisplayPopup() + return + if not currStateObj.isNil: + if data.len == PINLengthForStatusApp: + self.controller.enterKeycardPin(data) + else: + self.view.setCurrentState(newBiometricsPinInvalidState(FlowType.Authentication, nil)) + diff --git a/src/app/modules/shared_modules/keycard_popup/view.nim b/src/app/modules/shared_modules/keycard_popup/view.nim index 1d0e615aa7..312446689d 100644 --- a/src/app/modules/shared_modules/keycard_popup/view.nim +++ b/src/app/modules/shared_modules/keycard_popup/view.nim @@ -16,6 +16,8 @@ QtObject: keyPairStoredOnKeycardIsKnown: bool keyPairStoredOnKeycard: KeyPairSelectedItem keyPairStoredOnKeycardVariant: QVariant + keyPairForAuthentication: KeyPairSelectedItem + keyPairForAuthenticationVariant: QVariant keycardData: string # used to temporary store the data coming from keycard, depends on current state different data may be stored proc delete*(self: View) = @@ -33,6 +35,10 @@ QtObject: self.keyPairStoredOnKeycard.delete if not self.keyPairStoredOnKeycardVariant.isNil: self.keyPairStoredOnKeycardVariant.delete + if not self.keyPairForAuthentication.isNil: + self.keyPairForAuthentication.delete + if not self.keyPairForAuthenticationVariant.isNil: + self.keyPairForAuthenticationVariant.delete self.QObject.delete proc newView*(delegate: io_interface.AccessInterface): View = @@ -58,7 +64,7 @@ QtObject: read = getCurrentState proc keycardDataChanged*(self: View) {.signal.} - proc setKeycardData*(self: View, value: string) = + proc setKeycardData*(self: View, value: string) {.slot.} = if self.keycardData == value: return self.keycardData = value @@ -67,6 +73,7 @@ QtObject: return self.keycardData QtProperty[string] keycardData: read = getKeycardData + write = setKeycardData notify = keycardDataChanged proc onBackActionClicked*(self: View) {.slot.} = @@ -86,6 +93,8 @@ QtObject: proc keyPairModelChanged(self: View) {.signal.} proc getKeyPairModel(self: View): QVariant {.slot.} = + if self.keyPairModelVariant.isNil: + return newQVariant() return self.keyPairModelVariant QtProperty[QVariant] keyPairModel: read = getKeyPairModel @@ -108,6 +117,8 @@ QtObject: self.keyPairModelChanged() proc getSelectedKeyPairItem*(self: View): QVariant {.slot.} = + if self.selectedKeyPairItemVariant.isNil: + return newQVariant() return self.selectedKeyPairItemVariant QtProperty[QVariant] selectedKeyPairItem: read = getSelectedKeyPairItem @@ -124,12 +135,33 @@ QtObject: self.keyPairStoredOnKeycardIsKnown = value proc getKeyPairStoredOnKeycard*(self: View): QVariant {.slot.} = + if self.keyPairStoredOnKeycardVariant.isNil: + return newQVariant() return self.keyPairStoredOnKeycardVariant QtProperty[QVariant] keyPairStoredOnKeycard: read = getKeyPairStoredOnKeycard proc setKeyPairStoredOnKeycard*(self: View, item: KeyPairItem) = self.keyPairStoredOnKeycard.setItem(item) + proc createKeyPairForAuthentication*(self: View) = + if self.keyPairForAuthentication.isNil: + self.keyPairForAuthentication = newKeyPairSelectedItem() + if self.keyPairForAuthenticationVariant.isNil: + self.keyPairForAuthenticationVariant = newQVariant(self.keyPairForAuthentication) + + proc getKeyPairForAuthentication*(self: View): QVariant {.slot.} = + if self.keyPairForAuthenticationVariant.isNil: + return newQVariant() + return self.keyPairForAuthenticationVariant + QtProperty[QVariant] keyPairForAuthentication: + read = getKeyPairForAuthentication + proc setKeyPairForAuthentication*(self: View, item: KeyPairItem) = + self.keyPairForAuthentication.setItem(item) + proc setLockedPropForKeyPairForAuthentication*(self: View, locked: bool) = + if self.keyPairForAuthentication.isNil: + return + self.keyPairForAuthentication.updateLockedState(locked) + proc setPin*(self: View, value: string) {.slot.} = self.delegate.setPin(value) @@ -158,4 +190,7 @@ QtObject: return self.delegate.migratingProfileKeyPair() proc isProfileKeyPairMigrated*(self: View): bool {.slot.} = - return self.delegate.isProfileKeyPairMigrated() \ No newline at end of file + return self.delegate.isProfileKeyPairMigrated() + + proc getSigningPhrase*(self: View): string {.slot.} = + return self.delegate.getSigningPhrase() \ No newline at end of file diff --git a/src/app/modules/startup/controller.nim b/src/app/modules/startup/controller.nim index 72700ad183..3e6850c343 100644 --- a/src/app/modules/startup/controller.nim +++ b/src/app/modules/startup/controller.nim @@ -125,7 +125,7 @@ proc init*(self: Controller) = self.connectionIds.add(handlerId) handlerId = self.events.onWithUUID(SIGNAL_SHARED_KEYCARD_MODULE_DISPLAY_POPUP) do(e: Args): - let args = SharedKeycarModuleArgs(e) + let args = SharedKeycarModuleBaseArgs(e) if args.uniqueIdentifier != UNIQUE_STARTUP_MODULE_IDENTIFIER: return self.delegate.onDisplayKeycardSharedModuleFlow() diff --git a/ui/app/AppLayouts/Profile/views/KeycardView.qml b/ui/app/AppLayouts/Profile/views/KeycardView.qml index d0d975b119..10e7305bcd 100644 --- a/ui/app/AppLayouts/Profile/views/KeycardView.qml +++ b/ui/app/AppLayouts/Profile/views/KeycardView.qml @@ -15,9 +15,6 @@ import shared.status 1.0 import shared.popups.keycard 1.0 import "../stores" -import "../controls" -import "../panels" -import "../popups" SettingsContentBase { id: root diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index f0c91d55fc..3a50869bc2 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -22,6 +22,7 @@ import shared 1.0 import shared.controls 1.0 import shared.panels 1.0 import shared.popups 1.0 +import shared.popups.keycard 1.0 import shared.status 1.0 import StatusQ.Core.Theme 0.1 @@ -50,7 +51,16 @@ Item { Connections { target: rootStore.mainModuleInst + onDisplayUserProfile: Global.openProfilePopup(publicKey) + + onDisplayKeycardSharedModuleFlow: { + keycardPopup.active = true + } + + onDestroyKeycardSharedModuleFlow: { + keycardPopup.active = false + } } Connections { @@ -1177,4 +1187,16 @@ Item { Global.settingsHasLoaded(); Global.errorSound = errorSound; } + + Loader { + id: keycardPopup + active: false + sourceComponent: KeycardPopup { + sharedKeycardModule: rootStore.mainModuleInst.keycardSharedModule + } + + onLoaded: { + keycardPopup.item.open() + } + } } diff --git a/ui/imports/shared/popups/keycard/KeycardPopup.qml b/ui/imports/shared/popups/keycard/KeycardPopup.qml index 0d20d7777f..58d9e2c371 100644 --- a/ui/imports/shared/popups/keycard/KeycardPopup.qml +++ b/ui/imports/shared/popups/keycard/KeycardPopup.qml @@ -44,6 +44,9 @@ StatusModal { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { return qsTr("Factory reset a Keycard") } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + return qsTr("Authenticate") + } return "" } @@ -65,6 +68,7 @@ StatusModal { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardLocked || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin || @@ -86,7 +90,7 @@ StatusModal { return } } - else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || @@ -101,6 +105,31 @@ StatusModal { return } } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard) + { + root.sharedKeycardModule.currentState.doTertiaryAction() + return + } + } root.sharedKeycardModule.currentState.doPrimaryAction() } @@ -120,10 +149,16 @@ StatusModal { root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardLocked || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard || - root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay) + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardMetadataDisplay || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid) { return initComponent } @@ -140,6 +175,7 @@ StatusModal { root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified) { @@ -158,6 +194,12 @@ StatusModal { { return enterSeedPhraseWordsComponent } + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword) { + return passwordComponent + } return undefined } @@ -167,6 +209,10 @@ StatusModal { id: initComponent KeycardInit { sharedKeycardModule: root.sharedKeycardModule + + Component.onCompleted: { + d.primaryButtonEnabled = false + } } } @@ -204,6 +250,14 @@ StatusModal { id: keycardPinComponent KeycardPin { sharedKeycardModule: root.sharedKeycardModule + + Component.onCompleted: { + d.primaryButtonEnabled = false + } + + onPinUpdated: { + d.primaryButtonEnabled = pin.length === Constants.keycard.general.keycardPinLength + } } } @@ -252,14 +306,29 @@ StatusModal { } } } + + Component { + id: passwordComponent + EnterPassword { + sharedKeycardModule: root.sharedKeycardModule + + Component.onCompleted: { + d.primaryButtonEnabled = false + } + + onPasswordValid: { + d.primaryButtonEnabled = valid + } + } + } } leftButtons: [ StatusBackButton { id: backButton visible: root.sharedKeycardModule.currentState.displayBackButton - height: primaryButton.height - width: primaryButton.height + height: Constants.keycard.general.footerButtonsHeight + width: height onClicked: { root.sharedKeycardModule.currentState.backAction() } @@ -267,12 +336,72 @@ StatusModal { ] rightButtons: [ + StatusButton { + id: tertiaryButton + height: Constants.keycard.general.footerButtonsHeight + text: { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin) + return qsTr("Cancel") + } + return "" + } + visible: { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin) + return true + } + return false + } + + onClicked: { + root.sharedKeycardModule.currentState.doTertiaryAction() + } + }, StatusButton { id: secondaryButton + height: Constants.keycard.general.footerButtonsHeight text: { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardLocked || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || @@ -285,7 +414,7 @@ StatusModal { return qsTr("Cancel") } } - else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || @@ -296,12 +425,36 @@ StatusModal { root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation) return qsTr("Cancel") } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.loggedInUserUsesBiometricLogin()) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword) + return qsTr("Use biometrics instead") + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed) + return qsTr("Use password instead") + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) + return qsTr("Use biometrics") + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + 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") + } + } return "" } visible: { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardNotEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardLocked || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || @@ -314,7 +467,7 @@ StatusModal { return true } } - else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinVerified || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || @@ -326,9 +479,42 @@ StatusModal { return true } } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.loggedInUserUsesBiometricLogin()) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty) + return true + } + } return false } - highlighted: focus + enabled: { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.loggedInUserUsesBiometricLogin() && + (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty)) + return false + } + return true + } onClicked: { root.sharedKeycardModule.currentState.doSecondaryAction() @@ -336,6 +522,7 @@ StatusModal { }, StatusButton { id: primaryButton + height: Constants.keycard.general.footerButtonsHeight text: { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || @@ -362,13 +549,14 @@ StatusModal { } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) { - return qsTr("I don’t know the pin") + return qsTr("I don’t know the PIN") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) { return qsTr("Factory reset this Keycard") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardLocked || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmptyMetadata || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseDisplay || @@ -377,22 +565,23 @@ StatusModal { return qsTr("Next") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached) { - return qsTr("Tmp-Next") + return qsTr("Unlock Keycard") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateFailure) { return qsTr("Done") } - if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess && - root.sharedKeycardModule.migratingProfileKeyPair()) { - return qsTr("Restart app & sign in using your new Keycard") + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keyPairMigrateSuccess) { + if (root.sharedKeycardModule.migratingProfileKeyPair()) + return qsTr("Restart app & sign in using your new Keycard") + return qsTr("Done") } return qsTr("Cancel") } - else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) { - return qsTr("I don’t know the pin") + return qsTr("I don’t know the PIN") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) { @@ -404,7 +593,7 @@ StatusModal { return qsTr("Next") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached) { - return qsTr("Tmp-Next") + return qsTr("Unlock Keycard") } if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetSuccess) { @@ -412,6 +601,37 @@ StatusModal { } return qsTr("Cancel") } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin) { + return qsTr("Authenticate") + } + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword) { + return qsTr("Update password & authenticate") + } + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) { + return qsTr("Update PIN & authenticate") + } + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid) { + return qsTr("Try biometrics again") + } + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached) { + return qsTr("Unlock Keycard") + } + } return "" } enabled: { @@ -421,7 +641,7 @@ StatusModal { return false } } - else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.selectExistingKeyPair || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata || @@ -439,28 +659,64 @@ StatusModal { return false } } - else if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmation || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.factoryResetConfirmationDisplayMetadata) { return d.primaryButtonEnabled } } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin) { + return d.primaryButtonEnabled + } + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached) { + return true + } + } return true } icon.name: { if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.seedPhraseEnterWords || root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterSeedPhrase) { - if (root.sharedKeycardModule.migratingProfileKeyPair()) { if (root.sharedKeycardModule.loggedInUserUsesBiometricLogin()) return "touch-id" + if (root.sharedKeycardModule.isProfileKeyPairMigrated()) + return "keycard" return "password" - } + } + } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.loggedInUserUsesBiometricLogin()) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pluginReader || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty) + return "touch-id" } } return "" } - highlighted: focus onClicked: { root.sharedKeycardModule.currentState.doPrimaryAction() diff --git a/ui/imports/shared/popups/keycard/helpers/KeyPairItem.qml b/ui/imports/shared/popups/keycard/helpers/KeyPairItem.qml index ccdd9444d2..93bed3a6b8 100644 --- a/ui/imports/shared/popups/keycard/helpers/KeyPairItem.qml +++ b/ui/imports/shared/popups/keycard/helpers/KeyPairItem.qml @@ -23,6 +23,7 @@ StatusListItem { property string keyPairImage: "" property string keyPairDerivedFrom: "" property string keyPairAccounts: "" + property bool keyPairCardLocked: false signal keyPairSelected() @@ -36,17 +37,18 @@ StatusListItem { height: root.keyPairIcon? 24 : 40 name: root.keyPairImage? root.keyPairImage : root.keyPairIcon color: root.keyPairType === Constants.keycard.keyPairType.profile? - Utils.colorForPubkey(d.myPublicKey) : Theme.palette.primaryColor1 - letterSize: Math.max(4, this.width / 2.4) + Utils.colorForPubkey(d.myPublicKey) : + root.keyPairCardLocked? Theme.palette.dangerColor1 : Theme.palette.primaryColor1 + letterSize: Math.max(4, asset.width / 2.4) charactersLen: 2 - isLetterIdenticon: !root.keyPairIcon && !this.name.toString() - bgColor: Theme.palette.primaryColor3 + isLetterIdenticon: !root.keyPairIcon && !asset.name.toString() + bgColor: root.keyPairCardLocked? Theme.palette.dangerColor3 : Theme.palette.primaryColor3 } ringSettings { ringSpecModel: root.keyPairType === Constants.keycard.keyPairType.profile? Utils.getColorHashAsJson(d.myPublicKey) : [] - ringPxSize: Math.max(this.icon.width / 24.0) + ringPxSize: Math.max(asset.width / 24.0) } tagsModel: ListModel{} diff --git a/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml b/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml index 31c61c928f..a052bdb6bf 100644 --- a/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml +++ b/ui/imports/shared/popups/keycard/helpers/KeyPairUnknownItem.qml @@ -65,7 +65,7 @@ Rectangle { height: 24 name: root.keyPairIcon color: Utils.colorForPubkey(root.keyPairPubKey) - letterSize: Math.max(4, this.image.width / 2.4) + letterSize: Math.max(4, asset.width / 2.4) charactersLen: 2 isLetterIdenticon: false bgColor: Theme.palette.primaryColor3 diff --git a/ui/imports/shared/popups/keycard/states/EnterPassword.qml b/ui/imports/shared/popups/keycard/states/EnterPassword.qml new file mode 100644 index 0000000000..27032263e8 --- /dev/null +++ b/ui/imports/shared/popups/keycard/states/EnterPassword.qml @@ -0,0 +1,210 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 + +import utils 1.0 + +import "../helpers" + +Item { + id: root + + property var sharedKeycardModule + + property string kcData: root.sharedKeycardModule.keycardData + + signal passwordValid(bool valid) + + onKcDataChanged: { + d.updatePasswordValidation() + } + + onStateChanged: { + password.focus = true + } + + Component.onCompleted: timer.start() + + Timer { + id: timer + interval: 1000 + onTriggered: { + password.forceActiveFocus(Qt.MouseFocusReason) + } + } + + QtObject { + id: d + + function updatePasswordValidation() { + root.passwordValid(password.text !== "" && root.kcData === "") + } + } + + ColumnLayout { + anchors.fill: parent + anchors.topMargin: Style.current.xlPadding + anchors.bottomMargin: Style.current.halfPadding + anchors.leftMargin: Style.current.xlPadding + anchors.rightMargin: Style.current.xlPadding + spacing: Style.current.padding + + KeycardImage { + id: image + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: Constants.keycard.shared.imageHeight + Layout.preferredWidth: Constants.keycard.shared.imageWidth + } + + StatusBaseText { + id: title + Layout.alignment: Qt.AlignCenter + font.weight: Font.Bold + } + + StatusBaseText { + id: message + Layout.alignment: Qt.AlignCenter + wrapMode: Text.WordWrap + visible: text != "" + } + + StatusPasswordInput { + id: password + Layout.alignment: Qt.AlignHCenter + signingPhrase: root.sharedKeycardModule.getSigningPhrase() + placeholderText: qsTr("Password") + selectByMouse: true + focus: true + + onTextChanged: { + root.sharedKeycardModule.keycardData = "" + root.sharedKeycardModule.setPassword(text) + d.updatePasswordValidation() + } + + onAccepted: { + if (password.text !== "") { + root.sharedKeycardModule.currentState.doPrimaryAction() + } + } + } + + StatusBaseText { + id: info + Layout.alignment: Qt.AlignCenter + wrapMode: Text.WordWrap + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + states: [ + State { + name: Constants.keycardSharedState.enterPassword + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPassword + PropertyChanges { + target: image + source: Style.png("keycard/authenticate") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Enter your password") + font.pixelSize: Constants.keycard.general.fontSize1 + color: Theme.palette.directColor1 + } + PropertyChanges { + target: message + text: "" + } + PropertyChanges { + target: info + text: "" + } + }, + State { + name: Constants.keycardSharedState.wrongPassword + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPassword + PropertyChanges { + target: image + source: Style.png("keycard/authenticate") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Enter your password") + font.pixelSize: Constants.keycard.general.fontSize1 + color: Theme.palette.directColor1 + } + PropertyChanges { + target: message + text: "" + } + PropertyChanges { + target: info + text: root.kcData !== ""? qsTr("Password incorrect") : "" + color: Theme.palette.dangerColor1 + } + }, + State { + name: Constants.keycardSharedState.enterBiometricsPassword + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterBiometricsPassword + PropertyChanges { + target: image + source: Style.png("keycard/biometrics-success") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Password changed on other device") + font.pixelSize: Constants.keycard.general.fontSize1 + color: Theme.palette.directColor1 + } + PropertyChanges { + target: message + text: qsTr("Enter your new password to proceed") + color: Theme.palette.baseColor1 + } + PropertyChanges { + target: info + text: root.kcData !== ""? qsTr("Password incorrect") : "" + color: Theme.palette.dangerColor1 + } + }, + State { + name: Constants.keycardSharedState.wrongBiometricsPassword + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongBiometricsPassword + PropertyChanges { + target: image + source: Style.png("keycard/biometrics-success") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Password changed on other device") + font.pixelSize: Constants.keycard.general.fontSize1 + color: Theme.palette.directColor1 + } + PropertyChanges { + target: message + text: qsTr("Enter your new password to proceed") + color: Theme.palette.baseColor1 + } + PropertyChanges { + target: info + text: root.kcData !== ""? qsTr("Password incorrect") : "" + color: Theme.palette.dangerColor1 + } + } + ] +} diff --git a/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml b/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml index 73086d9d3f..d3b73f33e6 100644 --- a/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml +++ b/ui/imports/shared/popups/keycard/states/EnterSeedPhrase.qml @@ -29,11 +29,9 @@ Item { onWrongSeedPhraseChanged: { if (wrongSeedPhrase) { invalidSeedTxt.text = qsTr("The phrase you’ve entered does not match this Keycard’s seed phrase") - invalidSeedTxt.visible = true } else { invalidSeedTxt.text = "" - invalidSeedTxt.visible = false } } @@ -50,7 +48,6 @@ Item { root.sharedKeycardModule.setSeedPhrase(mnemonicString) } else { invalidSeedTxt.text = qsTr("Invalid seed phrase") - invalidSeedTxt.visible = true d.allEntriesValid = false } } @@ -65,11 +62,9 @@ Item { d.allEntriesValid = d.allEntriesValid && d.seedPhrases_en.words.includes(word) if (d.allEntriesValid) { invalidSeedTxt.text = "" - invalidSeedTxt.visible = false } else { invalidSeedTxt.text = qsTr("The phrase you’ve entered is invalid") - invalidSeedTxt.visible = true } } @@ -311,7 +306,6 @@ Item { id: invalidSeedTxt Layout.alignment: Qt.AlignHCenter color: Theme.palette.dangerColor1 - visible: false } } diff --git a/ui/imports/shared/popups/keycard/states/KeycardInit.qml b/ui/imports/shared/popups/keycard/states/KeycardInit.qml index f420ce7d37..91b4d4e3d8 100644 --- a/ui/imports/shared/popups/keycard/states/KeycardInit.qml +++ b/ui/imports/shared/popups/keycard/states/KeycardInit.qml @@ -6,11 +6,6 @@ import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Components 0.1 -///////// Remove Later//////////// -import StatusQ.Popups 0.1 -import StatusQ.Controls 0.1 -////////////////////////////////// - import utils 1.0 import "../helpers" @@ -22,7 +17,7 @@ Item { Component.onCompleted: { if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair) { - passwordPopup.open() + root.sharedKeycardModule.currentState.doPrimaryAction() } } @@ -41,37 +36,6 @@ Item { } } - ///////// Remove Later//////////// - StatusModal { - id: passwordPopup - width: 300 - height: 200 - anchors.centerIn: parent - header.title: qsTr("Temporary Popup") - closePolicy: Popup.NoAutoClose - contentItem: Item { - StatusInput { - id: password - width: parent.width - Style.current.padding * 2 - anchors.centerIn: parent - input.clearable: true - placeholderText: qsTr("Enter password...") - } - } - rightButtons: [ - StatusButton { - id: primaryButton - text: qsTr("Next") - onClicked: { - root.sharedKeycardModule.setPassword(password.text) - passwordPopup.close() - root.sharedKeycardModule.currentState.doPrimaryAction() - } - } - ] - } - ////////////////////////////////// - Component { id: keyPairComponent KeyPairItem { @@ -110,6 +74,20 @@ Item { } } + Component { + id: keyPairForAuthenticationComponent + KeyPairItem { + keyPairType: root.sharedKeycardModule.keyPairForAuthentication.pairType + keyPairPubKey: root.sharedKeycardModule.keyPairForAuthentication.pubKey + keyPairName: root.sharedKeycardModule.keyPairForAuthentication.name + keyPairIcon: root.sharedKeycardModule.keyPairForAuthentication.icon + keyPairImage: root.sharedKeycardModule.keyPairForAuthentication.image + keyPairDerivedFrom: root.sharedKeycardModule.keyPairForAuthentication.derivedFrom + keyPairAccounts: root.sharedKeycardModule.keyPairForAuthentication.accounts + keyPairCardLocked: root.sharedKeycardModule.keyPairForAuthentication.locked + } + } + ColumnLayout { anchors.fill: parent anchors.topMargin: Style.current.xlPadding @@ -187,6 +165,21 @@ Item { return true } } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication && + !!root.sharedKeycardModule.keyPairForAuthentication && + root.sharedKeycardModule.keyPairForAuthentication.name !== "") { + if(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard) { + return true + } + } return false } @@ -216,6 +209,21 @@ Item { return unknownKeyPairCompontnt } } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard) { + return keyPairForAuthenticationComponent + } + } + + return undefined } } @@ -338,12 +346,17 @@ Item { } PropertyChanges { target: image - pattern: "keycard/strong_error/img-%1" - source: "" + pattern: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + "" : "keycard/strong_error/img-%1" + source: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + Style.png("keycard/plain-error") : "" startImgIndexForTheFirstLoop: 0 - startImgIndexForOtherLoops: 18 - endImgIndex: 29 - duration: 1300 + startImgIndexForOtherLoops: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + 0 : 18 + endImgIndex: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + 0 : 29 + duration: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + 0 : 1300 loops: -1 } PropertyChanges { @@ -353,6 +366,28 @@ Item { color: Theme.palette.dangerColor1 } }, + State { + name: Constants.keycardSharedState.wrongKeycard + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard + PropertyChanges { + target: title + text: qsTr("Wrong Keycard inserted") + font.pixelSize: Constants.keycard.general.fontSize1 + font.weight: Font.Bold + color: Theme.palette.dangerColor1 + } + PropertyChanges { + target: image + source: Style.png("keycard/plain-error") + pattern: "" + } + PropertyChanges { + target: message + text: qsTr("Keycard inserted does not match the Keycard below") + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.dangerColor1 + } + }, State { name: Constants.keycardSharedState.maxPinRetriesReached when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.maxPinRetriesReached @@ -375,7 +410,9 @@ Item { } PropertyChanges { target: message - text: qsTr("Pin entered incorrectly too many times") + text: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + qsTr("You will need to unlock it before proceeding") : + qsTr("Pin entered incorrectly too many times") font.pixelSize: Constants.keycard.general.fontSize2 color: Theme.palette.dangerColor1 } @@ -410,18 +447,21 @@ Item { text: qsTr("Keycard is empty") font.pixelSize: Constants.keycard.general.fontSize1 font.weight: Font.Bold - color: Theme.palette.directColor1 + color: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + Theme.palette.dangerColor1 : Theme.palette.directColor1 } PropertyChanges { target: image - source: Style.png("keycard/card-empty") + source: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + Style.png("keycard/plain-error") : Style.png("keycard/card-empty") pattern: "" } PropertyChanges { target: message text: qsTr("There is no key pair on this Keycard") font.pixelSize: Constants.keycard.general.fontSize2 - color: Theme.palette.directColor1 + color: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication? + Theme.palette.dangerColor1 : Theme.palette.directColor1 } }, State { @@ -449,6 +489,33 @@ Item { color: Theme.palette.directColor1 } }, + State { + name: Constants.keycardSharedState.keycardLocked + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardLocked + PropertyChanges { + target: title + text: qsTr("Keycard locked and already stores keys") + font.pixelSize: Constants.keycard.general.fontSize1 + font.weight: Font.Bold + color: Theme.palette.directColor1 + } + PropertyChanges { + target: image + pattern: "keycard/strong_error/img-%1" + source: "" + startImgIndexForTheFirstLoop: 0 + startImgIndexForOtherLoops: 18 + endImgIndex: 29 + duration: 1300 + loops: -1 + } + PropertyChanges { + target: message + text: qsTr("The Keycard you have inserted is locked,\nyou will need to factory reset it before proceeding") + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.directColor1 + } + }, State { name: Constants.keycardSharedState.recognizedKeycard when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.recognizedKeycard @@ -601,6 +668,88 @@ Item { target: message text: "" } + }, + State { + name: Constants.keycardSharedState.biometricsReadyToSign + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign + PropertyChanges { + target: title + text: qsTr("Ready to authenticate...") + font.pixelSize: Constants.keycard.general.fontSize1 + font.weight: Font.Bold + color: Theme.palette.directColor1 + } + PropertyChanges { + target: image + source: Style.png("keycard/card-inserted") + pattern: "" + } + PropertyChanges { + target: message + text: "" + } + }, + State { + name: Constants.keycardSharedState.biometricsPasswordFailed + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPasswordFailed + PropertyChanges { + target: image + source: Style.png("keycard/biometrics-fail") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Biometric scan failed") + font.pixelSize: Constants.keycard.general.fontSize1 + font.weight: Font.Bold + color: Theme.palette.directColor1 + } + PropertyChanges { + target: message + text: qsTr("Biometrics incorrect") + color: Theme.palette.dangerColor1 + } + }, + State { + name: Constants.keycardSharedState.biometricsPinFailed + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed + PropertyChanges { + target: image + source: Style.png("keycard/plain-error") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Biometric scan failed") + font.pixelSize: Constants.keycard.general.fontSize1 + font.weight: Font.Bold + color: Theme.palette.directColor1 + } + PropertyChanges { + target: message + text: qsTr("Biometrics incorrect") + color: Theme.palette.dangerColor1 + } + }, + State { + name: Constants.keycardSharedState.biometricsPinInvalid + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid + PropertyChanges { + target: image + source: Style.png("keycard/plain-error") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Biometric pin invalid") + font.pixelSize: Constants.keycard.general.fontSize1 + font.weight: Font.Bold + color: Theme.palette.dangerColor1 + } + PropertyChanges { + target: message + text: "" + } } ] } diff --git a/ui/imports/shared/popups/keycard/states/KeycardPin.qml b/ui/imports/shared/popups/keycard/states/KeycardPin.qml index d0dbae7302..4ec9a812bb 100644 --- a/ui/imports/shared/popups/keycard/states/KeycardPin.qml +++ b/ui/imports/shared/popups/keycard/states/KeycardPin.qml @@ -19,8 +19,11 @@ Item { property int remainingAttempts: parseInt(root.sharedKeycardModule.keycardData, 10) + signal pinUpdated(string pin) + onRemainingAttemptsChanged: { - if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) { pinInputField.statesInitialization() pinInputField.forceFocus() } @@ -36,14 +39,51 @@ Item { } } + Component.onCompleted: timer.start() + + Timer { + id: timer + interval: 1000 + onTriggered: { + pinInputField.statesInitialization() + pinInputField.forceFocus() + } + } + + Component { + id: keyPairComponent + KeyPairItem { + keyPairType: root.sharedKeycardModule.selectedKeyPairItem.pairType + keyPairPubKey: root.sharedKeycardModule.selectedKeyPairItem.pubKey + keyPairName: root.sharedKeycardModule.selectedKeyPairItem.name + keyPairIcon: root.sharedKeycardModule.selectedKeyPairItem.icon + keyPairImage: root.sharedKeycardModule.selectedKeyPairItem.image + keyPairDerivedFrom: root.sharedKeycardModule.selectedKeyPairItem.derivedFrom + keyPairAccounts: root.sharedKeycardModule.selectedKeyPairItem.accounts + } + } + + Component { + id: keyPairForAuthenticationComponent + KeyPairItem { + keyPairType: root.sharedKeycardModule.keyPairForAuthentication.pairType + keyPairPubKey: root.sharedKeycardModule.keyPairForAuthentication.pubKey + keyPairName: root.sharedKeycardModule.keyPairForAuthentication.name + keyPairIcon: root.sharedKeycardModule.keyPairForAuthentication.icon + keyPairImage: root.sharedKeycardModule.keyPairForAuthentication.image + keyPairDerivedFrom: root.sharedKeycardModule.keyPairForAuthentication.derivedFrom + keyPairAccounts: root.sharedKeycardModule.keyPairForAuthentication.accounts + keyPairCardLocked: root.sharedKeycardModule.keyPairForAuthentication.locked + } + } + ColumnLayout { anchors.fill: parent anchors.topMargin: Style.current.xlPadding anchors.bottomMargin: Style.current.halfPadding anchors.leftMargin: Style.current.xlPadding anchors.rightMargin: Style.current.xlPadding - spacing: Style.current.padding - clip: true + spacing: Style.current.halfPadding KeycardImage { id: image @@ -58,16 +98,26 @@ Item { font.weight: Font.Bold } + StatusBaseText { + id: subTitle + Layout.alignment: Qt.AlignCenter + wrapMode: Text.WordWrap + visible: text !== "" + } + StatusPinInput { id: pinInputField Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: !info.visble && !message.visible? true : false validator: StatusIntValidator{bottom: 0; top: 999999;} pinLen: Constants.keycard.general.keycardPinLength enabled: root.sharedKeycardModule.currentState.stateType !== Constants.keycardSharedState.pinSet && root.sharedKeycardModule.currentState.stateType !== Constants.keycardSharedState.pinVerified onPinInputChanged: { - if (root.state !== Constants.keycardSharedState.wrongPin) { + root.pinUpdated(pinInput) + if (root.state !== Constants.keycardSharedState.wrongPin || + root.state === Constants.keycardSharedState.wrongKeychainPin) { image.source = Style.png("keycard/enter-pin-%1".arg(pinInput.length)) } if(pinInput.length == 0) { @@ -75,8 +125,11 @@ Item { } if(root.state === Constants.keycardSharedState.createPin || root.state === Constants.keycardSharedState.enterPin || - root.state === Constants.keycardSharedState.wrongPin) { + root.state === Constants.keycardSharedState.wrongPin || + root.state === Constants.keycardSharedState.wrongKeychainPin) { root.sharedKeycardModule.setPin(pinInput) + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) + return root.sharedKeycardModule.currentState.doTertiaryAction() } else if(root.state === Constants.keycardSharedState.repeatPin) { @@ -95,6 +148,7 @@ Item { StatusBaseText { id: info Layout.alignment: Qt.AlignCenter + Layout.fillHeight: info.visble && !message.visible? true : false wrapMode: Text.WordWrap visible: text !== "" } @@ -102,30 +156,49 @@ Item { StatusBaseText { id: message Layout.alignment: Qt.AlignCenter + Layout.fillHeight: message.visible? true : false wrapMode: Text.WordWrap visible: text !== "" } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - Loader { Layout.preferredWidth: parent.width - active: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard && - (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin || - root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin || - root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet) + active: { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet) { + return true + } + } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) { + return true + } + } - sourceComponent: KeyPairItem { - keyPairType: root.sharedKeycardModule.selectedKeyPairItem.pairType - keyPairPubKey: root.sharedKeycardModule.selectedKeyPairItem.pubKey - keyPairName: root.sharedKeycardModule.selectedKeyPairItem.name - keyPairIcon: root.sharedKeycardModule.selectedKeyPairItem.icon - keyPairImage: root.sharedKeycardModule.selectedKeyPairItem.image - keyPairDerivedFrom: root.sharedKeycardModule.selectedKeyPairItem.derivedFrom - keyPairAccounts: root.sharedKeycardModule.selectedKeyPairItem.accounts + return false + } + + sourceComponent: { + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.setupNewKeycard) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.createPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.repeatPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.pinSet) { + return keyPairComponent + } + } + if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) { + if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin || + root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) { + return keyPairForAuthenticationComponent + } + } + + return undefined } } } @@ -145,6 +218,10 @@ Item { font.pixelSize: Constants.keycard.general.fontSize1 color: Theme.palette.directColor1 } + PropertyChanges { + target: subTitle + text: "" + } PropertyChanges { target: info text: "" @@ -168,6 +245,45 @@ Item { font.pixelSize: Constants.keycard.general.fontSize1 color: Theme.palette.directColor1 } + PropertyChanges { + target: subTitle + text: "" + } + PropertyChanges { + target: info + text: qsTr("PIN incorrect") + color: Theme.palette.dangerColor1 + font.pixelSize: Constants.keycard.general.fontSize3 + } + PropertyChanges { + target: message + text: qsTr("%n attempt(s) remaining", "", root.remainingAttempts) + color: root.remainingAttempts === 1? + Theme.palette.dangerColor1 : + Theme.palette.baseColor1 + font.pixelSize: Constants.keycard.general.fontSize3 + } + }, + State { + name: Constants.keycardSharedState.wrongKeychainPin + when: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin + PropertyChanges { + target: image + source: Style.png("keycard/plain-error") + pattern: "" + } + PropertyChanges { + target: title + text: qsTr("Your saved PIN is out of date") + font.pixelSize: Constants.keycard.general.fontSize1 + color: Theme.palette.directColor1 + } + PropertyChanges { + target: subTitle + text: qsTr("Enter your new PIN to proceed") + font.pixelSize: Constants.keycard.general.fontSize3 + color: Theme.palette.baseColor1 + } PropertyChanges { target: info text: qsTr("PIN incorrect") @@ -197,6 +313,10 @@ Item { font.pixelSize: Constants.keycard.general.fontSize1 color: Theme.palette.directColor1 } + PropertyChanges { + target: subTitle + text: "" + } PropertyChanges { target: info text: qsTr("It is very important that you do not lose this PIN") @@ -222,6 +342,10 @@ Item { font.pixelSize: Constants.keycard.general.fontSize1 color: Theme.palette.directColor1 } + PropertyChanges { + target: subTitle + text: "" + } PropertyChanges { target: info text: qsTr("It is very important that you do not lose this PIN") @@ -252,6 +376,10 @@ Item { font.pixelSize: Constants.keycard.general.fontSize1 color: Theme.palette.directColor1 } + PropertyChanges { + target: subTitle + text: "" + } PropertyChanges { target: info text: "" @@ -280,6 +408,10 @@ Item { font.pixelSize: Constants.keycard.general.fontSize1 color: Theme.palette.directColor1 } + PropertyChanges { + target: subTitle + text: "" + } PropertyChanges { target: info text: "" diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 528b808088..f1c80c9b1f 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -79,6 +79,7 @@ QtObject { readonly property string general: "General" readonly property string factoryReset: "FactoryReset" readonly property string setupNewKeycard: "SetupNewKeycard" + readonly property string authentication: "Authentication" } readonly property QtObject keycardSharedState: QtObject { @@ -93,6 +94,7 @@ QtObject { readonly property string pinVerified: "PinVerified" readonly property string enterPin: "EnterPin" readonly property string wrongPin: "WrongPin" + readonly property string wrongKeychainPin: "WrongKeychainPin" readonly property string maxPinRetriesReached: "MaxPinRetriesReached" readonly property string factoryResetConfirmation: "FactoryResetConfirmation" readonly property string factoryResetConfirmationDisplayMetadata: "FactoryResetConfirmationDisplayMetadata" @@ -101,7 +103,9 @@ QtObject { readonly property string keycardMetadataDisplay: "KeycardMetadataDisplay" readonly property string keycardEmpty: "KeycardEmpty" readonly property string keycardNotEmpty: "KeycardNotEmpty" + readonly property string keycardLocked: "KeycardLocked" readonly property string notKeycard: "NotKeycard" + readonly property string wrongKeycard: "WrongKeycard" readonly property string recognizedKeycard: "RecognizedKeycard" readonly property string selectExistingKeyPair: "SelectExistingKeyPair" readonly property string enterSeedPhrase: "EnterSeedPhrase" @@ -111,6 +115,14 @@ QtObject { readonly property string keyPairMigrateSuccess: "KeyPairMigrateSuccess" readonly property string keyPairMigrateFailure: "KeyPairMigrateFailure" readonly property string migratingKeyPair: "MigratingKeyPair" + readonly property string enterPassword: "EnterPassword" + readonly property string wrongPassword: "WrongPassword" + readonly property string biometricsPasswordFailed: "BiometricsPasswordFailed" + readonly property string biometricsPinFailed: "BiometricsPinFailed" + readonly property string biometricsPinInvalid: "BiometricsPinInvalid" + readonly property string biometricsReadyToSign: "BiometricsReadyToSign" + readonly property string enterBiometricsPassword: "EnterBiometricsPassword" + readonly property string wrongBiometricsPassword: "WrongBiometricsPassword" } readonly property QtObject keychain: QtObject { @@ -372,6 +384,7 @@ QtObject { readonly property int popupBiggerHeight: 766 readonly property int titleHeight: 44 readonly property int messageHeight: 48 + readonly property int footerButtonsHeight: 44 } readonly property QtObject keyPairType: QtObject {