diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 6c412e83559..6a1d1c9d180 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -178,7 +178,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = # Services result.generalService = general_service.newService(statusFoundation.events, statusFoundation.threadpool) result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool) - result.keycardServiceV2 = keycard_serviceV2.newService(statusFoundation.events, statusFoundation.threadpool) + result.keycardServiceV2 = keycard_serviceV2.newService(statusFoundation.events, statusFoundation.threadpool, result.keycardService) result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration, result.settingsService, statusFoundation.events) result.keychainService = keychain_service.newService(statusFoundation.events) @@ -440,8 +440,10 @@ proc mainDidLoad*(self: AppController) = self.checkForStoringPasswordToKeychain() proc start*(self: AppController) = + if self.shouldUseTheNewOnboardingModule(): + self.keycardServiceV2.init() + self.keycardService.init() - self.keycardServiceV2.init() self.keychainService.init() self.generalService.init() self.accountsService.init() diff --git a/src/app/modules/onboarding/controller.nim b/src/app/modules/onboarding/controller.nim index f42ded98e71..5f1c438fb1f 100644 --- a/src/app/modules/onboarding/controller.nim +++ b/src/app/modules/onboarding/controller.nim @@ -1,4 +1,4 @@ -import chronicles, strutils +import chronicles import io_interface import uuids @@ -9,6 +9,7 @@ import app_service/service/accounts/service as accounts_service import app_service/service/accounts/dto/image_crop_rectangle import app_service/service/devices/service as devices_service import app_service/service/keycardV2/service as keycard_serviceV2 +from app_service/service/keycardV2/dto import KeycardExportedKeysDto logScope: topics = "onboarding-controller" @@ -60,9 +61,47 @@ proc init*(self: Controller) = self.delegate.onLocalPairingStatusUpdate(args) self.connectionIds.add(handlerId) -proc setPin*(self: Controller, pin: string): bool = - self.keycardServiceV2.setPin(pin) - discard + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_STATE_UPDATED) do(e: Args): + let args = KeycardEventArg(e) + self.delegate.onKeycardStateUpdated(args.keycardEvent) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_SET_PIN_FAILURE) do(e: Args): + let args = KeycardErrorArg(e) + self.delegate.onKeycardSetPinFailure(args.error) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_AUTHORIZE_FAILURE) do(e: Args): + let args = KeycardErrorArg(e) + self.delegate.onKeycardAuthorizeFailure(args.error) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_LOAD_MNEMONIC_FAILURE) do(e: Args): + let args = KeycardErrorArg(e) + self.delegate.onKeycardLoadMnemonicFailure(args.error) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_LOAD_MNEMONIC_SUCCESS) do(e: Args): + let args = KeycardKeyUIDArg(e) + self.delegate.onKeycardLoadMnemonicSuccess(args.keyUID) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE) do(e: Args): + let args = KeycardErrorArg(e) + self.delegate.onKeycardExportKeysFailure(args.error) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS) do(e: Args): + let args = KeycardExportedKeysArg(e) + self.delegate.onKeycardExportKeysSuccess(args.exportedKeys) + self.connectionIds.add(handlerId) + +proc initialize*(self: Controller, pin: string) = + let puk = self.keycardServiceV2.generateRandomPUK() + self.keycardServiceV2.initialize(pin, puk) + +proc authorize*(self: Controller, pin: string) = + self.keycardServiceV2.asyncAuthorize(pin) proc getPasswordStrengthScore*(self: Controller, password, userName: string): int = return self.generalService.getPasswordStrengthScore(password, userName) @@ -73,16 +112,11 @@ proc validMnemonic*(self: Controller, mnemonic: string): bool = return true return false -proc buildSeedPhrasesFromIndexes(self: Controller, seedPhraseIndexes: seq[int]): string = - if seedPhraseIndexes.len == 0: - error "keycard error: cannot generate mnemonic" - return - let sp = self.keycardServiceV2.buildSeedPhrasesFromIndexes(seedPhraseIndexes) - return sp.join(" ") +proc loadMnemonic*(self: Controller, mnemonic: string) = + self.keycardServiceV2.loadMnemonic(mnemonic) -proc getMnemonic*(self: Controller): string = - let indexes = self.keycardServiceV2.getMnemonicIndexes() - return self.buildSeedPhrasesFromIndexes(indexes) +proc generateRandomPUK*(self: Controller): string = + return self.keycardServiceV2.generateRandomPUK() proc validateLocalPairingConnectionString*(self: Controller, connectionString: string): bool = let err = self.devicesService.validateConnectionString(connectionString) @@ -110,6 +144,14 @@ proc restoreAccountAndLogin*(self: Controller, password, mnemonic: string, recov keycardInstanceUID, ) +proc restoreKeycardAccountAndLogin*(self: Controller, keyUid, instanceUid: string, keycardKeys: KeycardExportedKeysDto, recoverAccount: bool): string = + return self.accountsService.restoreKeycardAccountAndLoginV2( + keyUid, + instanceUid, + keycardKeys, + recoverAccount, + ) + proc setLoggedInAccount*(self: Controller, account: AccountDto) = self.accountsService.setLoggedInAccount(account) self.accountsService.updateLoggedInAccount(account.name, account.images) @@ -123,3 +165,12 @@ proc loginLocalPairingAccount*(self: Controller, account: AccountDto, password, proc finishPairingThroughSeedPhraseProcess*(self: Controller, installationId: string) = self.devicesService.finishPairingThroughSeedPhraseProcess(installationId) + +proc stopKeycardService*(self: Controller) = + self.keycardServiceV2.stop() + +proc generateMnemonic*(self: Controller, length: int): string = + return self.keycardServiceV2.generateMnemonic(length) + +proc exportRecoverKeysFromKeycard*(self: Controller) = + self.keycardServiceV2.asyncExportRecoverKeys() diff --git a/src/app/modules/onboarding/io_interface.nim b/src/app/modules/onboarding/io_interface.nim index 68346f1e3d9..db0d8fae1c3 100644 --- a/src/app/modules/onboarding/io_interface.nim +++ b/src/app/modules/onboarding/io_interface.nim @@ -3,6 +3,7 @@ type from app_service/service/settings/dto/settings import SettingsDto from app_service/service/accounts/dto/accounts import AccountDto +from app_service/service/keycardV2/dto import KeycardEventDto, KeycardExportedKeysDto from app_service/service/devices/dto/local_pairing_status import LocalPairingStatus method delete*(self: AccessInterface) {.base.} = @@ -17,7 +18,10 @@ method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, s method load*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method setPin*(self: AccessInterface, pin: string): bool {.base.} = +method initialize*(self: AccessInterface, pin: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method authorize*(self: AccessInterface, pin: string) {.base.} = raise newException(ValueError, "No implementation available") method getPasswordStrengthScore*(self: AccessInterface, password, userName: string): int {.base.} = @@ -35,12 +39,39 @@ method validateLocalPairingConnectionString*(self: AccessInterface, connectionSt method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string) {.base.} = raise newException(ValueError, "No implementation available") +method loadMnemonic*(self: AccessInterface, dataJson: string) {.base.} = + raise newException(ValueError, "No implementation available") + method finishOnboardingFlow*(self: AccessInterface, flowInt: int, dataJson: string): string {.base.} = raise newException(ValueError, "No implementation available") method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} = raise newException(ValueError, "No implementation available") +method onKeycardStateUpdated*(self: AccessInterface, keycardEvent: KeycardEventDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardSetPinFailure*(self: AccessInterface, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardAuthorizeFailure*(self: AccessInterface, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardLoadMnemonicFailure*(self: AccessInterface, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardLoadMnemonicSuccess*(self: AccessInterface, keyUID: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardExportKeysFailure*(self: AccessInterface, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardExportKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method exportRecoverKeys*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + # This way (using concepts) is used only for the modules managed by AppController type DelegateInterface* = concept c diff --git a/src/app/modules/onboarding/module.nim b/src/app/modules/onboarding/module.nim index 34fa87f28b6..bed5752df06 100644 --- a/src/app/modules/onboarding/module.nim +++ b/src/app/modules/onboarding/module.nim @@ -12,6 +12,7 @@ import app_service/service/devices/service as devices_service import app_service/service/keycardV2/service as keycard_serviceV2 from app_service/service/settings/dto/settings import SettingsDto from app_service/service/accounts/dto/accounts import AccountDto +from app_service/service/keycardV2/dto import KeycardEventDto, KeycardExportedKeysDto, KeycardState export io_interface @@ -22,7 +23,6 @@ type SecondaryFlow* {.pure} = enum Unknown = 0, CreateProfileWithPassword, CreateProfileWithSeedphrase, - CreateProfileWithKeycard, CreateProfileWithKeycardNewSeedphrase, CreateProfileWithKeycardExistingSeedphrase, LoginWithSeedphrase, @@ -30,6 +30,12 @@ type SecondaryFlow* {.pure} = enum LoginWithKeycard, ActualLogin, # TODO get the real name and value for this when it's implemented on the front-end +type ProgressState* {.pure.} = enum + Idle, + InProgress, + Success, + Failed + type Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface delegate: T @@ -38,6 +44,7 @@ type controller: Controller localPairingStatus: LocalPairingStatus currentFlow: SecondaryFlow + exportedKeys: KeycardExportedKeysDto proc newModule*[T]( delegate: T, @@ -82,8 +89,13 @@ method load*[T](self: Module[T]) = self.controller.init() self.delegate.onboardingDidLoad() -method setPin*[T](self: Module[T], pin: string): bool = - self.controller.setPin(pin) +method initialize*[T](self: Module[T], pin: string) = + self.view.setPinSettingState(ProgressState.InProgress.int) + self.controller.initialize(pin) + +method authorize*[T](self: Module[T], pin: string) = + self.view.setAuthorizationState(ProgressState.InProgress.int) + self.controller.authorize(pin) method getPasswordStrengthScore*[T](self: Module[T], password, userName: string): int = self.controller.getPasswordStrengthScore(password, userName) @@ -92,7 +104,7 @@ method validMnemonic*[T](self: Module[T], mnemonic: string): bool = self.controller.validMnemonic(mnemonic) method getMnemonic*[T](self: Module[T]): string = - self.controller.getMnemonic() + return self.controller.generateMnemonic(SupportedMnemonicLength12) method validateLocalPairingConnectionString*[T](self: Module[T], connectionString: string): bool = self.controller.validateLocalPairingConnectionString(connectionString) @@ -100,6 +112,10 @@ method validateLocalPairingConnectionString*[T](self: Module[T], connectionStrin method inputConnectionStringForBootstrapping*[T](self: Module[T], connectionString: string) = self.controller.inputConnectionStringForBootstrapping(connectionString) +method loadMnemonic*[T](self: Module[T], mnemonic: string) = + self.view.setAddKeyPairState(ProgressState.InProgress.int) + self.controller.loadMnemonic(mnemonic) + method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string): string = try: self.currentFlow = SecondaryFlow(flowInt) @@ -121,15 +137,24 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string) recoverAccount = false, keycardInstanceUID = "", ) - of SecondaryFlow.CreateProfileWithKeycard: - # TODO implement keycard function - discard of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase: - # TODO implement keycard function - discard + # New user with a seedphrase we showed them + let keycardEvent = self.view.getKeycardEvent() + err = self.controller.restoreAccountAndLogin( + password = "", # For keycard it will be substituted with`encryption.publicKey` in status-go + seedPhrase, + recoverAccount = false, + keycardInstanceUID = keycardEvent.keycardInfo.instanceUID, + ) of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase: - # TODO implement keycard function - discard + # New user who entered their own seed phrase + let keycardEvent = self.view.getKeycardEvent() + err = self.controller.restoreAccountAndLogin( + password = "", # For keycard it will be substituted with`encryption.publicKey` in status-go + seedPhrase, + recoverAccount = false, + keycardInstanceUID = keycardEvent.keycardInfo.instanceUID, + ) # LOGIN FLOWS of SecondaryFlow.LoginWithSeedphrase: @@ -147,8 +172,12 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string) self.localPairingStatus.chatKey, ) of SecondaryFlow.LoginWithKeycard: - # TODO implement keycard function - discard + err = self.controller.restoreKeycardAccountAndLogin( + self.view.getKeycardEvent().keycardInfo.keyUID, + self.view.getKeycardEvent().keycardInfo.instanceUID, + self.exportedKeys, + recoverAccount = true + ) else: raise newException(ValueError, "Unknown flow: " & $self.currentFlow) @@ -167,6 +196,8 @@ proc finishAppLoading2[T](self: Module[T]) = singletonInstance.globalEvents.addCentralizedMetricIfEnabled(eventType, $(%*{"flowType": repr(self.currentFlow)})) + self.controller.stopKeycardService() + self.delegate.finishAppLoading() method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) = @@ -187,4 +218,38 @@ method onLocalPairingStatusUpdate*[T](self: Module[T], status: LocalPairingStatu self.localPairingStatus = status self.view.setSyncState(status.state.int) +method onKeycardStateUpdated*[T](self: Module[T], keycardEvent: KeycardEventDto) = + self.view.setKeycardEvent(keycardEvent) + + if keycardEvent.state == KeycardState.NotEmpty and self.view.getPinSettingState() == ProgressState.InProgress.int: + # We just finished setting the pin + self.view.setPinSettingState(ProgressState.Success.int) + + if keycardEvent.state == KeycardState.Authorized and self.view.getAuthorizationState() == ProgressState.InProgress.int: + # We just finished authorizing + self.view.setAuthorizationState(ProgressState.Success.int) + +method onKeycardSetPinFailure*[T](self: Module[T], error: string) = + self.view.setPinSettingState(ProgressState.Failed.int) + +method onKeycardAuthorizeFailure*[T](self: Module[T], error: string) = + self.view.setAuthorizationState(ProgressState.Failed.int) + +method onKeycardLoadMnemonicFailure*[T](self: Module[T], error: string) = + self.view.setAddKeyPairState(ProgressState.Failed.int) + +method onKeycardLoadMnemonicSuccess*[T](self: Module[T], keyUID: string) = + self.view.setAddKeyPairState(ProgressState.Success.int) + +method onKeycardExportKeysFailure*[T](self: Module[T], error: string) = + self.view.setRestoreKeysExportState(ProgressState.Failed.int) + +method onKeycardExportKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) = + self.exportedKeys = exportedKeys + self.view.setRestoreKeysExportState(ProgressState.Success.int) + +method exportRecoverKeys*[T](self: Module[T]) = + self.view.setRestoreKeysExportState(ProgressState.InProgress.int) + self.controller.exportRecoverKeysFromKeycard() + {.pop.} diff --git a/src/app/modules/onboarding/view.nim b/src/app/modules/onboarding/view.nim index 0146e9264b7..84589b6e37e 100644 --- a/src/app/modules/onboarding/view.nim +++ b/src/app/modules/onboarding/view.nim @@ -1,14 +1,17 @@ import NimQml import io_interface +from app_service/service/keycardV2/dto import KeycardEventDto QtObject: type View* = ref object of QObject delegate: io_interface.AccessInterface + keycardEvent: KeycardEventDto syncState: int - keycardState: int - keycardRemainingPinAttempts: int addKeyPairState: int + pinSettingState: int + authorizationState: int + restoreKeysExportState: int proc delete*(self: View) = self.QObject.delete @@ -35,25 +38,49 @@ QtObject: self.syncState = syncState self.syncStateChanged() + proc pinSettingStateChanged*(self: View) {.signal.} + proc getPinSettingState*(self: View): int {.slot.} = + return self.pinSettingState + QtProperty[int] pinSettingState: + read = getPinSettingState + notify = pinSettingStateChanged + proc setPinSettingState*(self: View, pinSettingState: int) = + self.pinSettingState = pinSettingState + self.pinSettingStateChanged() + + proc authorizationStateChanged*(self: View) {.signal.} + proc getAuthorizationState*(self: View): int {.slot.} = + return self.authorizationState + QtProperty[int] authorizationState: + read = getAuthorizationState + notify = authorizationStateChanged + proc setAuthorizationState*(self: View, authorizationState: int) = + self.authorizationState = authorizationState + self.authorizationStateChanged() + + proc restoreKeysExportStateChanged*(self: View) {.signal.} + proc getRestoreKeysExportState*(self: View): int {.slot.} = + return self.restoreKeysExportState + QtProperty[int] restoreKeysExportState: + read = getRestoreKeysExportState + notify = restoreKeysExportStateChanged + proc setRestoreKeysExportState*(self: View, restoreKeysExportState: int) = + self.restoreKeysExportState = restoreKeysExportState + self.restoreKeysExportStateChanged() + proc keycardStateChanged*(self: View) {.signal.} proc getKeycardState(self: View): int {.slot.} = - return self.keycardState + return self.keycardEvent.state.int QtProperty[int] keycardState: read = getKeycardState notify = keycardStateChanged - proc setKeycardState*(self: View, keycardState: int) = - self.keycardState = keycardState - self.keycardStateChanged() proc keycardRemainingPinAttemptsChanged*(self: View) {.signal.} proc getKeycardRemainingPinAttempts(self: View): int {.slot.} = - return self.keycardRemainingPinAttempts + return self.keycardEvent.keycardStatus.remainingAttemptsPIN QtProperty[int] keycardRemainingPinAttempts: read = getKeycardRemainingPinAttempts notify = keycardRemainingPinAttemptsChanged - proc setKeycardRemainingPinAttempts*(self: View, keycardRemainingPinAttempts: int) = - self.keycardRemainingPinAttempts = keycardRemainingPinAttempts - self.keycardRemainingPinAttemptsChanged() proc addKeyPairStateChanged*(self: View) {.signal.} proc getAddKeyPairState(self: View): int {.slot.} = @@ -65,15 +92,24 @@ QtObject: self.addKeyPairState = addKeyPairState self.addKeyPairStateChanged() + proc setKeycardEvent*(self: View, keycardEvent: KeycardEventDto) = + self.keycardEvent = keycardEvent + self.keycardStateChanged() + self.keycardRemainingPinAttemptsChanged() + + proc getKeycardEvent*(self: View): KeycardEventDto = + return self.keycardEvent ### slots ### - proc setPin(self: View, pin: string): bool {.slot.} = - return self.delegate.setPin(pin) + proc setPin(self: View, pin: string) {.slot.} = + self.delegate.initialize(pin) + + proc authorize(self: View, pin: string) {.slot.} = + self.delegate.authorize(pin) - # TODO find what does this do - # proc startKeypairTransfer(self: View) {.slot.} = - # self.delegate.startKeypairTransfer() + proc loadMnemonic(self: View, mnemonic: string) {.slot.} = + self.delegate.loadMnemonic(mnemonic) proc getPasswordStrengthScore(self: View, password: string, userName: string): int {.slot.} = return self.delegate.getPasswordStrengthScore(password, userName) @@ -90,9 +126,8 @@ QtObject: proc inputConnectionStringForBootstrapping(self: View, connectionString: string) {.slot.} = self.delegate.inputConnectionStringForBootstrapping(connectionString) - # TODO find what does this do - # proc mnemonicWasShown(self: View): string {.slot.} = - # return self.delegate.getMnemonic() + proc exportRecoverKeys(self: View) {.slot.} = + self.delegate.exportRecoverKeys() proc finishOnboardingFlow(self: View, flowInt: int, dataJson: string): string {.slot.} = self.delegate.finishOnboardingFlow(flowInt, dataJson) diff --git a/src/app_service/service/accounts/service.nim b/src/app_service/service/accounts/service.nim index c068f46ee4a..83acc31ffed 100644 --- a/src/app_service/service/accounts/service.nim +++ b/src/app_service/service/accounts/service.nim @@ -8,6 +8,7 @@ import ./dto/login_request import ./dto/restore_account_request from ../keycard/service import KeycardEvent, KeyDetails +from ../keycardV2/dto import KeycardExportedKeysDto, KeyDetailsV2 import ../../../backend/general as status_general import ../../../backend/core as status_core import ../../../backend/privacy as status_privacy @@ -240,6 +241,7 @@ QtObject: self.restoreAccountAndLogin(request) + # TODO remove this function when the old keycard service is removed proc restoreKeycardAccountAndLogin*(self: Service, keycardData: KeycardEvent, recoverAccount: bool, @@ -270,6 +272,35 @@ QtObject: return self.restoreAccountAndLogin(request) + proc restoreKeycardAccountAndLoginV2*(self: Service, + keyUid: string, + instanceUid: string, + keycardKeys: KeycardExportedKeysDto, + recoverAccount: bool, + ): string = + + let keycard = KeycardData( + keyUid: keyUid, + address: keycardKeys.masterKey.address, + whisperPrivateKey: keycardKeys.whisperKey.privateKey, + whisperPublicKey: keycardKeys.whisperKey.publicKey, + whisperAddress: keycardKeys.whisperKey.address, + walletPublicKey: keycardKeys.walletKey.publicKey, + walletAddress: keycardKeys.walletKey.address, + walletRootAddress: keycardKeys.walletRootKey.address, + eip1581Address: keycardKeys.eip1581Key.address, + encryptionPublicKey: keycardKeys.encryptionKey.publicKey, + ) + + var request = RestoreAccountRequest( + keycard: keycard, + fetchBackup: recoverAccount, + createAccountRequest: defaultCreateAccountRequest(), + ) + request.createAccountRequest.keycardInstanceUID = instanceUid + + return self.restoreAccountAndLogin(request) + proc restoreAccountAndLogin(self: Service, request: RestoreAccountRequest): string = try: let response = status_account.restoreAccountAndLogin(request) diff --git a/src/app_service/service/keycard/internal.nim b/src/app_service/service/keycard/internal.nim index ba786bbe9a2..a7d14c763c7 100644 --- a/src/app_service/service/keycard/internal.nim +++ b/src/app_service/service/keycard/internal.nim @@ -95,7 +95,7 @@ proc toTransactionSignature(jsonObj: JsonNode): TransactionSignature = if v == 1: result.v = "01" -proc toKeycardEvent(jsonObj: JsonNode): KeycardEvent = +proc toKeycardEvent*(jsonObj: JsonNode): KeycardEvent = discard jsonObj.getProp(ResponseParamErrorKey, result.error) discard jsonObj.getProp(ResponseParamInstanceUID, result.instanceUID) discard jsonObj.getProp(ResponseParamFreeSlots, result.freePairingSlots) diff --git a/src/app_service/service/keycard/service.nim b/src/app_service/service/keycard/service.nim index 80202dfa534..8b89c22e27c 100644 --- a/src/app_service/service/keycard/service.nim +++ b/src/app_service/service/keycard/service.nim @@ -127,7 +127,7 @@ QtObject: self.lastReceivedKeycardData = (flowType: flowType, flowEvent: flowEvent) self.events.emit(SIGNAL_KEYCARD_RESPONSE, KeycardLibArgs(flowType: flowType, flowEvent: flowEvent)) - proc receiveKeycardSignal(self: Service, signal: string) {.slot.} = + proc receiveKeycardSignal*(self: Service, signal: string) {.slot.} = self.busy = false self.processSignal(signal) if self.waitingFlows.len > 0: diff --git a/src/app_service/service/keycardV2/async_tasks.nim b/src/app_service/service/keycardV2/async_tasks.nim index de621bbeb79..2f184224b40 100644 --- a/src/app_service/service/keycardV2/async_tasks.nim +++ b/src/app_service/service/keycardV2/async_tasks.nim @@ -1,13 +1,68 @@ type - AsyncSetPinTaskArg = ref object of QObjectTaskArg + AsyncInitializeTaskArg = ref object of QObjectTaskArg pin: string + puk: string + rpcCounter: int -proc asyncSetPinTask(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[AsyncSetPinTaskArg](argEncoded) +proc asyncInitializeTask(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncInitializeTaskArg](argEncoded) try: - # TODO Call function from keycard_go - echo "Set pin ", arg.pin + let response = callRPC(arg.rpcCounter, "Initialize", %*{"pin": arg.pin, "puk": arg.puk}) arg.finish(%*{ + "response": response, + "error": "" + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) + +type + AsyncAuthorizeArg = ref object of QObjectTaskArg + pin: string + rpcCounter: int + +proc asyncAuthorizeTask(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncInitializeTaskArg](argEncoded) + try: + let response = callRPC(arg.rpcCounter, "Authorize", %*{"pin": arg.pin}) + arg.finish(%*{ + "response": response, + "error": "" + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) + +type + AsyncLoadMnemonicArg = ref object of QObjectTaskArg + mnemonic: string + rpcCounter: int + +proc asyncLoadMnemonicTask(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncLoadMnemonicArg](argEncoded) + try: + let response = callRPC(arg.rpcCounter, "LoadMnemonic", %*{"mnemonic": arg.mnemonic}) + arg.finish(%*{ + "response": response, + "error": "" + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) + +type + AsyncExportRecoverKeysArg = ref object of QObjectTaskArg + rpcCounter: int + +proc asyncExportRecoverKeysTask(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncExportRecoverKeysArg](argEncoded) + try: + let response = callRPC(arg.rpcCounter, "ExportRecoverKeys") + arg.finish(%*{ + "response": response, "error": "" }) except Exception as e: diff --git a/src/app_service/service/keycardV2/dto.nim b/src/app_service/service/keycardV2/dto.nim new file mode 100644 index 00000000000..b4672d2a353 --- /dev/null +++ b/src/app_service/service/keycardV2/dto.nim @@ -0,0 +1,169 @@ +import json, strutils +include ../../common/json_utils + + + +type StateString* = enum + UnknownReaderState = "unknown" + NoPCSC = "no-pcsc" + InternalError = "internal-error" + WaitingForReader = "waiting-for-reader" + WaitingForCard = "waiting-for-card" + ConnectingCard = "connecting-card" + ConnectionError = "connection-error" + NotKeycard = "not-keycard" + PairingError = "pairing-error" + EmptyKeycard = "empty-keycard" + NoAvailablePairingSlots = "no-available-pairing-slots" + BlockedPIN = "blocked-pin" # PIN remaining attempts == 0 + BlockedPUK = "blocked-puk" # PUK remaining attempts == 0 + FactoryResetting = "factory-resetting" + Ready = "ready" + Authorized = "authorized" + +type KeycardState* = enum + UnknownReaderState = -1, + NoPCSCService, + PluginReader, + InsertKeycard, + ReadingKeycard, + WrongKeycard, + NotKeycard, + MaxPairingSlotsReached, + Locked, + NotEmpty, + Empty, + Authorized + +type KeycardInfoDto* = object + initialized*: bool + instanceUID*: string + version*: string + availableSlots*: int + keyUID*: string + +type KeycardStatusDto* = object + remainingAttemptsPIN*: int + remainingAttemptsPUK*: int + keyInitialized*: bool + path*: string + +type KeycardEventDto* = object + state*: KeycardState + keycardInfo*: KeycardInfoDto + keycardStatus*: KeycardStatusDto + +type + KeyDetailsV2* = object + address*: string + publicKey*: string + privateKey*: string + +type KeycardExportedKeysDto* = object + eip1581Key*: KeyDetailsV2 + encryptionKey*: KeyDetailsV2 + masterKey*: KeyDetailsV2 + walletKey*: KeyDetailsV2 + walletRootKey*: KeyDetailsV2 + whisperKey*: KeyDetailsV2 + masterKeyAddress*: string + +proc fromStringStateToInt*(state: StateString): KeycardState = + case state + of StateString.UnknownReaderState: + result = KeycardState.UnknownReaderState + of StateString.NoPCSC: + result = KeycardState.NoPCSCService + of StateString.InternalError: + result = KeycardState.UnknownReaderState + of StateString.WaitingForReader: + result = KeycardState.PluginReader + of StateString.WaitingForCard: + result = KeycardState.InsertKeycard + of StateString.ConnectingCard: + result = KeycardState.ReadingKeycard + of StateString.ConnectionError: + result = KeycardState.NoPCSCService # TODO Change the UI states to have a connection error state + of StateString.NotKeycard: + result = KeycardState.NotKeycard + of StateString.PairingError: + result = KeycardState.NoPCSCService # TODO Change the UI states to have a pairing error state + of StateString.EmptyKeycard: + result = KeycardState.Empty + of StateString.NoAvailablePairingSlots: + result = KeycardState.MaxPairingSlotsReached + of StateString.BlockedPIN: + result = KeycardState.Locked + of StateString.BlockedPUK: + result = KeycardState.Locked # TODO do we need a new state for the PUK lock or we don't use PUK anymore? + of StateString.FactoryResetting: + result = KeycardState.NotEmpty # TODO create a new UI state + of StateString.Ready: + result = KeycardState.NotEmpty + of StateString.Authorized: + result = KeycardState.Authorized + else: + result = KeycardState.UnknownReaderState + +proc toKeycardInfoDto*(jsonObj: JsonNode): KeycardInfoDto = + result = KeycardInfoDto() + discard jsonObj.getProp("initialized", result.initialized) + discard jsonObj.getProp("instanceUID", result.instanceUID) + discard jsonObj.getProp("version", result.version) + discard jsonObj.getProp("availableSlots", result.availableSlots) + if jsonObj.getProp("keyUID", result.keyUID) and + result.keyUID.len > 0 and + not result.keyUID.startsWith("0x"): + result.keyUID = "0x" & result.keyUID + +proc toKeycardStatusDto*(jsonObj: JsonNode): KeycardStatusDto = + result = KeycardStatusDto() + discard jsonObj.getProp("remainingAttemptsPIN", result.remainingAttemptsPIN) + discard jsonObj.getProp("remainingAttemptsPUK", result.remainingAttemptsPUK) + discard jsonObj.getProp("keyInitialized", result.keyInitialized) + discard jsonObj.getProp("path", result.path) + +proc toKeycardEventDto*(jsonObj: JsonNode): KeycardEventDto = + result = KeycardEventDto() + + try: + result.state = parseEnum[StateString](jsonObj["state"].getStr).fromStringStateToInt + except: + result.state = KeycardState.UnknownReaderState + + var keycardInfoObj: JsonNode + if jsonObj.getProp("keycardInfo", keycardInfoObj): + result.keycardInfo = keycardInfoObj.toKeycardInfoDto + + var keycardStatusObj: JsonNode + if jsonObj.getProp("keycardStatus", keycardStatusObj): + result.keycardStatus = keycardStatusObj.toKeycardStatusDto + +proc toKeyDetails(jsonObj: JsonNode): KeyDetailsV2 = + discard jsonObj.getProp("address", result.address) + discard jsonObj.getProp("privateKey", result.privateKey) + if jsonObj.getProp("publicKey", result.publicKey): + result.publicKey = "0x" & result.publicKey + +proc toKeycardExportedKeysDto*(jsonObj: JsonNode): KeycardExportedKeysDto = + result = KeycardExportedKeysDto() + + var obj: JsonNode + + if jsonObj.getProp("eip1581", obj): + result.eip1581Key = toKeyDetails(obj) + + if jsonObj.getProp("encryptionPrivateKey", obj): + result.encryptionKey = toKeyDetails(obj) + + if jsonObj.getProp("masterKey", obj): + result.masterKey = toKeyDetails(obj) + + if jsonObj.getProp("walletKey", obj): + result.walletKey = toKeyDetails(obj) + + if jsonObj.getProp("walletRootKey", obj): + result.walletRootKey = toKeyDetails(obj) + + if jsonObj.getProp("whisperPrivateKey", obj): + result.whisperKey = toKeyDetails(obj) diff --git a/src/app_service/service/keycardV2/service.nim b/src/app_service/service/keycardV2/service.nim index 13051e016dc..48682041f67 100644 --- a/src/app_service/service/keycardV2/service.nim +++ b/src/app_service/service/keycardV2/service.nim @@ -1,67 +1,239 @@ -import NimQml, json, chronicles, strutils -# import keycard_go +import NimQml, json, chronicles, strutils, random, json_serialization +import keycard_go import app/global/global_singleton import app/core/eventemitter import app/core/tasks/[qt, threadpool] +import app_service/service/keycard/service as old_keycard_service +import ../../../backend/response_type +import ../../../constants as status_const +import ./dto + +proc callRPC*(rpcCounter: int, methodName: string, params: JsonNode = %*{}): string = + var request = %*{ + "id": rpcCounter, + "method": "keycard." & methodName, + "params": %*[ params ], + } + var response = keycard_go.keycardCallRPC($request) + return response + include ../../common/mnemonics include async_tasks logScope: topics = "keycardV2-service" +const SupportedMnemonicLength12* = 12 +const PUKLengthForStatusApp* = 12 + +const SIGNAL_KEYCARD_STATE_UPDATED* = "keycardStateUpdated" +const SIGNAL_KEYCARD_SET_PIN_FAILURE* = "keycardSetPinFailure" +const SIGNAL_KEYCARD_AUTHORIZE_FAILURE* = "keycardAuthorizeFailure" +const SIGNAL_KEYCARD_LOAD_MNEMONIC_FAILURE* = "keycardLoadMnemonicFailure" +const SIGNAL_KEYCARD_LOAD_MNEMONIC_SUCCESS* = "keycardLoadMnemonicSuccess" +const SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE* = "keycardExportKeysFailure" +const SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS* = "keycardExportKeysSuccess" + +type + KeycardEventArg* = ref object of Args + keycardEvent*: KeycardEventDto + + KeycardErrorArg* = ref object of Args + error*: string + + KeycardKeyUIDArg* = ref object of Args + keyUID*: string + + KeycardExportedKeysArg* = ref object of Args + exportedKeys*: KeycardExportedKeysDto + QtObject: type Service* = ref object of QObject events: EventEmitter threadpool: ThreadPool + rpcCounter: int + oldKeyCardService: old_keycard_service.Service proc delete*(self: Service) = self.QObject.delete - proc newService*(events: EventEmitter, threadpool: ThreadPool): Service = + proc newService*(events: EventEmitter, threadpool: ThreadPool, oldKeyCardService: old_keycard_service.Service): Service = new(result, delete) result.QObject.setup result.events = events result.threadpool = threadpool + result.rpcCounter = 0 + result.oldKeyCardService = oldKeyCardService + + proc initializeRPC(self: Service) + proc start*(self: Service, storageDir: string) proc init*(self: Service) = + debug "KeycardServiceV2 init" + self.initializeRPC() + self.start(status_const.KEYCARDPAIRINGDATAFILE) discard - proc receiveKeycardSignal(self: Service, signal: string) {.slot.} = - var jsonSignal: JsonNode - try: - jsonSignal = signal.parseJson - except: - error "Invalid signal received", data = signal - return + proc initializeRPC(self: Service) {.slot.} = + var response = keycard_go.keycardInitializeRPC() - debug "keycard_signal", response=signal + proc callRPC(self: Service, methodName: string, params: JsonNode = %*{}): string = + self.rpcCounter += 1 + return callRPC(self.rpcCounter, methodName, params) - proc buildSeedPhrasesFromIndexes*(self: Service, seedPhraseIndexes: seq[int]): seq[string] = + proc start*(self: Service, storageDir: string) = + discard self.callRPC("Start", %*{"storageFilePath": storageDir}) + + proc stop*(self: Service) = + discard self.callRPC("Stop") + + proc buildSeedPhrasesFromIndexes*(seedPhraseIndexes: JsonNode): seq[string] = var seedPhrase: seq[string] - for ind in seedPhraseIndexes: - seedPhrase.add(englishWords[ind]) + for ind in seedPhraseIndexes.items: + seedPhrase.add(englishWords[ind.getInt]) return seedPhrase - proc getMnemonicIndexes*(self: Service): seq[int] = - # TODO call lib to get mnemonic indexes - echo "Get mnemonic indexes" - return @[] + proc generateMnemonic*(self: Service, length: int): string = + try: + let response = self.callRPC("GenerateMnemonic", %*{"length": length}) + let rpcResponseObj = response.parseJson + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + let error = Json.decode(rpcResponseObj["error"].getStr, RpcError) + raise newException(RpcException, "Error loading mnemonic: " & error.message) + + let words = buildSeedPhrasesFromIndexes(rpcResponseObj["result"]["indexes"]) + var jArray = newJArray() + for item in words: + jArray.add(%item) + return $jArray + except Exception as e: + error "error generating mnemonic", err=e.msg - proc setPin*(self: Service, pin: string) = - let arg = AsyncSetPinTaskArg( - tptr: asyncSetPinTask, + proc loadMnemonic*(self: Service, mnemonic: string) = + self.rpcCounter += 1 + let arg = AsyncLoadMnemonicArg( + tptr: asyncLoadMnemonicTask, vptr: cast[uint](self.vptr), - slot: "onAsyncSetPinResponse", + slot: "onAsyncLoadMnemonicResponse", + mnemonic: mnemonic, + rpcCounter: self.rpcCounter, + ) + self.threadpool.start(arg) + + proc onAsyncLoadMnemonicResponse(self: Service, response: string) {.slot.} = + try: + let responseObj = response.parseJson + if responseObj{"error"}.kind != JNull and responseObj{"error"}.getStr != "": + raise newException(CatchableError, responseObj{"error"}.getStr) + + let rpcResponseObj = responseObj["response"].getStr().parseJson() + + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + let error = Json.decode(rpcResponseObj["error"].getStr, RpcError) + raise newException(RpcException, "Error loading mnemonic: " & error.message) + + self.events.emit(SIGNAL_KEYCARD_LOAD_MNEMONIC_SUCCESS, KeycardKeyUIDArg(keyUID: rpcResponseObj["result"]["keyUID"].getStr)) + except Exception as e: + error "error loading mnemonic", err=e.msg + self.events.emit(SIGNAL_KEYCARD_LOAD_MNEMONIC_FAILURE, KeycardErrorArg(error: e.msg)) + + proc asyncAuthorize*(self: Service, pin: string) = + self.rpcCounter += 1 + let arg = AsyncAuthorizeArg( + tptr: asyncAuthorizeTask, + vptr: cast[uint](self.vptr), + slot: "onAsyncAuthorizeResponse", pin: pin, + rpcCounter: self.rpcCounter, ) self.threadpool.start(arg) - proc onAsyncSetPinResponse*(self: Service, response: string) {.slot.} = + proc onAsyncAuthorizeResponse*(self: Service, response: string) {.slot.} = try: - let rpcResponseObj = response.parseJson - echo "Set the pin ", response + let responseObj = response.parseJson + + if responseObj{"error"}.kind != JNull and responseObj{"error"}.getStr != "": + raise newException(CatchableError, responseObj{"error"}.getStr) - if (rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != ""): - raise newException(CatchableError, rpcResponseObj{"error"}.getStr) + let rpcResponseObj = responseObj["response"].getStr().parseJson() + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + let error = Json.decode(rpcResponseObj["error"].getStr, RpcError) + raise newException(RpcException, "Error authorizing: " & error.message) except Exception as e: - error "error set pin: ", msg = e.msg \ No newline at end of file + error "error set pin: ", msg = e.msg + self.events.emit(SIGNAL_KEYCARD_AUTHORIZE_FAILURE, KeycardErrorArg(error: e.msg)) + + proc receiveKeycardSignalV2(self: Service, signal: string) {.slot.} = + try: + # Since only one service can register to signals, we pass the signal to the old service too + self.oldKeyCardService.receiveKeycardSignal(signal) + var jsonSignal = signal.parseJson + + if jsonSignal["type"].getStr == "status-changed": + let keycardEvent = jsonSignal["event"].toKeycardEventDto() + + self.events.emit(SIGNAL_KEYCARD_STATE_UPDATED, KeycardEventArg(keycardEvent: keycardEvent)) + except Exception as e: + error "error receiving a keycard signal", err=e.msg, data = signal + + proc initialize*(self: Service, pin: string, puk: string) = + self.rpcCounter += 1 + let arg = AsyncInitializeTaskArg( + tptr: asyncInitializeTask, + vptr: cast[uint](self.vptr), + slot: "onAsyncInitializeResponse", + pin: pin, + puk: puk, + rpcCounter: self.rpcCounter, + ) + self.threadpool.start(arg) + + proc onAsyncInitializeResponse*(self: Service, response: string) {.slot.} = + try: + let responseObj = response.parseJson + + if responseObj{"error"}.kind != JNull and responseObj{"error"}.getStr != "": + raise newException(CatchableError, responseObj{"error"}.getStr) + + let rpcResponseObj = responseObj["response"].getStr().parseJson() + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + let error = Json.decode(rpcResponseObj["error"].getStr, RpcError) + raise newException(RpcException, "Error authorizing: " & error.message) + except Exception as e: + error "error set pin: ", msg = e.msg + self.events.emit(SIGNAL_KEYCARD_SET_PIN_FAILURE, KeycardErrorArg(error: e.msg)) + + proc generateRandomPUK*(self: Service): string = + randomize() + for i in 0 ..< PUKLengthForStatusApp: + result = result & $rand(0 .. 9) + + proc asyncExportRecoverKeys*(self: Service) = + self.rpcCounter += 1 + let arg = AsyncExportRecoverKeysArg( + tptr: asyncExportRecoverKeysTask, + vptr: cast[uint](self.vptr), + slot: "onAsyncExportRecoverKeys", + rpcCounter: self.rpcCounter, + ) + self.threadpool.start(arg) + + proc onAsyncExportRecoverKeys*(self: Service, response: string) {.slot.} = + try: + let responseObj = response.parseJson + + if responseObj{"error"}.kind != JNull and responseObj{"error"}.getStr != "": + raise newException(CatchableError, responseObj{"error"}.getStr) + + let rpcResponseObj = responseObj["response"].getStr().parseJson() + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + let error = Json.decode(rpcResponseObj["error"].getStr, RpcError) + raise newException(RpcException, "Error authorizing: " & error.message) + + let keys = rpcResponseObj["result"]["keys"].toKeycardExportedKeysDto() + self.events.emit(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS, KeycardExportedKeysArg(exportedKeys: keys)) + except Exception as e: + error "error exporting recover keys", msg = e.msg + self.events.emit(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE, KeycardErrorArg(error: e.msg)) + + \ No newline at end of file diff --git a/src/app_service/service/transaction/dto.nim b/src/app_service/service/transaction/dto.nim index a62622e0186..8acce560514 100644 --- a/src/app_service/service/transaction/dto.nim +++ b/src/app_service/service/transaction/dto.nim @@ -25,6 +25,7 @@ type type PendingTransactionTypeDto* {.pure.} = enum + Unknown = "Unknown" RegisterENS = "RegisterENS", SetPubKey = "SetPubKey", ReleaseENS = "ReleaseENS", @@ -72,15 +73,15 @@ type symbol*: string proc getTotalFees(tip: string, baseFee: string, gasUsed: string, maxFee: string): string = - var maxFees = stint.fromHex(Uint256, maxFee) - var totalGasUsed = stint.fromHex(Uint256, tip) + stint.fromHex(Uint256, baseFee) - if totalGasUsed > maxFees: - totalGasUsed = maxFees - var totalGasUsedInHex = (totalGasUsed * stint.fromHex(Uint256, gasUsed)).toHex - return totalGasUsedInHex + var maxFees = stint.fromHex(Uint256, maxFee) + var totalGasUsed = stint.fromHex(Uint256, tip) + stint.fromHex(Uint256, baseFee) + if totalGasUsed > maxFees: + totalGasUsed = maxFees + var totalGasUsedInHex = (totalGasUsed * stint.fromHex(Uint256, gasUsed)).toHex + return totalGasUsedInHex proc getMaxTotalFees(maxFee: string, gasLimit: string): string = - return (stint.fromHex(Uint256, maxFee) * stint.fromHex(Uint256, gasLimit)).toHex + return (stint.fromHex(Uint256, maxFee) * stint.fromHex(Uint256, gasLimit)).toHex proc toTransactionDto*(jsonObj: JsonNode): TransactionDto = result = TransactionDto() diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index ec169758ef4..0b1be1cea4b 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -268,7 +268,13 @@ QtObject: toTokenKey: watchTxResult["toTokenKey"].getStr, toAmount: watchTxResult["toAmount"].getStr, ) - self.events.emit(parseEnum[PendingTransactionTypeDto](watchTxResult["trxType"].getStr).event, ev) + var transactionType = PendingTransactionTypeDto.Unknown + try: + transactionType = parseEnum[PendingTransactionTypeDto](watchTxResult["trxType"].getStr) + except: + discard + + self.events.emit(transactionType.event, ev) transactions.checkRecentHistory(@[chainId], @[address]) proc watchTransaction*( diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index 242ffafe900..9b6728c9d9c 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -6,11 +6,10 @@ import app/core/main import constants as main_constants import statusq_bridge -import app/global/global_singleton +import app/global/[global_singleton, feature_flags] import app/global/local_app_settings import app/boot/app_controller - when defined(macosx) and defined(arm64): import posix @@ -77,9 +76,15 @@ proc setupRemoteSignalsHandling() = signal_handler(signalsManagerQObjPointer, p0, "receiveSignal") status_go.setSignalEventCallback(callbackStatusGo) - var callbackKeycardGo: keycard_go.KeycardSignalCallback = proc(p0: cstring) {.cdecl.} = - if keycardServiceQObjPointer != nil: - signal_handler(keycardServiceQObjPointer, p0, "receiveKeycardSignal") + var callbackKeycardGo: keycard_go.KeycardSignalCallback + if singletonInstance.featureFlags().getOnboardingV2Enabled(): + callbackKeycardGo = proc(p0: cstring) {.cdecl.} = + if keycardServiceQObjPointer != nil: + signal_handler(keycardServiceQObjPointer, p0, "receiveKeycardSignalV2") + else: + callbackKeycardGo = proc(p0: cstring) {.cdecl.} = + if keycardServiceQObjPointer != nil: + signal_handler(keycardServiceQObjPointer, p0, "receiveKeycardSignal") keycard_go.setSignalEventCallback(callbackKeycardGo) proc ensureDirectories*(dataDir, tmpDir, logDir: string) = @@ -240,7 +245,10 @@ proc mainProc() = # We need these global variables in order to be able to access the application # from the non-closure callback passed to `statusgo_backend.setSignalEventCallback` signalsManagerQObjPointer = cast[pointer](statusFoundation.signalsManager.vptr) - keycardServiceQObjPointer = cast[pointer](appController.keycardService.vptr) + if singletonInstance.featureFlags().getOnboardingV2Enabled(): + keycardServiceQObjPointer = cast[pointer](appController.keycardServiceV2.vptr) + else: + keycardServiceQObjPointer = cast[pointer](appController.keycardService.vptr) setupRemoteSignalsHandling() info "app info", version=APP_VERSION, commit=GIT_COMMIT, currentDateTime=now() diff --git a/storybook/pages/KeycardAddKeyPairPagePage.qml b/storybook/pages/KeycardAddKeyPairPagePage.qml index d9c6b898716..8e9c05eef0c 100644 --- a/storybook/pages/KeycardAddKeyPairPagePage.qml +++ b/storybook/pages/KeycardAddKeyPairPagePage.qml @@ -17,10 +17,10 @@ Item { onKeypairAddTryAgainRequested: { console.warn("!!! onKeypairAddTryAgainRequested") - ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.AddKeyPairState.InProgress) + ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.ProgressState.InProgress) Backpressure.debounce(root, 2000, function() { console.warn("!!! SIMULATION: SUCCESS") - ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.AddKeyPairState.Success) + ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.ProgressState.Success) })() } onKeypairAddContinueRequested: console.warn("!!! onKeypairAddContinueRequested") @@ -35,7 +35,7 @@ Item { width: 350 textRole: "name" valueRole: "value" - model: Onboarding.getModelFromEnum("AddKeyPairState") + model: Onboarding.getModelFromEnum("ProgressState") } } diff --git a/storybook/pages/KeycardEnterPinPagePage.qml b/storybook/pages/KeycardEnterPinPagePage.qml index dd62ce98f9f..ad799973413 100644 --- a/storybook/pages/KeycardEnterPinPagePage.qml +++ b/storybook/pages/KeycardEnterPinPagePage.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import AppLayouts.Onboarding2.pages 1.0 +import AppLayouts.Onboarding.enums 1.0 Item { id: root @@ -12,15 +13,12 @@ Item { KeycardEnterPinPage { id: page anchors.fill: parent - tryToSetPinFunction: (pin) => { - const valid = pin === root.existingPin - if (!valid) - remainingAttempts-- - return valid - } + remainingAttempts: 3 unblockWithPukAvailable: ctrlUnblockWithPUK.checked - onKeycardPinEntered: (pin) => { + authorizationState: Onboarding.ProgressState.Idle + restoreKeysExportState: Onboarding.ProgressState.Idle + onAuthorizationRequested: (pin) => { console.warn("!!! PIN:", pin) console.warn("!!! RESETTING FLOW") state = "entering" diff --git a/storybook/pages/OnboardingLayoutPage.qml b/storybook/pages/OnboardingLayoutPage.qml index f4273fd39fe..2ea1a4673c7 100644 --- a/storybook/pages/OnboardingLayoutPage.qml +++ b/storybook/pages/OnboardingLayoutPage.qml @@ -60,8 +60,11 @@ SplitView { id: store property int keycardState: Onboarding.KeycardState.NoPCSCService - property int addKeyPairState: Onboarding.AddKeyPairState.InProgress - property int syncState: Onboarding.SyncState.InProgress + property int addKeyPairState: Onboarding.ProgressState.Idle + property int pinSettingState: Onboarding.ProgressState.Idle + property int authorizationState: Onboarding.ProgressState.Idle + property int restoreKeysExportState: Onboarding.ProgressState.Idle + property int syncState: Onboarding.ProgressState.Idle property int keycardRemainingPinAttempts: 2 property int keycardRemainingPukAttempts: 3 @@ -91,9 +94,16 @@ SplitView { return valid } - function startKeypairTransfer() { // -> void - logs.logEvent("OnboardingStore.startKeypairTransfer") - addKeyPairState = Onboarding.AddKeyPairState.InProgress + function authorize(pin: string) { + logs.logEvent("OnboardingStore.authorize", ["pin"], arguments) + } + + function loadMnemonic(mnemonic) { // -> void + logs.logEvent("OnboardingStore.loadMnemonic", ["mnemonic"], arguments) + } + + function exportRecoverKeys() { // -> void + logs.logEvent("OnboardingStore.exportRecoverKeys") } // password @@ -110,17 +120,13 @@ SplitView { function getMnemonic() { // -> string logs.logEvent("OnboardingStore.getMnemonic()") - return mockDriver.seedWords.join(" ") + return JSON.stringify(mockDriver.seedWords) } function mnemonicWasShown() { // -> void logs.logEvent("OnboardingStore.mnemonicWasShown()") } - function removeMnemonic() { // -> void - logs.logEvent("OnboardingStore.removeMnemonic()") - } - function validateLocalPairingConnectionString(connectionString: string) { // -> bool logs.logEvent("OnboardingStore.validateLocalPairingConnectionString", ["connectionString"], arguments) return !Number.isNaN(parseInt(connectionString)) @@ -477,7 +483,7 @@ SplitView { } Repeater { - model: Onboarding.getModelFromEnum("AddKeyPairState") + model: Onboarding.getModelFromEnum("ProgressState") RoundButton { text: modelData.name @@ -498,7 +504,6 @@ SplitView { } Flow { - Layout.fillWidth: true spacing: 2 ButtonGroup { @@ -506,7 +511,7 @@ SplitView { } Repeater { - model: Onboarding.getModelFromEnum("SyncState") + model: Onboarding.getModelFromEnum("ProgressState") RoundButton { text: modelData.name @@ -519,6 +524,90 @@ SplitView { } } } + + ToolSeparator {} + + Label { + text: "Pin Setting state:" + } + + Flow { + spacing: 2 + + ButtonGroup { + id: pinSettingStateButtonGroup + } + + Repeater { + model: Onboarding.getModelFromEnum("ProgressState") + + RoundButton { + text: modelData.name + checkable: true + checked: store.pinSettingState === modelData.value + + ButtonGroup.group: pinSettingStateButtonGroup + + onClicked: store.pinSettingState = modelData.value + } + } + } + } + + RowLayout { + Label { + text: "Authorization state:" + } + + Flow { + spacing: 2 + + ButtonGroup { + id: authorizationStateButtonGroup + } + + Repeater { + model: Onboarding.getModelFromEnum("ProgressState") + + RoundButton { + text: modelData.name + checkable: true + checked: store.authorizationState === modelData.value + + ButtonGroup.group: authorizationStateButtonGroup + + onClicked: store.authorizationState = modelData.value + } + } + } + + ToolSeparator {} + + Label { + text: "Restore Keys Export state:" + } + + Flow { + spacing: 2 + + ButtonGroup { + id: restoreKeysExportStateButtonGroup + } + + Repeater { + model: Onboarding.getModelFromEnum("ProgressState") + + RoundButton { + text: modelData.name + checkable: true + checked: store.restoreKeysExportState === modelData.value + + ButtonGroup.group: restoreKeysExportStateButtonGroup + + onClicked: store.restoreKeysExportState = modelData.value + } + } + } } Item { diff --git a/storybook/pages/SyncProgressPagePage.qml b/storybook/pages/SyncProgressPagePage.qml index 53e71e2a3db..805f44030d2 100644 --- a/storybook/pages/SyncProgressPagePage.qml +++ b/storybook/pages/SyncProgressPagePage.qml @@ -15,10 +15,10 @@ Item { syncState: ctrlState.currentValue onRestartSyncRequested: { console.warn("!!! RESTART SYNC REQUESTED") - ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.SyncState.InProgress) + ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.ProgressState.InProgress) Backpressure.debounce(root, 2000, function() { console.warn("!!! SIMULATION: SUCCESS") - ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.SyncState.Success) + ctrlState.currentIndex = ctrlState.indexOfValue(Onboarding.ProgressState.Success) })() } onLoginToAppRequested: console.warn("!!! LOGIN TO APP REQUESTED") @@ -32,7 +32,7 @@ Item { width: 300 textRole: "name" valueRole: "value" - model: Onboarding.getModelFromEnum("SyncState") + model: Onboarding.getModelFromEnum("ProgressState") } } diff --git a/storybook/qmlTests/tests/tst_OnboardingLayout.qml b/storybook/qmlTests/tests/tst_OnboardingLayout.qml index aa6b1531800..59b3a9f5b05 100644 --- a/storybook/qmlTests/tests/tst_OnboardingLayout.qml +++ b/storybook/qmlTests/tests/tst_OnboardingLayout.qml @@ -24,6 +24,9 @@ Item { QtObject { id: mockDriver property int keycardState // enum Onboarding.KeycardState + property int pinSettingState // enum Onboarding.ProgressState + property int authorizationState // enum Onboarding.ProgressState + property int restoreKeysExportState // enum Onboarding.ProgressState property bool biometricsAvailable property string existingPin @@ -55,6 +58,9 @@ Item { onboardingStore: OnboardingStore { readonly property int keycardState: mockDriver.keycardState // enum Onboarding.KeycardState + readonly property int pinSettingState: mockDriver.pinSettingState // enum Onboarding.ProgressState + readonly property int authorizationState: mockDriver.authorizationState // enum Onboarding.ProgressState + readonly property int restoreKeysExportState: mockDriver.restoreKeysExportState // enum Onboarding.ProgressState property int keycardRemainingPinAttempts: 5 function setPin(pin: string) { @@ -64,25 +70,30 @@ Item { return valid } - readonly property int addKeyPairState: Onboarding.AddKeyPairState.InProgress // enum Onboarding.AddKeyPairState - function startKeypairTransfer() {} + function authorize(pin: string) {} + + readonly property int addKeyPairState: Onboarding.ProgressState.InProgress // enum Onboarding.ProgressState // password function getPasswordStrengthScore(password: string) { return Math.min(password.length-1, 4) } + function finishOnboardingFlow(flow: int, data: Object) { // -> bool + return true + } + // seedphrase/mnemonic function validMnemonic(mnemonic: string) { return mnemonic === mockDriver.mnemonic } function getMnemonic() { - return mockDriver.seedWords.join(" ") + return JSON.stringify(mockDriver.seedWords) } - function mnemonicWasShown() {} - function removeMnemonic() {} + function loadMnemonic(mnemonic) {} + function exportRecoverKeys() {} - readonly property int syncState: Onboarding.SyncState.InProgress // enum Onboarding.SyncState + readonly property int syncState: Onboarding.ProgressState.InProgress // enum Onboarding.ProgressState function validateLocalPairingConnectionString(connectionString: string) { return !Number.isNaN(parseInt(connectionString)) } @@ -153,6 +164,9 @@ Item { function cleanup() { mockDriver.keycardState = -1 + mockDriver.pinSettingState = 0 + mockDriver.authorizationState = 0 + mockDriver.restoreKeysExportState = 0 mockDriver.biometricsAvailable = false mockDriver.existingPin = "" dynamicSpy.cleanup() @@ -179,13 +193,13 @@ Item { // common variant data for all flow related TDD tests function init_data() { return [ { tag: "shareUsageData+bioEnabled", shareBtnName: "btnShare", shareResult: true, biometrics: true, bioEnabled: true }, - { tag: "dontShareUsageData+bioEnabled", shareBtnName: "btnDontShare", shareResult: false, biometrics: true, bioEnabled: true }, + { tag: "dontShareUsageData+bioEnabled", shareBtnName: "btnDontShare", shareResult: false, biometrics: true, bioEnabled: true }, - { tag: "shareUsageData+bioDisabled", shareBtnName: "btnShare", shareResult: true, biometrics: true, bioEnabled: false }, - { tag: "dontShareUsageData+bioDisabled", shareBtnName: "btnDontShare", shareResult: false, biometrics: true, bioEnabled: false }, + { tag: "shareUsageData+bioDisabled", shareBtnName: "btnShare", shareResult: true, biometrics: true, bioEnabled: false }, + { tag: "dontShareUsageData+bioDisabled", shareBtnName: "btnDontShare", shareResult: false, biometrics: true, bioEnabled: false }, - { tag: "shareUsageData-bio", shareBtnName: "btnShare", shareResult: true, biometrics: false }, - { tag: "dontShareUsageData-bio", shareBtnName: "btnDontShare", shareResult: false, biometrics: false }, + { tag: "shareUsageData-bio", shareBtnName: "btnShare", shareResult: true, biometrics: false }, + { tag: "dontShareUsageData-bio", shareBtnName: "btnDontShare", shareResult: false, biometrics: false }, ] } @@ -306,6 +320,7 @@ Item { compare(resultData.seedphrase, "") } + // FLOW: Create Profile -> Use a recovery phrase (create profile with seedphrase) function test_flow_createProfile_withSeedphrase(data) { verify(!!controlUnderTest) @@ -454,6 +469,9 @@ Item { keyClickSequence(newPin) tryCompare(dynamicSpy, "count", 1) compare(dynamicSpy.signalArguments[0][0], newPin) + dynamicSpy.setup(page, "keycardAuthorized") + mockDriver.authorizationState = Onboarding.ProgressState.Success + tryCompare(dynamicSpy, "count", 1) // PAGE 7: Backup your recovery phrase (intro) page = getCurrentPage(stack, BackupSeedphraseIntro) @@ -520,8 +538,8 @@ Item { // PAGE 12: Adding key pair to Keycard page = getCurrentPage(stack, KeycardAddKeyPairPage) - tryCompare(page, "addKeyPairState", Onboarding.AddKeyPairState.InProgress) - page.addKeyPairState = Onboarding.AddKeyPairState.Success // SIMULATION + tryCompare(page, "addKeyPairState", Onboarding.ProgressState.InProgress) + page.addKeyPairState = Onboarding.ProgressState.Success // SIMULATION btnContinue = findChild(page, "btnContinue") verify(!!btnContinue) compare(btnContinue.enabled, true) @@ -546,7 +564,7 @@ Item { compare(resultData.password, "") compare(resultData.enableBiometrics, data.biometrics && data.bioEnabled) compare(resultData.keycardPin, newPin) - compare(resultData.seedphrase, "") + compare(resultData.seedphrase, mockDriver.seedWords.join(",")) } // FLOW: Create Profile -> Use an empty Keycard -> Use an existing recovery phrase (create profile with keycard + existing seedphrase) @@ -600,6 +618,10 @@ Item { keyClickSequence(newPin) tryCompare(dynamicSpy, "count", 1) compare(dynamicSpy.signalArguments[0][0], newPin) + dynamicSpy.setup(page, "keycardPinSuccessfullySet") + mockDriver.pinSettingState = Onboarding.ProgressState.Success + tryCompare(dynamicSpy, "count", 1) + // PAGE 7: Create profile on empty Keycard using a recovery phrase page = getCurrentPage(stack, SeedphrasePage) @@ -613,11 +635,14 @@ Item { keySequence(StandardKey.Paste) compare(btnContinue.enabled, true) mouseClick(btnContinue) + dynamicSpy.setup(page, "keycardAuthorized") + mockDriver.authorizationState = Onboarding.ProgressState.Success + tryCompare(dynamicSpy, "count", 1) // PAGE 8: Adding key pair to Keycard page = getCurrentPage(stack, KeycardAddKeyPairPage) - tryCompare(page, "addKeyPairState", Onboarding.AddKeyPairState.InProgress) - page.addKeyPairState = Onboarding.AddKeyPairState.Success // SIMULATION + tryCompare(page, "addKeyPairState", Onboarding.ProgressState.InProgress) + page.addKeyPairState = Onboarding.ProgressState.Success // SIMULATION const btnContinue2 = findChild(page, "btnContinue") verify(!!btnContinue2) compare(btnContinue2.enabled, true) @@ -801,8 +826,8 @@ Item { // PAGE 5: Profile sync in progress page = getCurrentPage(stack, SyncProgressPage) - tryCompare(page, "syncState", Onboarding.SyncState.InProgress) - page.syncState = Onboarding.SyncState.Success // SIMULATION + tryCompare(page, "syncState", Onboarding.ProgressState.InProgress) + page.syncState = Onboarding.ProgressState.Success // SIMULATION const btnLogin2 = findChild(page, "btnLogin") // TODO test other flows/buttons here as well verify(!!btnLogin2) compare(btnLogin2.enabled, true) @@ -868,11 +893,19 @@ Item { // PAGE 5: Enter Keycard PIN page = getCurrentPage(stack, KeycardEnterPinPage) - dynamicSpy.setup(page, "keycardPinEntered") + dynamicSpy.setup(page, "authorizationRequested") keyClickSequence(mockDriver.existingPin) tryCompare(dynamicSpy, "count", 1) compare(dynamicSpy.signalArguments[0][0], mockDriver.existingPin) + dynamicSpy.setup(page, "exportKeysRequested") + mockDriver.authorizationState = Onboarding.ProgressState.Success + tryCompare(dynamicSpy, "count", 1) + + dynamicSpy.setup(page, "exportKeysDone") + mockDriver.restoreKeysExportState = Onboarding.ProgressState.Success + tryCompare(dynamicSpy, "count", 1) + // PAGE 6: Enable Biometrics if (data.biometrics) { page = getCurrentPage(stack, EnableBiometricsPage) @@ -891,7 +924,7 @@ Item { verify(!!resultData) compare(resultData.password, "") compare(resultData.enableBiometrics, data.biometrics && data.bioEnabled) - compare(resultData.keycardPin, mockDriver.existingPin) + compare(resultData.keycardPin, "") compare(resultData.seedphrase, "") } @@ -1206,11 +1239,14 @@ Item { keyClickSequence(newPin) tryCompare(dynamicSpy, "count", 1) compare(dynamicSpy.signalArguments[0][0], newPin) + dynamicSpy.setup(page, "keycardAuthorized") + mockDriver.authorizationState = Onboarding.ProgressState.Success + tryCompare(dynamicSpy, "count", 1) // PAGE 6: Adding key pair to Keycard page = getCurrentPage(stack, KeycardAddKeyPairPage) - tryCompare(page, "addKeyPairState", Onboarding.AddKeyPairState.InProgress) - page.addKeyPairState = Onboarding.AddKeyPairState.Success // SIMULATION + tryCompare(page, "addKeyPairState", Onboarding.ProgressState.InProgress) + page.addKeyPairState = Onboarding.ProgressState.Success // SIMULATION btnContinue = findChild(page, "btnContinue") verify(!!btnContinue) diff --git a/ui/StatusQ/src/onboarding/enums.h b/ui/StatusQ/src/onboarding/enums.h index 0a594e95ad4..964eeb264f8 100644 --- a/ui/StatusQ/src/onboarding/enums.h +++ b/ui/StatusQ/src/onboarding/enums.h @@ -38,7 +38,6 @@ class OnboardingEnums: public QObject CreateProfileWithPassword, CreateProfileWithSeedphrase, - CreateProfileWithKeycard, CreateProfileWithKeycardNewSeedphrase, CreateProfileWithKeycardExistingSeedphrase, @@ -68,27 +67,21 @@ class OnboardingEnums: public QObject BlockedPUK, // PUK remaining attempts == 0 // exit states NotEmpty, - Empty + Empty, + Authorized }; - enum class AddKeyPairState { + enum class ProgressState { + Idle, InProgress, Success, Failed }; - enum class SyncState { - Idle, - InProgress, - Failed, - Success - }; - private: Q_ENUM(PrimaryFlow) Q_ENUM(SecondaryFlow) Q_ENUM(LoginMethod) Q_ENUM(KeycardState) - Q_ENUM(AddKeyPairState) - Q_ENUM(SyncState) + Q_ENUM(ProgressState) }; diff --git a/ui/app/AppLayouts/Onboarding2/KeycardCreateProfileFlow.qml b/ui/app/AppLayouts/Onboarding2/KeycardCreateProfileFlow.qml index 161e72513ce..c0499da2e2a 100644 --- a/ui/app/AppLayouts/Onboarding2/KeycardCreateProfileFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/KeycardCreateProfileFlow.qml @@ -13,26 +13,27 @@ SQUtils.QObject { required property StackView stackView required property int keycardState + required property int pinSettingState + required property int authorizationState required property int addKeyPairState required property int keycardPinInfoPageDelay - required property var seedWords + required property var getSeedWords required property var isSeedPhraseValid property bool displayKeycardPromoBanner signal loginWithKeycardRequested signal keycardFactoryResetRequested - signal keyPairTransferRequested + signal loadMnemonicRequested signal keycardPinCreated(string pin) signal seedphraseSubmitted(string seedphrase) signal keypairAddTryAgainRequested signal reloadKeycardRequested signal createProfileWithoutKeycardRequested + signal authorizationRequested - signal mnemonicWasShown() - signal mnemonicRemovalRequested() signal finished(bool withNewSeedphrase) function init() { @@ -43,6 +44,7 @@ SQUtils.QObject { id: d property bool withNewSeedphrase + property var seedWords function initialComponent() { if (root.keycardState === Onboarding.KeycardState.Empty) @@ -124,9 +126,17 @@ SQUtils.QObject { Component { id: backupSeedRevealPage BackupSeedphraseReveal { - seedWords: root.seedWords + Component.onCompleted: { + try { + const seedwords = root.getSeedWords() + d.seedWords = JSON.parse(seedwords) + root.seedphraseSubmitted(d.seedWords) + } catch (e) { + console.error('Failed to get seedwords', e) + } + } + seedWords: d.seedWords - onMnemonicWasShown: root.mnemonicWasShown() onBackupSeedphraseConfirmed: root.stackView.push(backupSeedVerifyPage) } } @@ -135,9 +145,9 @@ SQUtils.QObject { id: backupSeedVerifyPage BackupSeedphraseVerify { seedWordsToVerify: { - const randomIndexes = SQUtils.Utils.nSamples(4, root.seedWords.length) + const randomIndexes = SQUtils.Utils.nSamples(4, d.seedWords.length) return randomIndexes.map(i => ({ seedWordNumber: i+1, - seedWord: root.seedWords[i] + seedWord: d.seedWords[i] })) } @@ -150,8 +160,7 @@ SQUtils.QObject { BackupSeedphraseOutro { onBackupSeedphraseRemovalConfirmed: { - root.mnemonicRemovalRequested() - root.keyPairTransferRequested() + root.loadMnemonicRequested() root.stackView.push(addKeypairPage) } } @@ -161,13 +170,20 @@ SQUtils.QObject { id: seedphrasePage SeedphrasePage { + id: seedphrasePage title: qsTr("Create profile on empty Keycard using a recovery phrase") + authorizationState: root.authorizationState isSeedPhraseValid: root.isSeedPhraseValid onSeedphraseSubmitted: (seedphrase) => { root.seedphraseSubmitted(seedphrase) - root.keyPairTransferRequested() - root.stackView.push(addKeypairPage) + root.authorizationRequested() + } + onKeycardAuthorized: { + if (!d.withNewSeedphrase) { + root.loadMnemonicRequested() + root.stackView.push(addKeypairPage) + } } } } @@ -176,14 +192,26 @@ SQUtils.QObject { id: keycardCreatePinPage KeycardCreatePinPage { + id: createPinPage + + keycardPinInfoPageDelay: root.keycardPinInfoPageDelay + pinSettingState: root.pinSettingState + authorizationState: root.authorizationState onKeycardPinCreated: (pin) => { - Backpressure.debounce(root, root.keycardPinInfoPageDelay, () => { - root.keycardPinCreated(pin) - if (d.withNewSeedphrase) - root.stackView.push(backupSeedIntroPage) - else - root.stackView.push(seedphrasePage) - })() + root.keycardPinCreated(pin) + } + onKeycardPinSuccessfullySet: { + if (d.withNewSeedphrase) { + // Need to authorize before getting a seedphrase + root.authorizationRequested() + } else { + root.stackView.push(seedphrasePage) + } + } + onKeycardAuthorized: { + if (d.withNewSeedphrase) { + root.stackView.push(backupSeedIntroPage) + } } } } diff --git a/ui/app/AppLayouts/Onboarding2/KeycardCreateReplacementFlow.qml b/ui/app/AppLayouts/Onboarding2/KeycardCreateReplacementFlow.qml index 99109b16a25..a27fd8a4cb9 100644 --- a/ui/app/AppLayouts/Onboarding2/KeycardCreateReplacementFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/KeycardCreateReplacementFlow.qml @@ -14,6 +14,8 @@ SQUtils.QObject { required property int keycardState required property int addKeyPairState + required property int authorizationState + required property int pinSettingState required property int keycardPinInfoPageDelay required property var isSeedPhraseValid @@ -22,8 +24,8 @@ SQUtils.QObject { signal loginWithKeycardRequested signal keycardFactoryResetRequested - signal keyPairTransferRequested signal keycardPinCreated(string pin) + signal authorizationRequested signal seedphraseSubmitted(string seedphrase) signal keypairAddTryAgainRequested @@ -90,6 +92,7 @@ SQUtils.QObject { SeedphrasePage { title: qsTr("Enter recovery phrase of lost Keycard") + authorizationState: root.authorizationState isSeedPhraseValid: root.isSeedPhraseValid onSeedphraseSubmitted: (seedphrase) => { root.seedphraseSubmitted(seedphrase) @@ -102,12 +105,14 @@ SQUtils.QObject { id: keycardCreatePinPage KeycardCreatePinPage { + pinSettingState: root.pinSettingState + authorizationState: root.authorizationState onKeycardPinCreated: (pin) => { - Backpressure.debounce(root, root.keycardPinInfoPageDelay, () => { root.keycardPinCreated(pin) - root.keyPairTransferRequested() - root.stackView.push(addKeypairPage) - })() + root.authorizationRequested() + } + onKeycardAuthorized: { + root.stackView.push(addKeypairPage) } } } diff --git a/ui/app/AppLayouts/Onboarding2/LoginBySyncingFlow.qml b/ui/app/AppLayouts/Onboarding2/LoginBySyncingFlow.qml index c8067c1b181..9ffbf17b133 100644 --- a/ui/app/AppLayouts/Onboarding2/LoginBySyncingFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/LoginBySyncingFlow.qml @@ -39,7 +39,7 @@ SQUtils.QObject { SyncProgressPage { readonly property bool backAvailableHint: - root.syncState === Onboarding.SyncState.Failed + root.syncState === Onboarding.ProgressState.Failed syncState: root.syncState diff --git a/ui/app/AppLayouts/Onboarding2/LoginWithKeycardFlow.qml b/ui/app/AppLayouts/Onboarding2/LoginWithKeycardFlow.qml index 5814659be88..8954529798c 100644 --- a/ui/app/AppLayouts/Onboarding2/LoginWithKeycardFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/LoginWithKeycardFlow.qml @@ -13,7 +13,8 @@ SQUtils.QObject { required property StackView stackView required property int keycardState - required property var tryToSetPinFunction + required property int authorizationState + required property int restoreKeysExportState required property int remainingPinAttempts required property int remainingPukAttempts required property var isSeedPhraseValid @@ -22,13 +23,14 @@ SQUtils.QObject { property bool displayKeycardPromoBanner - signal keycardPinEntered(string pin) signal keycardPinCreated(string pin) signal seedphraseSubmitted(string seedphrase) + signal authorizationRequested(string pin) signal reloadKeycardRequested signal keycardFactoryResetRequested signal unblockWithPukRequested signal createProfileWithEmptyKeycardRequested + signal exportKeysRequested signal finished function init() { @@ -88,16 +90,15 @@ SQUtils.QObject { id: keycardEnterPinPage KeycardEnterPinPage { - tryToSetPinFunction: root.tryToSetPinFunction + authorizationState: root.authorizationState + restoreKeysExportState: root.restoreKeysExportState + onAuthorizationRequested: root.authorizationRequested(pin) remainingAttempts: root.remainingPinAttempts unblockWithPukAvailable: root.remainingPukAttempts > 0 + keycardPinInfoPageDelay: root.keycardPinInfoPageDelay - onKeycardPinEntered: (pin) => { - Backpressure.debounce(root, root.keycardPinInfoPageDelay, () => { - root.keycardPinEntered(pin) - root.finished() - })() - } + onExportKeysRequested: root.exportKeysRequested() + onExportKeysDone: root.finished() onReloadKeycardRequested: d.reload() onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() @@ -111,6 +112,7 @@ SQUtils.QObject { SeedphrasePage { title: qsTr("Unblock Keycard using the recovery phrase") btnContinueText: qsTr("Unblock Keycard") + authorizationState: root.authorizationState isSeedPhraseValid: root.isSeedPhraseValid onSeedphraseSubmitted: (seedphrase) => { root.seedphraseSubmitted(seedphrase) @@ -123,7 +125,10 @@ SQUtils.QObject { id: keycardCreatePinPage KeycardCreatePinPage { + id: createPinPage + onKeycardPinCreated: (pin) => { + createPinPage.loading = true Backpressure.debounce(root, root.keycardPinInfoPageDelay, () => { root.keycardPinCreated(pin) root.finished() diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml index 3e725c786ef..d04f23211d9 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml @@ -13,9 +13,12 @@ SQUtils.QObject { required property StackView stackView required property int keycardState + required property int pinSettingState + required property int authorizationState + required property int restoreKeysExportState required property int addKeyPairState required property int syncState - required property var seedWords + required property var getSeedWords required property int remainingPinAttempts required property int remainingPukAttempts @@ -33,7 +36,6 @@ SQUtils.QObject { required property var tryToSetPukFunction signal keycardPinCreated(string pin) - signal keycardPinEntered(string pin) signal enableBiometricsRequested(bool enable) signal shareUsageDataRequested(bool enabled) signal syncProceedWithConnectionString(string connectionString) @@ -41,10 +43,9 @@ SQUtils.QObject { signal setPasswordRequested(string password) signal reloadKeycardRequested signal keycardFactoryResetRequested - signal keyPairTransferRequested - - signal mnemonicWasShown() - signal mnemonicRemovalRequested() + signal exportKeysRequested + signal loadMnemonicRequested + signal authorizationRequested(string pin) signal linkActivated(string link) @@ -193,8 +194,10 @@ SQUtils.QObject { stackView: root.stackView keycardState: root.keycardState + pinSettingState: root.pinSettingState + authorizationState: root.authorizationState addKeyPairState: root.addKeyPairState - seedWords: root.seedWords + getSeedWords: root.getSeedWords displayKeycardPromoBanner: root.displayKeycardPromoBanner isSeedPhraseValid: root.isSeedPhraseValid @@ -202,10 +205,11 @@ SQUtils.QObject { onReloadKeycardRequested: root.reloadKeycardRequested() onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() - onKeyPairTransferRequested: root.keyPairTransferRequested() + onLoadMnemonicRequested: root.loadMnemonicRequested() onKeycardPinCreated: (pin) => root.keycardPinCreated(pin) onLoginWithKeycardRequested: loginWithKeycardFlow.init() - onKeypairAddTryAgainRequested: root.keyPairTransferRequested() // FIXME? + // onKeypairAddTryAgainRequested: root.keyPairTransferRequested() // FIXME? + onAuthorizationRequested: root.authorizationRequested("") // Pin was saved locally already onCreateProfileWithoutKeycardRequested: { const page = stackView.find( @@ -214,9 +218,6 @@ SQUtils.QObject { stackView.replace(page, createProfilePage, StackView.PopTransition) } - onMnemonicWasShown: root.mnemonicWasShown() - onMnemonicRemovalRequested: root.mnemonicRemovalRequested() - onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase) onFinished: (withNewSeedphrase) => { @@ -254,20 +255,22 @@ SQUtils.QObject { stackView: root.stackView keycardState: root.keycardState + authorizationState: root.authorizationState + restoreKeysExportState: root.restoreKeysExportState remainingPinAttempts: root.remainingPinAttempts remainingPukAttempts: root.remainingPukAttempts displayKeycardPromoBanner: root.displayKeycardPromoBanner - tryToSetPinFunction: root.tryToSetPinFunction + onAuthorizationRequested: root.authorizationRequested(pin) isSeedPhraseValid: root.isSeedPhraseValid keycardPinInfoPageDelay: root.keycardPinInfoPageDelay - onKeycardPinEntered: (pin) => root.keycardPinEntered(pin) onKeycardPinCreated: (pin) => root.keycardPinCreated(pin) onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase) onReloadKeycardRequested: root.reloadKeycardRequested() onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init() onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() + onExportKeysRequested: root.exportKeysRequested() onUnblockWithPukRequested: unblockWithPukFlow.init() onFinished: { @@ -302,6 +305,8 @@ SQUtils.QObject { stackView: root.stackView keycardState: root.keycardState + pinSettingState: root.pinSettingState + authorizationState: root.authorizationState addKeyPairState: root.addKeyPairState displayKeycardPromoBanner: root.displayKeycardPromoBanner @@ -311,10 +316,10 @@ SQUtils.QObject { onReloadKeycardRequested: root.reloadKeycardRequested() onKeycardFactoryResetRequested: root.keycardFactoryResetRequested() - onKeyPairTransferRequested: root.keyPairTransferRequested() onKeycardPinCreated: (pin) => root.keycardPinCreated(pin) onLoginWithKeycardRequested: loginWithKeycardFlow.init() - onKeypairAddTryAgainRequested: root.keyPairTransferRequested() // FIXME? + onAuthorizationRequested: root.authorizationRequested("") // Pin was saved locally already + // onKeypairAddTryAgainRequested: root.keyPairTransferRequested() // FIXME? onCreateProfileWithoutKeycardRequested: { const page = stackView.find( diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml index e4dc3c0918d..e003ebd6f72 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml @@ -97,6 +97,20 @@ Page { root.finished(flow, data) } + + function loadMnemonic() { + root.onboardingStore.loadMnemonic(d.seedphrase) + } + + function authorize(pin) { + if (!pin && !d.keycardPin) { + return + } + if (!pin) { + pin = d.keycardPin + } + root.onboardingStore.authorize(pin) + } } background: Rectangle { @@ -144,10 +158,15 @@ Page { stackView: stack keycardState: root.onboardingStore.keycardState + pinSettingState: root.onboardingStore.pinSettingState + authorizationState: root.onboardingStore.authorizationState + restoreKeysExportState: root.onboardingStore.restoreKeysExportState syncState: root.onboardingStore.syncState addKeyPairState: root.onboardingStore.addKeyPairState - seedWords: root.onboardingStore.getMnemonic().split(" ") + getSeedWords: function () { + return root.onboardingStore.getMnemonic().split(" ") + } displayKeycardPromoBanner: !d.settings.keycardPromoShown biometricsAvailable: root.biometricsAvailable @@ -166,16 +185,10 @@ Page { root.onboardingStore.setPin(pin) } - onKeycardPinEntered: (pin) => { - d.keycardPin = pin - root.onboardingStore.setPin(pin) - } - - onKeyPairTransferRequested: root.onboardingStore.startKeypairTransfer() + onLoadMnemonicRequested: d.loadMnemonic() + onAuthorizationRequested: d.authorize(pin) onShareUsageDataRequested: (enabled) => root.shareUsageDataRequested(enabled) onReloadKeycardRequested: root.reloadKeycardRequested() - onMnemonicWasShown: root.onboardingStore.mnemonicWasShown() - onMnemonicRemovalRequested: root.onboardingStore.removeMnemonic() onSyncProceedWithConnectionString: (connectionString) => root.onboardingStore.inputConnectionStringForBootstrapping(connectionString) @@ -183,6 +196,7 @@ Page { onSetPasswordRequested: (password) => d.password = password onEnableBiometricsRequested: (enabled) => d.enableBiometrics = enabled onLinkActivated: (link) => Qt.openUrlExternally(link) + onExportKeysRequested: root.onboardingStore.exportRecoverKeys() onFinished: (flow) => d.finishFlow(flow) onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested") } diff --git a/ui/app/AppLayouts/Onboarding2/UseRecoveryPhraseFlow.qml b/ui/app/AppLayouts/Onboarding2/UseRecoveryPhraseFlow.qml index bbbb465a568..a1845d26bd2 100644 --- a/ui/app/AppLayouts/Onboarding2/UseRecoveryPhraseFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/UseRecoveryPhraseFlow.qml @@ -4,6 +4,7 @@ import QtQuick.Controls 2.15 import StatusQ.Core.Utils 0.1 as SQUtils import AppLayouts.Onboarding2.pages 1.0 +import AppLayouts.Onboarding.enums 1.0 SQUtils.QObject { id: root @@ -41,6 +42,7 @@ SQUtils.QObject { SeedphrasePage { isSeedPhraseValid: root.isSeedPhraseValid + authorizationState: Onboarding.ProgressState.Idle onSeedphraseSubmitted: (seedphrase) => { root.seedphraseSubmitted(seedphrase) diff --git a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml index 175ffbeac20..6f43f46fdac 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/BackupSeedphraseReveal.qml @@ -15,7 +15,6 @@ OnboardingPage { required property var seedWords - signal mnemonicWasShown() signal backupSeedphraseConfirmed() QtObject { @@ -103,7 +102,6 @@ OnboardingPage { visible: !d.seedphraseRevealed onClicked: { d.seedphraseRevealed = true - root.mnemonicWasShown() } } } diff --git a/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml b/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml index dc900da57b8..e2118cca4dd 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/CreateKeycardProfilePage.qml @@ -77,7 +77,7 @@ OnboardingPage { StatusButton { objectName: "btnCreateWithEmptySeedphrase" Layout.fillWidth: true - text: qsTr("Let’s go!") + text: qsTr("Let's go!") font.pixelSize: Theme.additionalTextSize onClicked: root.createKeycardProfileWithNewSeedphrase() } diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardAddKeyPairPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardAddKeyPairPage.qml index 83a9a8269b1..8d354aa257d 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/KeycardAddKeyPairPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardAddKeyPairPage.qml @@ -13,7 +13,7 @@ import AppLayouts.Onboarding.enums 1.0 OnboardingPage { id: root - required property int addKeyPairState // Onboarding.AddKeyPairState.xxx + required property int addKeyPairState // Onboarding.ProgressState.xxx signal keypairAddContinueRequested() signal keypairAddTryAgainRequested() @@ -23,7 +23,7 @@ OnboardingPage { states: [ State { name: "inprogress" - when: root.addKeyPairState === Onboarding.AddKeyPairState.InProgress + when: root.addKeyPairState === Onboarding.ProgressState.InProgress PropertyChanges { target: root title: qsTr("Adding key pair to Keycard") @@ -44,7 +44,7 @@ OnboardingPage { }, State { name: "success" - when: root.addKeyPairState === Onboarding.AddKeyPairState.Success + when: root.addKeyPairState === Onboarding.ProgressState.Success PropertyChanges { target: root title: qsTr("Key pair added to Keycard") @@ -68,7 +68,7 @@ OnboardingPage { }, State { name: "failed" - when: root.addKeyPairState === Onboarding.AddKeyPairState.Failed + when: root.addKeyPairState === Onboarding.ProgressState.Failed PropertyChanges { target: root title: "".arg(Theme.palette.dangerColor1) + qsTr("Failed to add key pair to Keycard") + "" diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml index 028f518e73a..60433dcd483 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardCreatePinPage.qml @@ -7,15 +7,23 @@ import StatusQ.Components 0.1 import StatusQ.Controls 0.1 import StatusQ.Controls.Validators 0.1 import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Backpressure 0.1 import AppLayouts.Onboarding2.controls 1.0 +import AppLayouts.Onboarding.enums 1.0 import utils 1.0 KeycardBasePage { id: root + property int keycardPinInfoPageDelay + required property int pinSettingState + required property int authorizationState + signal keycardPinCreated(string pin) + signal keycardPinSuccessfullySet() + signal keycardAuthorized() image.source: Theme.png("onboarding/keycard/reading") @@ -53,10 +61,16 @@ KeycardBasePage { StatusBaseText { id: errorText anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("PINs don’t match") + text: qsTr("PINs don't match") font.pixelSize: Theme.tertiaryTextFontSize color: Theme.palette.dangerColor1 visible: false + }, + StatusLoadingIndicator { + id: loadingIndicator + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: Theme.halfPadding + visible: false } ] @@ -83,26 +97,89 @@ KeycardBasePage { image.source: Theme.png("onboarding/keycard/error") } }, + State { + name: "error" + when: root.pinSettingState === Onboarding.ProgressState.Failed || root.authorizationState === Onboarding.ProgressState.Failed + PropertyChanges { + target: errorText + visible: true + text: qsTr("Error setting pin") + } + PropertyChanges { + target: root + image.source: Theme.png("onboarding/keycard/error") + } + }, + State { + name: "authorized" + when: root.authorizationState === Onboarding.ProgressState.Success + PropertyChanges { + target: root + title: qsTr("PIN set") + } + PropertyChanges { + target: pinInput + enabled: false + } + PropertyChanges { + target: root + image.source: Theme.png("onboarding/keycard/success") + } + StateChangeScript { + script: { + Backpressure.debounce(root, keycardPinInfoPageDelay, function() { + root.keycardAuthorized() + })() + } + } + }, State { name: "success" + when: root.pinSettingState === Onboarding.ProgressState.Success + PropertyChanges { + target: root + title: qsTr("PIN set") + } + PropertyChanges { + target: pinInput + enabled: false + } + PropertyChanges { + target: root + image.source: Theme.png("onboarding/keycard/success") + } + StateChangeScript { + script: { + root.keycardPinSuccessfullySet() + } + } + }, + State { + name: "settingPin" extend: "repeating" - when: !!d.pin && !!d.pin2 && d.pin === d.pin2 + when: !!d.pin && !!d.pin2 && d.pin === d.pin2 && (root.pinSettingState === Onboarding.ProgressState.Idle || root.pinSettingState === Onboarding.ProgressState.InProgress) PropertyChanges { target: root - title: qsTr("Keycard PIN set") + title: qsTr("Setting Keycard PIN") } PropertyChanges { target: pinInput enabled: false } + PropertyChanges { + target: loadingIndicator + visible: true + } PropertyChanges { target: root image.source: Theme.png("onboarding/keycard/success") } StateChangeScript { script: { - pinInput.setPin(d.pin) - root.keycardPinCreated(d.pin) + Backpressure.debounce(root, keycardPinInfoPageDelay, function() { + pinInput.setPin(d.pin) + root.keycardPinCreated(d.pin) + })() } } }, diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml index c84b4adedb3..7980815679b 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml @@ -9,28 +9,33 @@ import StatusQ.Core.Theme 0.1 import StatusQ.Core.Backpressure 0.1 import AppLayouts.Onboarding2.controls 1.0 +import AppLayouts.Onboarding.enums 1.0 import utils 1.0 KeycardBasePage { id: root - property var tryToSetPinFunction: (pin) => { console.error("tryToSetPinFunction: IMPLEMENT ME"); return false } + required property int authorizationState + required property int restoreKeysExportState required property int remainingAttempts property bool unblockWithPukAvailable + property int keycardPinInfoPageDelay signal keycardPinEntered(string pin) signal reloadKeycardRequested signal unblockWithSeedphraseRequested signal unblockWithPukRequested signal keycardFactoryResetRequested + signal authorizationRequested(string pin) + signal exportKeysRequested() + signal exportKeysDone() image.source: Theme.png("onboarding/keycard/reading") QtObject { id: d property string tempPin - property bool pinValid } buttons: [ @@ -42,10 +47,7 @@ KeycardBasePage { onPinInputChanged: { if (pinInput.pinInput.length === pinInput.pinLen) { // we have the full length PIN now d.tempPin = pinInput.pinInput - d.pinValid = root.tryToSetPinFunction(d.tempPin) - if (!d.pinValid) { - pinInput.statesInitialization() - } + root.authorizationRequested(d.tempPin) } } }, @@ -57,6 +59,20 @@ KeycardBasePage { color: Theme.palette.dangerColor1 visible: false }, + StatusBaseText { + id: errorExportingText + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Error exporting the keys, please try again") + font.pixelSize: Theme.tertiaryTextFontSize + color: Theme.palette.dangerColor1 + visible: false + }, + StatusLoadingIndicator { + id: loadingIndicator + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: Theme.halfPadding + visible: false + }, MaybeOutlineButton { id: btnUnblockWithPuk visible: false @@ -125,7 +141,7 @@ KeycardBasePage { }, State { name: "incorrect" - when: !!d.tempPin && !d.pinValid + when: root.authorizationState === Onboarding.ProgressState.Failed PropertyChanges { target: root title: qsTr("PIN incorrect") @@ -134,20 +150,82 @@ KeycardBasePage { target: errorText visible: true } + StateChangeScript { + script: { + Backpressure.debounce(root, 100, function() { + pinInput.clearPin() + })() + } + } + }, + State { + name: "error" + when: root.restoreKeysExportState === Onboarding.ProgressState.Failed + PropertyChanges { + target: root + title: qsTr("Keys export failed") + } + PropertyChanges { + target: errorExportingText + visible: true + } + }, + State { + name: "authorizing" + when: root.authorizationState === Onboarding.ProgressState.InProgress + PropertyChanges { + target: root + title: qsTr("Authorizing") + } + PropertyChanges { + target: pinInput + enabled: false + } + PropertyChanges { + target: loadingIndicator + visible: true + } + }, + State { + name: "exportSuccess" + when: root.restoreKeysExportState === Onboarding.ProgressState.Success + PropertyChanges { + target: root + title: qsTr("Keys exported successfully") + } + PropertyChanges { + target: pinInput + enabled: false + } + StateChangeScript { + script: { + Backpressure.debounce(root, keycardPinInfoPageDelay, function() { + root.exportKeysDone() + })() + } + } }, State { - name: "success" - when: d.pinValid + name: "pinSuccess" + when: root.authorizationState === Onboarding.ProgressState.Success PropertyChanges { target: root - title: qsTr("PIN correct") + title: qsTr("PIN correct. Exporting keys.") } PropertyChanges { target: pinInput enabled: false } + PropertyChanges { + target: loadingIndicator + visible: true + } StateChangeScript { - script: root.keycardPinEntered(pinInput.pinInput) + script: { + Backpressure.debounce(root, keycardPinInfoPageDelay, function() { + root.exportKeysRequested() + })() + } } }, State { @@ -161,7 +239,6 @@ KeycardBasePage { pinInput.statesInitialization() pinInput.forceFocus() d.tempPin = "" - d.pinValid = false } } } diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml index c155b228c8a..5d853d47ec4 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardIntroPage.qml @@ -108,7 +108,7 @@ KeycardBasePage { MaybeOutlineButton { id: btnReload visible: false - text: qsTr("I’ve inserted a different Keycard") + text: qsTr("I've inserted a different Keycard") anchors.horizontalCenter: parent.horizontalCenter onClicked: root.reloadKeycardRequested() } diff --git a/ui/app/AppLayouts/Onboarding2/pages/KeycardLostPage.qml b/ui/app/AppLayouts/Onboarding2/pages/KeycardLostPage.qml index d947774c963..28da23881af 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/KeycardLostPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/KeycardLostPage.qml @@ -12,7 +12,7 @@ KeycardBasePage { signal useProfileWithoutKeycardRequested() title: qsTr("Lost Keycard") - subtitle: qsTr("Sorry you’ve lost your Keycard") + subtitle: qsTr("Sorry you've lost your Keycard") image.source: Theme.png("onboarding/keycard/empty") buttons: [ diff --git a/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml b/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml index 507be21f784..2512ec4f1fd 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/SeedphrasePage.qml @@ -7,6 +7,8 @@ import StatusQ.Components 0.1 import StatusQ.Controls 0.1 import StatusQ.Core.Theme 0.1 +import AppLayouts.Onboarding.enums 1.0 + import shared.panels 1.0 OnboardingPage { @@ -16,9 +18,12 @@ OnboardingPage { property string subtitle: qsTr("Enter your 12, 18 or 24 word recovery phrase") property alias btnContinueText: btnContinue.text + required property int authorizationState + property var isSeedPhraseValid: (mnemonic) => { console.error("isSeedPhraseValid IMPLEMENT ME"); return false } signal seedphraseSubmitted(string seedphrase) + signal keycardAuthorized() contentItem: Item { ColumnLayout { @@ -62,4 +67,30 @@ OnboardingPage { } } } + + state: "creating" + + states: [ + State { + name: "creating" + }, + State { + name: "authorized" + when: root.authorizationState === Onboarding.ProgressState.Success + StateChangeScript { + script: { + root.keycardAuthorized() + } + } + }, + State { + name: "loadingMnemonic" + when: root.authorizationState === Onboarding.ProgressState.InProgress + + PropertyChanges { + target: btnContinue + loading: true + } + } + ] } diff --git a/ui/app/AppLayouts/Onboarding2/pages/SyncProgressPage.qml b/ui/app/AppLayouts/Onboarding2/pages/SyncProgressPage.qml index 529742b98b1..c937b9b754a 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/SyncProgressPage.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/SyncProgressPage.qml @@ -12,7 +12,7 @@ import AppLayouts.Onboarding.enums 1.0 OnboardingPage { id: root - required property int syncState // Onboarding.SyncState.xxx + required property int syncState // Onboarding.ProgressState.xxx signal loginToAppRequested() signal restartSyncRequested() @@ -21,7 +21,7 @@ OnboardingPage { states: [ State { name: "inprogress" - when: root.syncState === Onboarding.SyncState.InProgress || root.syncState === Onboarding.SyncState.Idle + when: root.syncState === Onboarding.ProgressState.InProgress || root.syncState === Onboarding.ProgressState.Idle PropertyChanges { target: root title: qsTr("Profile sync in progress...") @@ -46,7 +46,7 @@ OnboardingPage { }, State { name: "success" - when: root.syncState === Onboarding.SyncState.Success + when: root.syncState === Onboarding.ProgressState.Success PropertyChanges { target: root title: qsTr("Profile synced") @@ -70,7 +70,7 @@ OnboardingPage { }, State { name: "failed" - when: root.syncState === Onboarding.SyncState.Failed + when: root.syncState === Onboarding.ProgressState.Failed PropertyChanges { target: root title: "".arg(Theme.palette.dangerColor1) + qsTr("Profile syncing failed") + "" diff --git a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml index 5af022324c5..a49439f3eb1 100644 --- a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml +++ b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml @@ -7,22 +7,25 @@ import AppLayouts.Onboarding.enums 1.0 QtObject { id: root - signal appLoaded() - + signal appLoaded readonly property QtObject d: StatusQUtils.QObject { id: d readonly property var onboardingModuleInst: onboardingModule Component.onCompleted: { - onboardingModuleInst.appLoaded.connect(root.appLoaded) - onboardingModuleInst.accountLoginError.connect(root.accountLoginError) - onboardingModuleInst.obtainingPasswordSuccess.connect(root.obtainingPasswordSuccess) - onboardingModuleInst.obtainingPasswordError.connect(root.obtainingPasswordError) + d.onboardingModuleInst.appLoaded.connect(root.appLoaded) + // TODO implement the following signals + // d.onboardingModuleInst.accountLoginError.connect(root.accountLoginError) + // d.onboardingModuleInst.obtainingPasswordSuccess.connect(root.obtainingPasswordSuccess) + // d.onboardingModuleInst.obtainingPasswordError.connect(root.obtainingPasswordError) } } // keycard readonly property int keycardState: d.onboardingModuleInst.keycardState // cf. enum Onboarding.KeycardState + readonly property int pinSettingState: d.onboardingModuleInst.pinSettingState // cf. enum Onboarding.ProgressState + readonly property int authorizationState: d.onboardingModuleInst.authorizationState // cf. enum Onboarding.ProgressState + readonly property int restoreKeysExportState: d.onboardingModuleInst.restoreKeysExportState // cf. enum Onboarding.ProgressState readonly property int keycardRemainingPinAttempts: d.onboardingModuleInst.keycardRemainingPinAttempts readonly property int keycardRemainingPukAttempts: d.onboardingModuleInst.keycardRemainingPukAttempts @@ -30,17 +33,24 @@ QtObject { return d.onboardingModuleInst.finishOnboardingFlow(flow, JSON.stringify(data)) } - function setPin(pin: string) { // -> bool - return d.onboardingModuleInst.setPin(pin) + function setPin(pin: string) { + d.onboardingModuleInst.setPin(pin) } function setPuk(puk: string) { // -> bool return d.onboardingModuleInst.setPuk(puk) } - readonly property int addKeyPairState: d.onboardingModuleInst.addKeyPairState // cf. enum Onboarding.AddKeyPairState - function startKeypairTransfer() { // -> void - d.onboardingModuleInst.startKeypairTransfer() + function authorize(pin: string) { + d.onboardingModuleInst.authorize(pin) + } + + readonly property int addKeyPairState: d.onboardingModuleInst.addKeyPairState // cf. enum Onboarding.ProgressState + function loadMnemonic(mnemonic) { // -> void + d.onboardingModuleInst.loadMnemonic(mnemonic) + } + function exportRecoverKeys() { // -> void + d.onboardingModuleInst.exportRecoverKeys() } // password @@ -59,17 +69,11 @@ QtObject { return d.onboardingModuleInst.validMnemonic(mnemonic) } function getMnemonic() { // -> string - return d.onboardingModuleInst.mnemonic() - } - function mnemonicWasShown() { // -> void - d.onboardingModuleInst.mnemonicWasShown() - } - function removeMnemonic() { // -> void - d.onboardingModuleInst.removeMnemonic() + return d.onboardingModuleInst.getMnemonic() } // sync - readonly property int syncState: d.onboardingModuleInst.syncState // cf. enum Onboarding.SyncState + readonly property int syncState: d.onboardingModuleInst.syncState // cf. enum Onboarding.ProgressState function validateLocalPairingConnectionString(connectionString: string) { // -> bool return d.onboardingModuleInst.validateLocalPairingConnectionString(connectionString) } diff --git a/vendor/nim-keycard-go b/vendor/nim-keycard-go index aa05fe490b5..d9f628f7d59 160000 --- a/vendor/nim-keycard-go +++ b/vendor/nim-keycard-go @@ -1 +1 @@ -Subproject commit aa05fe490b57023028e242567f52bd124ed87d30 +Subproject commit d9f628f7d59269a29c223efd95542683696959dd diff --git a/vendor/status-keycard-go b/vendor/status-keycard-go index 8122bdc0f30..bcf094518e5 160000 --- a/vendor/status-keycard-go +++ b/vendor/status-keycard-go @@ -1 +1 @@ -Subproject commit 8122bdc0f30c4957a0f72fb423c6972d3c2e8ed5 +Subproject commit bcf094518e5d3a8144b831669d833d70e7aed112