diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index ce7cf52140d..8cd470bb977 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -148,12 +148,9 @@ proc connect(self: AppController) = elif defined(production): setLogLevel(chronicles.LogLevel.INFO) -# TODO remove these functions once we have only the new onboarding module -proc shouldStartWithOnboardingScreen(self: AppController): bool = - return self.accountsService.openedAccounts().len == 0 +# TODO remove this function once we have only the new onboarding module proc shouldUseTheNewOnboardingModule(self: AppController): bool = - # Only the onboarding for new users is implemented in the new module for now - return singletonInstance.featureFlags().getOnboardingV2Enabled() and self.shouldStartWithOnboardingScreen() + return singletonInstance.featureFlags().getOnboardingV2Enabled() proc newAppController*(statusFoundation: StatusFoundation): AppController = result = AppController() diff --git a/src/app/modules/onboarding/controller.nim b/src/app/modules/onboarding/controller.nim index 5f1c438fb1f..dcd43d37a78 100644 --- a/src/app/modules/onboarding/controller.nim +++ b/src/app/modules/onboarding/controller.nim @@ -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 +import app_service/common/utils from app_service/service/keycardV2/dto import KeycardExportedKeysDto logScope: @@ -86,14 +87,29 @@ proc init*(self: Controller) = self.delegate.onKeycardLoadMnemonicSuccess(args.keyUID) self.connectionIds.add(handlerId) - handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE) do(e: Args): + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE) do(e: Args): let args = KeycardErrorArg(e) - self.delegate.onKeycardExportKeysFailure(args.error) + self.delegate.onKeycardExportRestoreKeysFailure(args.error) self.connectionIds.add(handlerId) - handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS) do(e: Args): + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_SUCCESS) do(e: Args): let args = KeycardExportedKeysArg(e) - self.delegate.onKeycardExportKeysSuccess(args.exportedKeys) + self.delegate.onKeycardExportRestoreKeysSuccess(args.exportedKeys) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE) do(e: Args): + let args = KeycardErrorArg(e) + self.delegate.onKeycardExportLoginKeysFailure(args.error) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_SUCCESS) do(e: Args): + let args = KeycardExportedKeysArg(e) + self.delegate.onKeycardExportLoginKeysSuccess(args.exportedKeys) + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_LOGIN_ERROR) do(e: Args): + let args = LoginErrorArgs(e) + self.delegate.onAccountLoginError(args.error) self.connectionIds.add(handlerId) proc initialize*(self: Controller, pin: string) = @@ -174,3 +190,40 @@ proc generateMnemonic*(self: Controller, length: int): string = proc exportRecoverKeysFromKeycard*(self: Controller) = self.keycardServiceV2.asyncExportRecoverKeys() + +proc exportLoginKeysFromKeycard*(self: Controller) = + self.keycardServiceV2.asyncExportLoginKeys() + +proc getOpenedAccounts*(self: Controller): seq[AccountDto] = + return self.accountsService.openedAccounts() + +proc getAccountByKeyUid*(self: Controller, keyUid: string): AccountDto = + return self.accountsService.getAccountByKeyUid(keyUid) + +proc login*( + self: Controller, + account: AccountDto, + password: string, + keycard: bool = false, + publicEncryptionKey: string = "", + privateWhisperKey: string = "", + mnemonic: string = "", + keycardReplacement: bool = false, + ) = + var passwordHash, chatPrivateKey = "" + + if not keycard: + passwordHash = hashPassword(password) + else: + passwordHash = publicEncryptionKey + chatPrivateKey = privateWhisperKey + + # if keycard and keycardReplacement: + # self.delegate.applyKeycardReplacementAfterLogin() + + self.accountsService.login( + account, + passwordHash, + chatPrivateKey, + mnemonic, + ) diff --git a/src/app/modules/onboarding/io_interface.nim b/src/app/modules/onboarding/io_interface.nim index db0d8fae1c3..a928ca6c239 100644 --- a/src/app/modules/onboarding/io_interface.nim +++ b/src/app/modules/onboarding/io_interface.nim @@ -45,6 +45,9 @@ method loadMnemonic*(self: AccessInterface, dataJson: string) {.base.} = method finishOnboardingFlow*(self: AccessInterface, flowInt: int, dataJson: string): string {.base.} = raise newException(ValueError, "No implementation available") +method loginRequested*(self: AccessInterface, keyUid: string, loginFlow: int, dataJson: string) {.base.} = + raise newException(ValueError, "No implementation available") + method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} = raise newException(ValueError, "No implementation available") @@ -63,10 +66,19 @@ method onKeycardLoadMnemonicFailure*(self: AccessInterface, error: string) {.bas method onKeycardLoadMnemonicSuccess*(self: AccessInterface, keyUID: string) {.base.} = raise newException(ValueError, "No implementation available") -method onKeycardExportKeysFailure*(self: AccessInterface, error: string) {.base.} = +method onKeycardExportRestoreKeysFailure*(self: AccessInterface, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardExportRestoreKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardExportLoginKeysFailure*(self: AccessInterface, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onKeycardExportLoginKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} = raise newException(ValueError, "No implementation available") -method onKeycardExportKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} = +method onAccountLoginError*(self: AccessInterface, error: string) {.base.} = raise newException(ValueError, "No implementation available") method exportRecoverKeys*(self: AccessInterface) {.base.} = diff --git a/src/app/modules/onboarding/module.nim b/src/app/modules/onboarding/module.nim index bed5752df06..ed3a1e936ec 100644 --- a/src/app/modules/onboarding/module.nim +++ b/src/app/modules/onboarding/module.nim @@ -1,4 +1,4 @@ -import NimQml, chronicles, json +import NimQml, chronicles, json, strutils import logging import io_interface @@ -14,12 +14,14 @@ 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 +import ../startup/models/login_account_item as login_acc_item + export io_interface logScope: topics = "onboarding-module" -type SecondaryFlow* {.pure} = enum +type OnboardingFlow* {.pure} = enum Unknown = 0, CreateProfileWithPassword, CreateProfileWithSeedphrase, @@ -28,13 +30,17 @@ type SecondaryFlow* {.pure} = enum LoginWithSeedphrase, LoginWithSyncing, LoginWithKeycard, - ActualLogin, # TODO get the real name and value for this when it's implemented on the front-end + +type LoginMethod* {.pure} = enum + Unknown = 0, + Password, + Keycard, type ProgressState* {.pure.} = enum Idle, InProgress, Success, - Failed + Failed, type Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface @@ -43,7 +49,8 @@ type viewVariant: QVariant controller: Controller localPairingStatus: LocalPairingStatus - currentFlow: SecondaryFlow + loginFlow: LoginMethod + onboardingFlow: OnboardingFlow exportedKeys: KeycardExportedKeysDto proc newModule*[T]( @@ -58,6 +65,8 @@ proc newModule*[T]( result.delegate = delegate result.view = view.newView(result) result.viewVariant = newQVariant(result.view) + result.onboardingFlow = OnboardingFlow.Unknown + result.loginFlow = LoginMethod.Unknown result.controller = controller.newController( result, events, @@ -87,6 +96,20 @@ method onAppLoaded*[T](self: Module[T]) = method load*[T](self: Module[T]) = singletonInstance.engine.setRootContextProperty("onboardingModule", self.viewVariant) self.controller.init() + + let openedAccounts = self.controller.getOpenedAccounts() + if openedAccounts.len > 0: + var items: seq[login_acc_item.Item] + for i in 0.. 0: + return self.accounts + try: let response = status_account.openedAccounts(main_constants.STATUSGODIR) @@ -136,6 +139,11 @@ QtObject: proc openedAccountsContainsKeyUid*(self: Service, keyUid: string): bool = return (keyUID in self.openedAccounts().mapIt(it.keyUid)) + proc getAccountByKeyUid*(self: Service, keyUid: string): AccountDto = + for account in self.openedAccounts(): + if account.keyUid == keyUid: + return account + # FIXME: remove this method, settings should be processed in status-go # https://github.com/status-im/status-go/issues/5359 proc addKeycardDetails(self: Service, kcInstance: string, settingsJson: var JsonNode, accountData: var JsonNode) = diff --git a/src/app_service/service/keycardV2/async_tasks.nim b/src/app_service/service/keycardV2/async_tasks.nim index 2f184224b40..d8d35eb5606 100644 --- a/src/app_service/service/keycardV2/async_tasks.nim +++ b/src/app_service/service/keycardV2/async_tasks.nim @@ -69,3 +69,20 @@ proc asyncExportRecoverKeysTask(argEncoded: string) {.gcsafe, nimcall.} = arg.finish(%* { "error": e.msg, }) + +type + AsyncExportLoginKeysArg = ref object of QObjectTaskArg + rpcCounter: int + +proc asyncExportLoginKeysTask(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncExportLoginKeysArg](argEncoded) + try: + let response = callRPC(arg.rpcCounter, "ExportLoginKeys") + arg.finish(%*{ + "response": response, + "error": "" + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) diff --git a/src/app_service/service/keycardV2/service.nim b/src/app_service/service/keycardV2/service.nim index 4cbf35c9f1c..cc2603bc32c 100644 --- a/src/app_service/service/keycardV2/service.nim +++ b/src/app_service/service/keycardV2/service.nim @@ -31,8 +31,10 @@ 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" +const SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE* = "keycardExportRestoreKeysFailure" +const SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_SUCCESS* = "keycardExportRestoreKeysSuccess" +const SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE* = "keycardExportLoginKeysFailure" +const SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_SUCCESS* = "keycardExportLoginKeysSuccess" type KeycardEventArg* = ref object of Args @@ -157,10 +159,9 @@ QtObject: 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) + raise newException(RpcException, rpcResponseObj["error"].getStr) except Exception as e: - error "error set pin: ", msg = e.msg + error "error during authorize: ", msg = e.msg self.events.emit(SIGNAL_KEYCARD_AUTHORIZE_FAILURE, KeycardErrorArg(error: e.msg)) proc receiveKeycardSignalV2(self: Service, signal: string) {.slot.} = @@ -231,9 +232,37 @@ QtObject: raise newException(RpcException, "Error authorizing: " & error.message) let keys = rpcResponseObj["result"]["keys"].toKeycardExportedKeysDto() - self.events.emit(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS, KeycardExportedKeysArg(exportedKeys: keys)) + self.events.emit(SIGNAL_KEYCARD_EXPORT_RESTORE_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)) + self.events.emit(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE, KeycardErrorArg(error: e.msg)) + + proc asyncExportLoginKeys*(self: Service) = + self.rpcCounter += 1 + let arg = AsyncExportLoginKeysArg( + tptr: asyncExportLoginKeysTask, + vptr: cast[uint](self.vptr), + slot: "onAsyncExportLoginKeys", + rpcCounter: self.rpcCounter, + ) + self.threadpool.start(arg) + + proc onAsyncExportLoginKeys*(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_LOGIN_KEYS_SUCCESS, KeycardExportedKeysArg(exportedKeys: keys)) + except Exception as e: + error "error exporting login keys", msg = e.msg + self.events.emit(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE, KeycardErrorArg(error: e.msg)) \ No newline at end of file diff --git a/storybook/qmlTests/tests/tst_OnboardingLayout.qml b/storybook/qmlTests/tests/tst_OnboardingLayout.qml index 8af1466eae8..555a2b21fd6 100644 --- a/storybook/qmlTests/tests/tst_OnboardingLayout.qml +++ b/storybook/qmlTests/tests/tst_OnboardingLayout.qml @@ -62,6 +62,7 @@ Item { readonly property int authorizationState: mockDriver.authorizationState // enum Onboarding.ProgressState readonly property int restoreKeysExportState: mockDriver.restoreKeysExportState // enum Onboarding.ProgressState property int keycardRemainingPinAttempts: 5 + property int keycardRemainingPukAttempts: 5 function setPin(pin: string) { const valid = pin === mockDriver.existingPin @@ -316,7 +317,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithPassword) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithPassword) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, mockDriver.dummyNewPassword) @@ -414,7 +415,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithSeedphrase) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithSeedphrase) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, mockDriver.dummyNewPassword) @@ -563,7 +564,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithKeycardNewSeedphrase) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithKeycardNewSeedphrase) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, "") @@ -666,7 +667,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, "") @@ -759,7 +760,7 @@ Item { } tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithSeedphrase) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithSeedphrase) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, mockDriver.dummyNewPassword) @@ -851,7 +852,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithSyncing) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithSyncing) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, "") @@ -924,7 +925,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithKeycard) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithKeycard) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, "") @@ -1017,6 +1018,7 @@ Item { compare(resultData.password, data.password) // verify validation & pass error + console.log("---- passwords:", data.password, mockDriver.dummyNewPassword) tryCompare(passwordInput, "hasError", data.password !== mockDriver.dummyNewPassword) } else if (!!data.pin) { // keycard profile mockDriver.keycardState = Onboarding.KeycardState.NotEmpty // happy path; keycard ready @@ -1053,7 +1055,7 @@ Item { } else { // manual PIN keyClickSequence(data.pin) if (data.pin !== mockDriver.existingPin) { - expectFail(data.tag, "Wrong PIN entered, expected to fail to login") + // Everything will still be called as with a good pin, the wrong pin return is async } } @@ -1174,7 +1176,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithLostKeycardSeedphrase) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithLostKeycardSeedphrase) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.password, mockDriver.dummyNewPassword) @@ -1260,7 +1262,7 @@ Item { // FINISH tryCompare(finishedSpy, "count", 1) - compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithRestoredKeycard) + compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithRestoredKeycard) const resultData = finishedSpy.signalArguments[0][1] verify(!!resultData) compare(resultData.enableBiometrics, false) diff --git a/ui/StatusQ/src/onboarding/enums.h b/ui/StatusQ/src/onboarding/enums.h index f83d50c9a97..a5e22e14c9a 100644 --- a/ui/StatusQ/src/onboarding/enums.h +++ b/ui/StatusQ/src/onboarding/enums.h @@ -33,7 +33,7 @@ class OnboardingEnums: public QObject Login }; - enum class SecondaryFlow { + enum class OnboardingFlow { Unknown, CreateProfileWithPassword, @@ -50,6 +50,7 @@ class OnboardingEnums: public QObject }; enum class LoginMethod { + Unknown, Password, Keycard, }; @@ -82,7 +83,7 @@ class OnboardingEnums: public QObject private: Q_ENUM(PrimaryFlow) - Q_ENUM(SecondaryFlow) + Q_ENUM(OnboardingFlow) Q_ENUM(LoginMethod) Q_ENUM(KeycardState) Q_ENUM(ProgressState) diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml index 7d393cdefcf..dca57c2ce55 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml @@ -166,7 +166,7 @@ SQUtils.QObject { CreateProfilePage { onCreateProfileWithPasswordRequested: createNewProfileFlow.init() onCreateProfileWithSeedphraseRequested: { - d.flow = Onboarding.SecondaryFlow.CreateProfileWithSeedphrase + d.flow = Onboarding.OnboardingFlow.CreateProfileWithSeedphrase useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.NewProfile) } onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init() @@ -183,7 +183,7 @@ SQUtils.QObject { onLoginWithKeycardRequested: loginWithKeycardFlow.init() onLoginWithSeedphraseRequested: { - d.flow = Onboarding.SecondaryFlow.LoginWithSeedphrase + d.flow = Onboarding.OnboardingFlow.LoginWithSeedphrase useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.Login) } } @@ -194,12 +194,12 @@ SQUtils.QObject { KeycardLostPage { onCreateReplacementKeycardRequested: { - d.flow = Onboarding.SecondaryFlow.LoginWithRestoredKeycard + d.flow = Onboarding.OnboardingFlow.LoginWithRestoredKeycard keycardCreateReplacementFlow.init() } onUseProfileWithoutKeycardRequested: { - d.flow = Onboarding.SecondaryFlow.LoginWithLostKeycardSeedphrase + d.flow = Onboarding.OnboardingFlow.LoginWithLostKeycardSeedphrase useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.KeycardRecovery) } } @@ -213,7 +213,7 @@ SQUtils.QObject { onFinished: (password) => { root.setPasswordRequested(password) - d.flow = Onboarding.SecondaryFlow.CreateProfileWithPassword + d.flow = Onboarding.OnboardingFlow.CreateProfileWithPassword d.pushOrSkipBiometricsPage() } } @@ -263,8 +263,8 @@ SQUtils.QObject { onFinished: (withNewSeedphrase) => { d.flow = withNewSeedphrase - ? Onboarding.SecondaryFlow.CreateProfileWithKeycardNewSeedphrase - : Onboarding.SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase + ? Onboarding.OnboardingFlow.CreateProfileWithKeycardNewSeedphrase + : Onboarding.OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase d.pushOrSkipBiometricsPage() } @@ -281,12 +281,12 @@ SQUtils.QObject { root.syncProceedWithConnectionString(connectionString) onLoginWithSeedphraseRequested: { - d.flow = Onboarding.SecondaryFlow.LoginWithSeedphrase + d.flow = Onboarding.OnboardingFlow.LoginWithSeedphrase useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.Login) } onFinished: { - d.flow = Onboarding.SecondaryFlow.LoginWithSyncing + d.flow = Onboarding.OnboardingFlow.LoginWithSyncing d.pushOrSkipBiometricsPage() } } @@ -314,7 +314,7 @@ SQUtils.QObject { onUnblockWithPukRequested: unblockWithPukFlow.init() onFinished: { - d.flow = Onboarding.SecondaryFlow.LoginWithKeycard + d.flow = Onboarding.OnboardingFlow.LoginWithKeycard d.pushOrSkipBiometricsPage() } } @@ -365,7 +365,7 @@ SQUtils.QObject { root.loginRequested(root.loginScreen.selectedProfileKeyId, Onboarding.LoginMethod.Keycard, { pin }) } else { - d.flow = Onboarding.SecondaryFlow.LoginWithKeycard + d.flow = Onboarding.OnboardingFlow.LoginWithKeycard d.pushOrSkipBiometricsPage() } } diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml index 0ff77a1ecea..4be5a8a5285 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml @@ -33,7 +33,7 @@ Page { signal shareUsageDataRequested(bool enabled) - // flow: Onboarding.SecondaryFlow + // flow: Onboarding.OnboardingFlow signal finished(int flow, var data) // -> "keyUid:string": User ID to login; "method:int": password or keycard (cf Onboarding.LoginMethod.*) enum; @@ -262,6 +262,15 @@ Page { loginScreen.setObtainingPasswordSuccess(password) } + + function onAccountLoginError(error: string, wrongPassword: bool) { + const loginScreen = onboardingFlow.loginScreen + + if (!loginScreen) + return + + loginScreen.setAccountLoginError(password, wrongPassword) + } } Component.onCompleted: restartFlow() diff --git a/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml b/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml index 44b40202f22..1641519ae03 100644 --- a/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml +++ b/ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml @@ -14,9 +14,9 @@ Control { id: root required property int keycardState - property var tryToSetPinFunction: (pin) => { console.error("LoginKeycardBox::tryToSetPinFunction: IMPLEMENT ME"); return false } required property int keycardRemainingPinAttempts required property int keycardRemainingPukAttempts + property string loginError required property bool isBiometricsLogin required property bool biometricsSuccessful @@ -37,6 +37,12 @@ Control { pinInputField.forceFocus() } + function onWrongPin() { + d.wrongPin = true + pinInputField.statesInitialization() + pinInputField.forceFocus() + } + function setPin(pin: string) { pinInputField.setPin(pin) } @@ -106,14 +112,7 @@ Control { onPinInputChanged: { if (pinInput.length === 6) { - if (root.tryToSetPinFunction(pinInput)) { - root.loginRequested(pinInput) - d.wrongPin = false - } else { - d.wrongPin = true - pinInputField.statesInitialization() - pinInputField.forceFocus() - } + root.loginRequested(pinInput) } } onPinEditedManually: { @@ -157,7 +156,7 @@ Control { PropertyChanges { target: infoText color: Theme.palette.dangerColor1 - text: qsTr("Oops this isn’t a Keycard.
Remove card and insert a Keycard.") + text: qsTr("Oops this isn't a Keycard.
Remove card and insert a Keycard.") } }, State { @@ -212,6 +211,16 @@ Control { text: qsTr("PIN incorrect. %n attempt(s) remaining.", "", root.keycardRemainingPinAttempts) } }, + State { + // TODO this is a deadend. We should never end up here, but I still don't know what it should look like + name: "errorDuringLogin" + when: !!root.loginError + PropertyChanges { + target: infoText + color: Theme.palette.dangerColor1 + text: qsTr("Error during login: %1").arg(root.loginError) + } + }, // exit states State { name: "notEmpty" diff --git a/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml b/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml index b65e35a5a41..02f9340e9f0 100644 --- a/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml +++ b/ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml @@ -121,6 +121,35 @@ OnboardingPage { passwordBox.detailedError = detailedError } + // (password) login + function setAccountLoginError(error: string, wrongPassword: bool) { + if (!error) { + return + } + + if (d.currentProfileIsKeycard) { + // Login with keycard + if (wrongPassword) { + keycardBox.onWrongPin() + } else { + keycardBox.loginError = error + } + return + } + + // Login with password + if (wrongPassword) { + passwordBox.validationError = qsTr("Password incorrect. %1").arg("" + qsTr("Forgot password?") + "") + passwordBox.detailedError = "" + } else { + passwordBox.validationError = qsTr("Login failed. %1").arg("" + qsTr("Show details.") + "") + passwordBox.detailedError = error + } + + passwordBox.clear() + passwordBox.forceActiveFocus() + } + padding: 40 contentItem: Item { diff --git a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml index 0775b46f611..929dc761446 100644 --- a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml +++ b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml @@ -8,6 +8,7 @@ QtObject { id: root signal appLoaded + readonly property QtObject d: StatusQUtils.QObject { id: d readonly property var onboardingModuleInst: onboardingModule @@ -18,6 +19,8 @@ QtObject { } } + readonly property var loginAccountsModel: d.onboardingModuleInst.loginAccountsModel + // keycard readonly property int keycardState: d.onboardingModuleInst.keycardState // cf. enum Onboarding.KeycardState readonly property int pinSettingState: d.onboardingModuleInst.pinSettingState // cf. enum Onboarding.ProgressState @@ -30,6 +33,10 @@ QtObject { return d.onboardingModuleInst.finishOnboardingFlow(flow, JSON.stringify(data)) } + function loginRequested(keyUid: string, method: int, data: Object) { // -> void + d.onboardingModuleInst.loginRequested(keyUid, method, JSON.stringify(data)) + } + function setPin(pin: string) { d.onboardingModuleInst.setPin(pin) } diff --git a/ui/main.qml b/ui/main.qml index b6cb8c92203..302794b1b25 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -193,7 +193,6 @@ StatusWindow { } startupOnboardingLoader.item.unload() startupOnboardingLoader.active = false - onboardingStoreLoader.active = false Theme.changeTheme(localAppSettings.theme, systemPalette.isCurrentSystemThemeDark()) Theme.changeFontSize(localAccountSensitiveSettings.fontSize) @@ -407,15 +406,6 @@ StatusWindow { } } - Loader { - id: onboardingStoreLoader - active: featureFlagsStore.onboardingV2Enabled - - sourceComponent: OnboardingStore { - onAppLoaded: moveToAppMain() - } - } - Loader { id: startupOnboardingLoader anchors.fill: parent @@ -443,32 +433,35 @@ StatusWindow { id: onboardingV2 Onboarding2.OnboardingLayout { + id: onboardingLayout objectName: "startupOnboardingLayout" anchors.fill: parent // TODO implement those two - loginAccountsModel: ListModel {} + loginAccountsModel: onboardingStore.loginAccountsModel isBiometricsLogin: false networkChecksEnabled: true biometricsAvailable: Qt.platform.os === Constants.mac - onboardingStore: onboardingStoreLoader.item + onboardingStore: onboardingStore onFinished: (flow, data) => { - console.warn("!!! ONBOARDING FINISHED; flow:", flow, "; data:", JSON.stringify(data)) - - let error = onboardingStoreLoader.item.finishOnboardingFlow(flow, data) + let error = onboardingStore.finishOnboardingFlow(flow, data) if (error != "") { + // We should never be here since everything should be validated already console.error("!!! ONBOARDING FINISHED WITH ERROR:", error) - // TODO show error return } - console.warn("!!! Onboarding completed!") stack.clear() stack.push(splashScreenV2, { runningProgressAnimation: true }) } + onLoginRequested: function (keyUid, method, data) { + stack.push(splashScreenV2, { runningProgressAnimation: true }) + onboardingStore.loginRequested(keyUid, method, data) + } + onShareUsageDataRequested: { applicationWindow.metricsStore.toggleCentralizedMetrics(enabled) if (enabled) { @@ -476,6 +469,14 @@ StatusWindow { } } onCurrentPageNameChanged: Global.addCentralizedMetricIfEnabled("navigation", {viewId: currentPageName}) + + OnboardingStore { + id: onboardingStore + onAppLoaded: moveToAppMain() + onAccountLoginError: function (error, wrongPassword) { + onboardingLayout.stack.pop() + } + } } }