From 59cb770ddb9fefd6159bf62154dd468e82205477 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Wed, 24 Apr 2024 18:31:22 +0200 Subject: [PATCH] Switch to new login/create/restore status-go endpoints (#14139) * fix: new login endpoints - iteration 1 * feat: use CreateAccountAndLogin endpoint * fix keycard import * better comment * wip: screens reordering * cleanup * force minimum of 30 seconds fetching backup * keycard flows fixes * typo fix * remove ganache argument * fix: wallet issues * update status-go --- src/app/modules/main/module.nim | 5 +- .../keycard_popup/controller.nim | 2 +- src/app/modules/startup/controller.nim | 133 +++++++--- .../startup/internal/biometrics_state.nim | 44 ++-- .../startup/internal/notification_state.nim | 2 +- .../internal/user_profile_chat_key_state.nim | 23 +- .../user_profile_confirm_password_state.nim | 12 +- .../user_profile_create_password_state.nim | 2 +- .../internal/user_profile_create_state.nim | 18 +- .../user_profile_enter_seed_phrase_state.nim | 57 ++-- src/app/modules/startup/io_interface.nim | 24 +- src/app/modules/startup/module.nim | 115 ++++---- src/app/modules/startup/view.nim | 34 ++- .../service/accounts/dto/accounts.nim | 2 +- .../accounts/dto/create_account_request.nim | 88 +++++++ .../accounts/dto/image_crop_rectangle.nim | 8 + .../service/accounts/dto/login_request.nim | 22 ++ .../accounts/dto/restore_account_request.nim | 18 ++ .../accounts/dto/wallet_secretes_config.nim | 38 +++ src/app_service/service/accounts/service.nim | 249 +++++++++--------- src/backend/accounts.nim | 75 ++---- .../Onboarding/OnboardingLayout.qml | 11 +- .../Onboarding/stores/StartupStore.qml | 1 + .../views/AllowNotificationsView.qml | 2 +- .../Onboarding/views/InsertDetailsView.qml | 184 +------------ .../Onboarding/views/ProfileChatKeyView.qml | 169 ++++++++++++ .../Onboarding/views/ProfileFetchingView.qml | 11 +- .../views/SeedPhraseWordsInputView.qml | 2 +- vendor/nim-status-go | 2 +- vendor/status-go | 2 +- 30 files changed, 813 insertions(+), 542 deletions(-) create mode 100644 src/app_service/service/accounts/dto/create_account_request.nim create mode 100644 src/app_service/service/accounts/dto/image_crop_rectangle.nim create mode 100644 src/app_service/service/accounts/dto/login_request.nim create mode 100644 src/app_service/service/accounts/dto/restore_account_request.nim create mode 100644 src/app_service/service/accounts/dto/wallet_secretes_config.nim create mode 100644 ui/app/AppLayouts/Onboarding/views/ProfileChatKeyView.qml diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 546939ea81..d0e8898d41 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -1546,10 +1546,11 @@ proc runStartUsingKeycardForProfilePopup[T](self: Module[T]) = ################################################################################ method checkAndPerformProfileMigrationIfNeeded*[T](self: Module[T]) = + let keyUid = singletonInstance.userProfile.getKeyUid() let migrationNeeded = self.settingsService.getProfileMigrationNeeded() - let profileKeypair = self.walletAccountService.getKeypairByKeyUid(singletonInstance.userProfile.getKeyUid()) + let profileKeypair = self.walletAccountService.getKeypairByKeyUid(keyUid) if profileKeypair.isNil: - info "quit the app because of unresolved profile keypair" + info "quit the app because of unresolved profile keypair", keyUid quit() # quit the app if not migrationNeeded: if not self.keycardSharedModule.isNil: diff --git a/src/app/modules/shared_modules/keycard_popup/controller.nim b/src/app/modules/shared_modules/keycard_popup/controller.nim index efaec6bc7e..4c90c89c9c 100644 --- a/src/app/modules/shared_modules/keycard_popup/controller.nim +++ b/src/app/modules/shared_modules/keycard_popup/controller.nim @@ -370,7 +370,7 @@ proc getSeedPhraseLength*(self: Controller): int = proc validSeedPhrase*(self: Controller, seedPhrase: string): bool = if not serviceApplicable(self.accountsService): return - let err = self.accountsService.validateMnemonic(seedPhrase) + let (_, err) = self.accountsService.validateMnemonic(seedPhrase) return err.len == 0 proc getKeyUidForSeedPhrase*(self: Controller, seedPhrase: string): string = diff --git a/src/app/modules/startup/controller.nim b/src/app/modules/startup/controller.nim index 637c8a08af..d6f1332834 100644 --- a/src/app/modules/startup/controller.nim +++ b/src/app/modules/startup/controller.nim @@ -13,19 +13,17 @@ import app_service/service/profile/service as profile_service import app_service/service/keycard/service as keycard_service import app_service/service/devices/service as devices_service import app_service/common/[account_constants, utils] - import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module +import app_service/service/accounts/dto/create_account_request + logScope: topics = "startup-controller" type ProfileImageDetails = object url*: string - croppedImage*: string - x1*: int - y1*: int - x2*: int - y2*: int + croppedImage*: string # TODO: Remove after https://github.com/status-im/status-go/issues/4977 + cropRectangle*: ImageCropRectangle type Controller* = ref object of RootObj @@ -57,6 +55,7 @@ type tmpRecoverKeycardUsingSeedPhraseWhileLoggingIn: bool tmpConnectionString: string localPairingStatus: LocalPairingStatus + loggedInPofilePublicKey: string proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter, @@ -116,12 +115,6 @@ proc connectToFetchingFromWakuEvents*(self: Controller) = self.delegate.onFetchingFromWakuMessageReceived(receivedData.clock, k, v.totalNumber, v.dataNumber) self.connectionIds.add(handlerId) -proc connectToTimeoutEventAndStratTimer*(self: Controller, timeoutInMilliseconds: int) = - var handlerId = self.events.onWithUUID(SIGNAL_GENERAL_TIMEOUT) do(e: Args): - self.delegate.startAppAfterDelay() - self.connectionIds.add(handlerId) - self.generalService.runTimer(timeoutInMilliseconds) - proc disconnect*(self: Controller) = self.disconnectKeychain() for id in self.connectionIds: @@ -134,7 +127,7 @@ proc delete*(self: Controller) = proc init*(self: Controller) = var handlerId = self.events.onWithUUID(SignalType.NodeLogin.event) do(e:Args): let signal = NodeSignal(e) - self.delegate.onNodeLogin(signal.error) + self.delegate.onNodeLogin(signal.error, signal.account, signal.settings) self.connectionIds.add(handlerId) handlerId = self.events.onWithUUID(SignalType.NodeStopped.event) do(e:Args): @@ -196,12 +189,14 @@ proc init*(self: Controller) = proc shouldStartWithOnboardingScreen*(self: Controller): bool = return self.accountsService.openedAccounts().len == 0 +# This is used when fetching backup failed and we create a new displayName and profileImage. +# At this point the account is already created in the database. All that's left is to set the displayName and profileImage. proc storeProfileDataAndProceedWithAppLoading*(self: Controller) = self.delegate.removeAllKeycardUidPairsForCheckingForAChangeAfterLogin() # reason for this is in the table in AppController.nim file discard self.profileService.setDisplayName(self.tmpDisplayName) let images = self.storeIdentityImage() self.accountsService.updateLoggedInAccount(self.tmpDisplayName, images) - self.delegate.finishAppLoading() + self.delegate.notifyLoggedInAccountChanged() proc checkFetchingStatusAndProceed*(self: Controller) = self.delegate.checkFetchingStatusAndProceed() @@ -221,11 +216,15 @@ proc clearImage*(self: Controller) = proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string = let formatedImg = singletonInstance.utils.formatImagePath(imageUrl) let images = self.generalService.generateImages(formatedImg, aX, aY, bX, bY) - if(images.len == 0): + if images.len == 0: return for img in images: - if(img.imgType == "large"): - self.tmpProfileImageDetails = ProfileImageDetails(url: imageUrl, croppedImage: img.uri, x1: aX, y1: aY, x2: bX, y2: bY) + if img.imgType == "large": + self.tmpProfileImageDetails = ProfileImageDetails( + url: formatedImg, + croppedImage: img.uri, + cropRectangle: ImageCropRectangle(ax: aX, ay: aY, bx: bX, by: bY) + ) return img.uri proc fetchWakuMessages*(self: Controller) = @@ -349,22 +348,46 @@ proc tryToObtainDataFromKeychain*(self: Controller) = let selectedAccount = self.getSelectedLoginAccount() self.keychainService.tryToObtainData(selectedAccount.keyUid) +# TODO: Remove when implemented https://github.com/status-im/status-go/issues/4977 proc storeIdentityImage*(self: Controller): seq[Image] = if self.tmpProfileImageDetails.url.len == 0: return let account = self.accountsService.getLoggedInAccount() let image = singletonInstance.utils.formatImagePath(self.tmpProfileImageDetails.url) - result = self.profileService.storeIdentityImage(account.keyUid, image, self.tmpProfileImageDetails.x1, - self.tmpProfileImageDetails.y1, self.tmpProfileImageDetails.x2, self.tmpProfileImageDetails.y2) + result = self.profileService.storeIdentityImage( + account.keyUid, + image, + self.tmpProfileImageDetails.cropRectangle.aX, + self.tmpProfileImageDetails.cropRectangle.aY, + self.tmpProfileImageDetails.cropRectangle.bX, + self.tmpProfileImageDetails.cropRectangle.bY, + ) self.tmpProfileImageDetails = ProfileImageDetails() +# validMnemonic only checks if mnemonic is valid +# This is used from UI. proc validMnemonic*(self: Controller, mnemonic: string): bool = - let err = self.accountsService.validateMnemonic(mnemonic) + let (keyUID, err) = self.accountsService.validateMnemonic(mnemonic) if err.len == 0: self.setSeedPhrase(mnemonic) return true return false +# validateMnemonicForImport checks if mnemonic is valid and not yet saved in local database +proc validateMnemonicForImport*(self: Controller, mnemonic: string): bool = + let (keyUID, err) = self.accountsService.validateMnemonic(mnemonic) + if err.len != 0: + self.delegate.emitStartupError(err, StartupErrorType.ImportAccError) + return false + + if self.accountsService.openedAccountsContainsKeyUid(keyUID): + self.delegate.emitStartupError(ACCOUNT_ALREADY_EXISTS_ERROR, StartupErrorType.ImportAccError) + return false + + self.setSeedPhrase(mnemonic) + return true + +# TODO: Remove after https://github.com/status-im/status-go/issues/4977 proc importMnemonic*(self: Controller): bool = let error = self.accountsService.importMnemonic(self.tmpSeedPhrase) if(error.len == 0): @@ -380,25 +403,37 @@ proc setupKeychain(self: Controller, store: bool) = else: singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER) -proc setupAccount(self: Controller, accountId: string, removeMnemonic: bool, storeToKeychain: bool, recoverAccount: bool = false) = - self.delegate.moveToLoadingAppState() - let error = self.accountsService.setupAccount(accountId, self.tmpPassword, self.tmpDisplayName, removeMnemonic, recoverAccount) +proc processCreateAccountResult*(self: Controller, error: string, storeToKeychain: bool) = if error != "": self.delegate.emitStartupError(error, StartupErrorType.SetupAccError) else: self.setupKeychain(storeToKeychain) -proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) = - let accounts = self.getGeneratedAccounts() - if accounts.len == 0: - error "list of generated accounts is empty" - return - let accountId = accounts[0].id - self.setupAccount(accountId, removeMnemonic=false, storeToKeychain) +proc createAccountAndLogin*(self: Controller, storeToKeychain: bool) = + self.delegate.moveToLoadingAppState() + let error = self.accountsService.createAccountAndLogin( + self.tmpPassword, + self.tmpDisplayName, + self.tmpProfileImageDetails.url, + self.tmpProfileImageDetails.cropRectangle + ) + self.processCreateAccountResult(error, storeToKeychain) -proc storeImportedAccountAndLogin*(self: Controller, storeToKeychain: bool, recoverAccount: bool = false) = - let accountId = self.getImportedAccount().id - self.setupAccount(accountId, removeMnemonic=true, storeToKeychain, recoverAccount) +proc importAccountAndLogin*(self: Controller, storeToKeychain: bool, recoverAccount: bool = false) = + if recoverAccount: + self.delegate.prepareAndInitFetchingData() + self.connectToFetchingFromWakuEvents() + else: + self.delegate.moveToLoadingAppState() + let error = self.accountsService.importAccountAndLogin( + self.tmpSeedPhrase, + self.tmpPassword, + recoverAccount, + self.tmpDisplayName, + self.tmpProfileImageDetails.url, + self.tmpProfileImageDetails.cropRectangle, + ) + self.processCreateAccountResult(error, storeToKeychain) proc storeKeycardAccountAndLogin*(self: Controller, storeToKeychain: bool, newKeycard: bool) = if self.importMnemonic(): @@ -419,6 +454,9 @@ proc setupKeycardAccount*(self: Controller, storeToKeychain: bool, recoverAccoun self.accountsService.openedAccountsContainsKeyUid(self.tmpKeycardEvent.keyUid): self.delegate.emitStartupError(ACCOUNT_ALREADY_EXISTS_ERROR, StartupErrorType.ImportAccError) return + if recoverAccount: + self.delegate.prepareAndInitFetchingData() + self.connectToFetchingFromWakuEvents() if self.tmpSeedPhrase.len > 0: # if `tmpSeedPhrase` is not empty means user has recovered keycard via seed phrase let accFromSeedPhrase = self.accountsService.createAccountFromMnemonic(self.tmpSeedPhrase, includeEncryption = true, @@ -441,7 +479,7 @@ proc setupKeycardAccount*(self: Controller, storeToKeychain: bool, recoverAccoun self.tmpKeycardEvent.encryptionKey.privateKey = accFromSeedPhrase.derivedAccounts.encryption.privateKey self.tmpKeycardEvent.encryptionKey.publicKey = accFromSeedPhrase.derivedAccounts.encryption.publicKey self.tmpKeycardEvent.encryptionKey.address = accFromSeedPhrase.derivedAccounts.encryption.address - self.delegate.moveToLoadingAppState() + self.syncKeycardBasedOnAppWalletStateAfterLogin() self.accountsService.setupAccountKeycard(self.tmpKeycardEvent, self.tmpDisplayName, useImportedAcc = false, recoverAccount) self.setupKeychain(storeToKeychain) @@ -611,3 +649,30 @@ proc validateLocalPairingConnectionString*(self: Controller, connectionString: s proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string): string = return self.devicesService.inputConnectionStringForBootstrapping(connectionString) + +proc setLoggedInAccount*(self: Controller, account: AccountDto) = + self.accountsService.setLoggedInAccount(account) + +proc setLoggedInProfile*(self: Controller, publicKey: string) = + self.loggedInPofilePublicKey = publicKey + +proc getLoggedInAccountPublicKey*(self: Controller): string = + return self.loggedInPofilePublicKey + +proc getLoggedInAccountDisplayName*(self: Controller): string = + return self.accountsService.getLoggedInAccount().name + +proc getLoggedInAccountImage*(self: Controller): string = + let images = self.accountsService.getLoggedInAccount().images + for img in images: + if img.imgType == "large": + return img.uri + return "" + +# NOTE: This could be a constant now, but in future we should check if the user +# has already enabled notifications and return corresponding result from this function. +proc notificationsNeedsEnable*(self: Controller): bool = + return main_constants.IS_MACOS + +proc proceedToApp*(self: Controller) = + self.delegate.finishAppLoading() diff --git a/src/app/modules/startup/internal/biometrics_state.nim b/src/app/modules/startup/internal/biometrics_state.nim index 0be5b3579c..beb9bf1127 100644 --- a/src/app/modules/startup/internal/biometrics_state.nim +++ b/src/app/modules/startup/internal/biometrics_state.nim @@ -10,16 +10,24 @@ proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState proc delete*(self: BiometricsState) = self.State.delete -method executePrimaryCommand*(self: BiometricsState, controller: Controller) = - let storeToKeychain = true # true, cause we have support for keychain for mac os +method getNextPrimaryState*(self: BiometricsState, controller: Controller): State = + if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or + self.flowType == FlowType.FirstRunOldUserKeycardImport: + return createState(StateType.ProfileFetching, self.flowType, self) + return nil + +method getNextSecondaryState*(self: BiometricsState, controller: Controller): State = + return self.getNextPrimaryState(controller) + +proc command(self: BiometricsState, controller: Controller, storeToKeychain: bool) = if self.flowType == FlowType.FirstRunNewUserNewKeys: - controller.storeGeneratedAccountAndLogin(storeToKeychain) + controller.createAccountAndLogin(storeToKeychain) elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase: - controller.storeImportedAccountAndLogin(storeToKeychain) + controller.importAccountAndLogin(storeToKeychain) elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase: ## This should not be the correct call for this flow, this is an issue, ## but since current implementation is like that and this is not a bug fixing issue, left as it is. - controller.storeImportedAccountAndLogin(storeToKeychain, recoverAccount = true) + controller.importAccountAndLogin(storeToKeychain, recoverAccount = true) elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys: controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true) elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: @@ -32,27 +40,11 @@ method executePrimaryCommand*(self: BiometricsState, controller: Controller) = elif self.flowType == FlowType.LostKeycardConvertToRegularAccount: controller.loginAccountKeycardUsingSeedPhrase(storeToKeychain) +method executePrimaryCommand*(self: BiometricsState, controller: Controller) = + self.command(controller, true) + method executeSecondaryCommand*(self: BiometricsState, controller: Controller) = - let storeToKeychain = false # false, cause we don't have keychain support for other than mac os - if self.flowType == FlowType.FirstRunNewUserNewKeys: - controller.storeGeneratedAccountAndLogin(storeToKeychain) - elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase: - controller.storeImportedAccountAndLogin(storeToKeychain) - elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase: - ## This should not be the correct call for this flow, this is an issue, - ## but since current implementation is like that and this is not a bug fixing issue, left as it is. - controller.storeImportedAccountAndLogin(storeToKeychain, recoverAccount = true) - elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys: - controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true) - elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: - controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true) - elif self.flowType == FlowType.FirstRunOldUserKeycardImport: - controller.setupKeycardAccount(storeToKeychain, recoverAccount = true) - elif self.flowType == FlowType.LostKeycardReplacement: - self.storeToKeychain = storeToKeychain - controller.startLoginFlowAutomatically(controller.getPin()) - elif self.flowType == FlowType.LostKeycardConvertToRegularAccount: - controller.loginAccountKeycardUsingSeedPhrase(storeToKeychain) + self.command(controller, false) method resolveKeycardNextState*(self: BiometricsState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = @@ -63,4 +55,4 @@ method resolveKeycardNextState*(self: BiometricsState, keycardFlowType: string, var storeToKeychainValue = LS_VALUE_NEVER if self.storeToKeychain: storeToKeychainValue = LS_VALUE_NOT_NOW - controller.loginAccountKeycard(storeToKeychainValue, keycardReplacement = true) \ No newline at end of file + controller.loginAccountKeycard(storeToKeychainValue, keycardReplacement = true) diff --git a/src/app/modules/startup/internal/notification_state.nim b/src/app/modules/startup/internal/notification_state.nim index 2ee9f63c2c..7bb76937f6 100644 --- a/src/app/modules/startup/internal/notification_state.nim +++ b/src/app/modules/startup/internal/notification_state.nim @@ -9,4 +9,4 @@ proc delete*(self: NotificationState) = self.State.delete method getNextPrimaryState*(self: NotificationState, controller: Controller): State = - return createState(StateType.Welcome, FlowType.General, nil) + controller.proceedToApp() diff --git a/src/app/modules/startup/internal/user_profile_chat_key_state.nim b/src/app/modules/startup/internal/user_profile_chat_key_state.nim index b57836a46e..e916f8ebd0 100644 --- a/src/app/modules/startup/internal/user_profile_chat_key_state.nim +++ b/src/app/modules/startup/internal/user_profile_chat_key_state.nim @@ -9,23 +9,10 @@ proc delete*(self: UserProfileChatKeyState) = self.State.delete method executePrimaryCommand*(self: UserProfileChatKeyState, controller: Controller) = - if main_constants.IS_MACOS: - return - let storeToKeychain = false # false, cause we don't have keychain support for other than mac os - if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys: - controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true) - elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: - controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true) - elif self.flowType == FlowType.FirstRunOldUserKeycardImport: - controller.setupKeycardAccount(storeToKeychain) + if not controller.notificationsNeedsEnable(): + controller.proceedToApp() method getNextPrimaryState*(self: UserProfileChatKeyState, controller: Controller): State = - if self.flowType == FlowType.FirstRunNewUserNewKeys or - self.flowType == FlowType.FirstRunNewUserImportSeedPhrase: - return createState(StateType.UserProfileCreatePassword, self.flowType, self) - if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or - self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or - self.flowType == FlowType.FirstRunOldUserKeycardImport: - if not main_constants.IS_MACOS: - return nil - return createState(StateType.Biometrics, self.flowType, self) + if controller.notificationsNeedsEnable(): + return createState(StateType.AllowNotifications, self.flowType, nil) + return nil diff --git a/src/app/modules/startup/internal/user_profile_confirm_password_state.nim b/src/app/modules/startup/internal/user_profile_confirm_password_state.nim index b08c3c1816..06a8dba127 100644 --- a/src/app/modules/startup/internal/user_profile_confirm_password_state.nim +++ b/src/app/modules/startup/internal/user_profile_confirm_password_state.nim @@ -9,23 +9,21 @@ proc delete*(self: UserProfileConfirmPasswordState) = self.State.delete method getNextPrimaryState*(self: UserProfileConfirmPasswordState, controller: Controller): State = - if not main_constants.IS_MACOS: + if not main_constants.SUPPORTS_FINGERPRINT: return nil return createState(StateType.Biometrics, self.flowType, self) method executePrimaryCommand*(self: UserProfileConfirmPasswordState, controller: Controller) = - if main_constants.IS_MACOS: + if main_constants.SUPPORTS_FINGERPRINT: return let storeToKeychain = false # false, cause we don't have keychain support for other than mac os if self.flowType == FlowType.FirstRunNewUserNewKeys: - controller.storeGeneratedAccountAndLogin(storeToKeychain) + controller.createAccountAndLogin(storeToKeychain) elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase: - controller.storeImportedAccountAndLogin(storeToKeychain) + controller.importAccountAndLogin(storeToKeychain) elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys: controller.storeKeycardAccountAndLogin(storeToKeychain, newKeycard = true) elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase: - controller.storeImportedAccountAndLogin(storeToKeychain, recoverAccount = true) + controller.importAccountAndLogin(storeToKeychain, recoverAccount = true) elif self.flowType == FlowType.LostKeycardConvertToRegularAccount: controller.loginAccountKeycardUsingSeedPhrase(storeToKeychain) - - diff --git a/src/app/modules/startup/internal/user_profile_create_password_state.nim b/src/app/modules/startup/internal/user_profile_create_password_state.nim index a61fb81fe7..a9b145f82a 100644 --- a/src/app/modules/startup/internal/user_profile_create_password_state.nim +++ b/src/app/modules/startup/internal/user_profile_create_password_state.nim @@ -12,4 +12,4 @@ method getNextPrimaryState*(self: UserProfileCreatePasswordState, controller: Co return createState(StateType.UserProfileConfirmPassword, self.flowType, self) method executeBackCommand*(self: UserProfileCreatePasswordState, controller: Controller) = - controller.setPassword("") \ No newline at end of file + controller.setPassword("") diff --git a/src/app/modules/startup/internal/user_profile_create_state.nim b/src/app/modules/startup/internal/user_profile_create_state.nim index 213ba20d39..6187c5d810 100644 --- a/src/app/modules/startup/internal/user_profile_create_state.nim +++ b/src/app/modules/startup/internal/user_profile_create_state.nim @@ -9,6 +9,7 @@ proc delete*(self: UserProfileCreateState) = self.State.delete method executePrimaryCommand*(self: UserProfileCreateState, controller: Controller) = + # We're here in case of a backup fetch failure if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or self.flowType == FlowType.FirstRunOldUserKeycardImport: controller.storeProfileDataAndProceedWithAppLoading() @@ -16,9 +17,20 @@ method executePrimaryCommand*(self: UserProfileCreateState, controller: Controll method getNextPrimaryState*(self: UserProfileCreateState, controller: Controller): State = if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or self.flowType == FlowType.FirstRunOldUserKeycardImport: - return - return createState(StateType.UserProfileChatKey, self.flowType, self) + return createState(StateType.UserProfileChatKey, self.flowType, self) + + if self.flowType == FlowType.FirstRunNewUserNewKeys or + self.flowType == FlowType.FirstRunNewUserImportSeedPhrase: + return createState(StateType.UserProfileCreatePassword, self.flowType, self) + + if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or + self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: + if main_constants.SUPPORTS_FINGERPRINT: + return createState(StateType.Biometrics, self.flowType, self) + return createState(StateType.UserProfileChatKey, self.flowType, self) + + return nil method executeBackCommand*(self: UserProfileCreateState, controller: Controller) = controller.setDisplayName("") - controller.clearImage() \ No newline at end of file + controller.clearImage() diff --git a/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim b/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim index bd72e3a6a3..aeeb9de8f3 100644 --- a/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim +++ b/src/app/modules/startup/internal/user_profile_enter_seed_phrase_state.nim @@ -44,34 +44,37 @@ method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState, controller: C method executePrimaryCommand*(self: UserProfileEnterSeedPhraseState, controller: Controller) = if self.flowType == FlowType.FirstRunNewUserImportSeedPhrase or self.flowType == FlowType.FirstRunOldUserImportSeedPhrase: - self.successfulImport = controller.importMnemonic() - else: - self.successfulImport = controller.validMnemonic(controller.getSeedPhrase()) - if self.successfulImport: - controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false)) - let keyUid = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase()) + self.successfulImport = controller.validateMnemonicForImport(controller.getSeedPhrase()) + return - if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: - controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase()) - if self.flowType == FlowType.FirstRunOldUserKeycardImport: - self.enteredMnemonicMatchTargetedKeyUid = keyUid == controller.getKeyUid() - if not self.enteredMnemonicMatchTargetedKeyUid: - controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) - if self.flowType == FlowType.AppLogin: - self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid) - if not self.enteredMnemonicMatchTargetedKeyUid: - controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) - if self.flowType == FlowType.LostKeycardReplacement: - self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid) - if self.enteredMnemonicMatchTargetedKeyUid: - controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase()) - else: - controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) - if self.flowType == FlowType.LostKeycardConvertToRegularAccount: - self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid) - if not self.enteredMnemonicMatchTargetedKeyUid: - controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) + self.successfulImport = controller.validMnemonic(controller.getSeedPhrase()) + if not self.successfulImport: + return + + controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = false)) + let keyUid = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase()) + + if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: + controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase()) + if self.flowType == FlowType.FirstRunOldUserKeycardImport: + self.enteredMnemonicMatchTargetedKeyUid = keyUid == controller.getKeyUid() + if not self.enteredMnemonicMatchTargetedKeyUid: + controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) + if self.flowType == FlowType.AppLogin: + self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid) + if not self.enteredMnemonicMatchTargetedKeyUid: + controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) + if self.flowType == FlowType.LostKeycardReplacement: + self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid) + if self.enteredMnemonicMatchTargetedKeyUid: + controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase()) + else: + controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) + if self.flowType == FlowType.LostKeycardConvertToRegularAccount: + self.enteredMnemonicMatchTargetedKeyUid = controller.keyUidMatchSelectedLoginAccount(keyUid) + if not self.enteredMnemonicMatchTargetedKeyUid: + controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true)) method resolveKeycardNextState*(self: UserProfileEnterSeedPhraseState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = - return ensureReaderAndCardPresenceAndResolveNextOnboardingState(self, keycardFlowType, keycardEvent, controller) \ No newline at end of file + return ensureReaderAndCardPresenceAndResolveNextOnboardingState(self, keycardFlowType, keycardEvent, controller) diff --git a/src/app/modules/startup/io_interface.nim b/src/app/modules/startup/io_interface.nim index 261e8ebcf8..4079bda7c1 100644 --- a/src/app/modules/startup/io_interface.nim +++ b/src/app/modules/startup/io_interface.nim @@ -3,6 +3,7 @@ import ../../../app_service/service/accounts/service as accounts_service import models/login_account_item as login_acc_item from ../../../app_service/service/keycard/service import KeycardEvent, KeyDetails from ../../../app_service/service/devices/dto/local_pairing_status import LocalPairingStatus +from ../../../app_service/service/settings/dto/settings import SettingsDto const UNIQUE_STARTUP_MODULE_IDENTIFIER* = "SartupModule" @@ -115,7 +116,7 @@ method importAccountSuccess*(self: AccessInterface) {.base.} = method setSelectedLoginAccount*(self: AccessInterface, item: login_acc_item.Item) {.base.} = raise newException(ValueError, "No implementation available") -method onNodeLogin*(self: AccessInterface, error: string) {.base.} = +method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, settings: SettingsDto) {.base.} = raise newException(ValueError, "No implementation available") method onProfileConverted*(self: AccessInterface, success: bool) {.base.} = @@ -171,15 +172,15 @@ method onFetchingFromWakuMessageReceived*(self: AccessInterface, backedUpMsgCloc totalMessages: int, loadedMessages: int) {.base.} = raise newException(ValueError, "No implementation available") +method prepareAndInitFetchingData*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method finishAppLoading*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") method checkFetchingStatusAndProceed*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method startAppAfterDelay*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - method getConnectionString*(self: AccessInterface): string {.base} = raise newException(ValueError, "No implementation available") @@ -215,6 +216,21 @@ method insertMockedKeycardAction*(self: AccessInterface, cardIndex: int) {.base. method removeMockedKeycardAction*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method notificationsNeedsEnable*(self: AccessInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method getLoggedInAccountPublicKey*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method getLoggedInAccountDisplayName*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method getLoggedInAccountImage*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method notifyLoggedInAccountChanged*(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/startup/module.nim b/src/app/modules/startup/module.nim index c344952737..cd1811e7a4 100644 --- a/src/app/modules/startup/module.nim +++ b/src/app/modules/startup/module.nim @@ -21,6 +21,7 @@ import app_service/service/general/service as general_service import app_service/service/profile/service as profile_service import app_service/service/keycard/service as keycard_service import app_service/service/devices/service as devices_service +from app_service/service/settings/dto/settings import SettingsDto import app/modules/shared_modules/keycard_popup/module as keycard_shared_module @@ -124,11 +125,8 @@ method load*[T](self: Module[T]) = accounts.add(gen_acc_item.initItem(acc.id, acc.alias, acc.address, acc.derivedAccounts.whisper.publicKey, acc.keyUid)) self.view.setGeneratedAccountList(accounts) - if(self.controller.shouldStartWithOnboardingScreen()): - if main_constants.IS_MACOS: - self.view.setCurrentStartupState(newNotificationState(state.FlowType.General, nil)) - else: - self.view.setCurrentStartupState(newWelcomeState(state.FlowType.General, nil)) + if self.controller.shouldStartWithOnboardingScreen(): + self.view.setCurrentStartupState(newWelcomeState(state.FlowType.General, nil)) else: let openedAccounts = self.controller.getOpenedAccounts() var items: seq[login_acc_item.Item] @@ -349,7 +347,8 @@ method checkFetchingStatusAndProceed*[T](self: Module[T]) = if currStateObj.isNil: error "cannot resolve current state in order to resolve next state" return - self.view.setCurrentStartupState(newProfileFetchingAnnouncementState(currStateObj.flowType(), nil)) + let nextState = newProfileFetchingAnnouncementState(currStateObj.flowType(), nil) + self.view.setCurrentStartupState(nextState) method onFetchingFromWakuMessageReceived*[T](self: Module[T], backedUpMsgClock: uint64, section: string, totalMessages: int, receivedMessageAtPosition: int) = @@ -374,26 +373,10 @@ method onFetchingFromWakuMessageReceived*[T](self: Module[T], backedUpMsgClock: if self.view.fetchingDataModel().allMessagesLoaded(): self.view.setCurrentStartupState(newProfileFetchingSuccessState(currStateObj.flowType(), nil)) -proc prepareAndInitFetchingData[T](self: Module[T]) = +method prepareAndInitFetchingData*[T](self: Module[T]) = # fetching data from waku starts when messenger starts self.view.createAndInitFetchingDataModel(listOfEntitiesWeExpectToBeSynced) -proc delayStartingApp[T](self: Module[T]) = - ## In the following 2 cases: - ## - FlowType.FirstRunOldUserImportSeedPhrase - ## - FlowType.FirstRunOldUserKeycardImport - ## we want to delay app start just to be sure that messages from waku will be received - self.controller.connectToTimeoutEventAndStratTimer(timeoutInMilliseconds = 30000) # delay for 30 seconds - -method startAppAfterDelay*[T](self: Module[T]) = - if not self.view.fetchingDataModel().allMessagesLoaded(): - let currStateObj = self.view.currentStartupStateObj() - if currStateObj.isNil: - error "cannot determine current startup state", procName="startAppAfterDelay" - quit() # quit the app - self.view.setCurrentStartupState(newProfileFetchingState(currStateObj.flowType(), nil)) - self.moveToStartupState() - proc logoutAndDisplayError[T](self: Module[T], error: string, errType: StartupErrorType) = self.delegate.logout() if self.controller.isSelectedLoginAccountKeycardAccount() and @@ -418,35 +401,13 @@ method onProfileConverted*[T](self: Module[T], success: bool) = self.moveToStartupState() self.view.setCurrentStartupState(newLoginKeycardConvertedToRegularAccountState(currStateObj.flowType(), nil)) -method onNodeLogin*[T](self: Module[T], error: string) = +method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) = let currStateObj = self.view.currentStartupStateObj() if currStateObj.isNil: error "cannot determine current startup state", procName="onNodeLogin" quit() # quit the app - if error.len == 0: - if currStateObj.flowType() == state.FlowType.FirstRunOldUserImportSeedPhrase or - currStateObj.flowType() == state.FlowType.FirstRunOldUserKeycardImport: - self.prepareAndInitFetchingData() - self.controller.connectToFetchingFromWakuEvents() - self.delayStartingApp() - let err = self.delegate.userLoggedIn() - if err.len > 0: - self.logoutAndDisplayError(err, StartupErrorType.UnknownType) - return - elif currStateObj.flowType() == state.FlowType.LostKeycardConvertToRegularAccount: - self.controller.convertKeycardProfileKeypairToRegular() - return - else: - let err = self.delegate.userLoggedIn() - if err.len > 0: - self.logoutAndDisplayError(err, StartupErrorType.UnknownType) - return - if currStateObj.flowType() != FlowType.AppLogin: - let images = self.controller.storeIdentityImage() - self.accountsService.updateLoggedInAccount(self.getDisplayName, images) - self.finishAppLoading() - else: + if error.len != 0: self.moveToStartupState() if currStateObj.flowType() == state.FlowType.AppLogin: if self.controller.isSelectedLoginAccountKeycardAccount(): @@ -456,6 +417,51 @@ method onNodeLogin*[T](self: Module[T], error: string) = else: self.emitStartupError(error, StartupErrorType.SetupAccError) error "login error", methodName="onNodeLogin", errDesription =error + return + + self.controller.setLoggedInAccount(account) + self.accountsService.updateLoggedInAccount(account.name, account.images) + self.controller.setLoggedInProfile(settings.publicKey) + self.view.notifyLoggedInAccountChanged() + + if currStateObj.flowType() == state.FlowType.FirstRunOldUserImportSeedPhrase or + currStateObj.flowType() == state.FlowType.FirstRunOldUserKeycardImport: + let err = self.delegate.userLoggedIn() + if err.len > 0: + self.logoutAndDisplayError(err, StartupErrorType.UnknownType) + return + return + + if currStateObj.flowType() == state.FlowType.LostKeycardConvertToRegularAccount: + self.controller.convertKeycardProfileKeypairToRegular() + return + + let err = self.delegate.userLoggedIn() + if err.len > 0: + self.logoutAndDisplayError(err, StartupErrorType.UnknownType) + return + + if currStateObj.flowType() == FlowType.AppLogin: + self.finishAppLoading() + return + + # TODO: Remove this block when implemented https://github.com/status-im/status-go/issues/4977 + if currStateObj.flowType() == FlowType.FirstRunNewUserNewKeycardKeys or + currStateObj.flowType() == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard: + let images = self.controller.storeIdentityImage() + self.accountsService.updateLoggedInAccount(self.getDisplayName, images) + self.view.notifyLoggedInAccountChanged() + + var nextState: state.State + if currStateObj.flowType() == FlowType.LostKeycardReplacement: + if not self.controller.notificationsNeedsEnable(): + self.finishAppLoading() + return + nextState = newNotificationState(currStateObj.flowType(), nil) + else: + nextState = newUserProfileChatKeyState(currStateObj.flowType(), nil) + self.view.setCurrentStartupState(nextState) + self.moveToStartupState() method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEvent: KeycardEvent) = if self.isSharedKeycardModuleFlowRunning(): @@ -585,4 +591,19 @@ method insertMockedKeycardAction*[T](self: Module[T], cardIndex: int) = method removeMockedKeycardAction*[T](self: Module[T]) = self.keycardService.removeMockedKeycardAction() +method notificationsNeedsEnable*[T](self: Module[T]): bool = + return self.controller.notificationsNeedsEnable() + +method getLoggedInAccountPublicKey*[T](self: Module[T]): string = + return self.controller.getLoggedInAccountPublicKey() + +method getLoggedInAccountDisplayName*[T](self: Module[T]): string = + return self.controller.getLoggedInAccountDisplayName() + +method getLoggedInAccountImage*[T](self: Module[T]): string = + return self.controller.getLoggedInAccountImage() + +method notifyLoggedInAccountChanged*[T](self: Module[T]) = + self.view.notifyLoggedInAccountChanged() + {.pop.} diff --git a/src/app/modules/startup/view.nim b/src/app/modules/startup/view.nim index 3c7e825a23..051c063821 100644 --- a/src/app/modules/startup/view.nim +++ b/src/app/modules/startup/view.nim @@ -1,4 +1,4 @@ -import NimQml +import NimQml, chronicles import io_interface import selected_login_account import internal/[state, state_wrapper] @@ -127,6 +127,7 @@ QtObject: proc setAppState*(self: View, state: AppState) = if(self.appState == state): return + debug "app state changed ", state self.appState = state self.appStateChanged(self.appState.int) QtProperty[int] appState: @@ -206,10 +207,8 @@ QtObject: proc validMnemonic*(self: View, mnemonic: string): bool {.slot.} = return self.delegate.validMnemonic(mnemonic) - proc accountImportSuccess*(self: View) {.signal.} proc importAccountSuccess*(self: View) = self.importedAccountChanged() - self.accountImportSuccess() proc selectedLoginAccountChanged*(self: View) {.signal.} proc getSelectedLoginAccount(self: View): QVariant {.slot.} = @@ -389,4 +388,31 @@ QtObject: self.delegate.insertMockedKeycardAction(cardIndex) proc removeMockedKeycardAction*(self: View) {.slot.} = - self.delegate.removeMockedKeycardAction() \ No newline at end of file + self.delegate.removeMockedKeycardAction() + + proc getNotificationsNeedsEnable*(self: View): bool {.slot.} = + return self.delegate.notificationsNeedsEnable() + QtProperty[bool] notificationsNeedsEnable: + read = getNotificationsNeedsEnable + + proc getLoggedInAccountPublicKey*(self: View): string {.slot.} = + return self.delegate.getLoggedInAccountPublicKey() + proc loggedInAccountChanged*(self: View) {.signal.} + QtProperty[string] loggedInAccountPublicKey: + read = getLoggedInAccountPublicKey + notify = loggedInAccountChanged + + proc getLoggedInAccountDisplayName*(self: View): string {.slot.} = + return self.delegate.getLoggedInAccountDisplayName() + QtProperty[string] loggedInAccountDisplayName: + read = getLoggedInAccountDisplayName + notify = loggedInAccountChanged + + proc getLoggedInAccountImage*(self: View): string {.slot.} = + return self.delegate.getLoggedInAccountImage() + QtProperty[string] loggedInAccountImage: + read = getLoggedInAccountImage + notify = loggedInAccountChanged + + proc notifyLoggedInAccountChanged*(self: View) = + self.loggedInAccountChanged() diff --git a/src/app_service/service/accounts/dto/accounts.nim b/src/app_service/service/accounts/dto/accounts.nim index a26448e4b0..f9fecf4b8c 100644 --- a/src/app_service/service/accounts/dto/accounts.nim +++ b/src/app_service/service/accounts/dto/accounts.nim @@ -77,4 +77,4 @@ proc toWakuBackedUpProfileDto*(jsonObj: JsonNode): WakuBackedUpProfileDto = result.images.add(toImage(imgObj)) if(jsonObj.getProp("socialLinks", obj) and obj.kind == JArray): - result.socialLinks = toSocialLinks(obj) \ No newline at end of file + result.socialLinks = toSocialLinks(obj) diff --git a/src/app_service/service/accounts/dto/create_account_request.nim b/src/app_service/service/accounts/dto/create_account_request.nim new file mode 100644 index 0000000000..995d031c72 --- /dev/null +++ b/src/app_service/service/accounts/dto/create_account_request.nim @@ -0,0 +1,88 @@ +import json, std/options +import ./wallet_secretes_config +import ./image_crop_rectangle + +export wallet_secretes_config +export image_crop_rectangle + +type + CreateAccountRequest* = object + backupDisabledDataDir*: string + kdfIterations*: int + deviceName*: string + displayName*: string + password*: string + imagePath*: string + imageCropRectangle*: ImageCropRectangle + customizationColor*: string + emoji*: string + + wakuV2Nameserver*: Option[string] + wakuV2LightClient*: bool + + logLevel*: Option[string] + logFilePath*: string + logEnabled*: bool + + previewPrivacy*: bool + + verifyTransactionURL*: Option[string] + verifyENSURL*: Option[string] + verifyENSContractAddress*: Option[string] + verifyTransactionChainID*: Option[int64] + upstreamConfig*: string + networkID*: Option[uint64] + + walletSecretsConfig*: WalletSecretsConfig + + torrentConfigEnabled*: Option[bool] + torrentConfigPort*: Option[int] + +proc toJson*(self: CreateAccountRequest): JsonNode = + result = %*{ + "backupDisabledDataDir": self.backupDisabledDataDir, + "kdfIterations": self.kdfIterations, + "deviceName": self.deviceName, + "displayName": self.displayName, + "password": self.password, + "imagePath": self.imagePath, + "imageCropRectangle": self.imageCropRectangle, + "customizationColor": self.customizationColor, + "emoji": self.emoji, + "wakuV2LightClient": self.wakuV2LightClient, + "logFilePath": self.logFilePath, + "logEnabled": self.logEnabled, + "previewPrivacy": self.previewPrivacy, + "upstreamConfig": self.upstreamConfig, + } + + if self.logLevel.isSome(): + result["logLevel"] = %self.logLevel.get("") + + if self.wakuV2Nameserver.isSome(): + result["wakuV2Nameserver"] = %self.wakuV2Nameserver.get() + + if self.verifyTransactionURL.isSome(): + result["verifyTransactionURL"] = %self.verifyTransactionURL.get() + + if self.verifyENSURL.isSome(): + result["verifyENSURL"] = %self.verifyENSURL.get() + + if self.verifyENSContractAddress.isSome(): + result["verifyENSContractAddress"] = %self.verifyENSContractAddress.get() + + if self.verifyTransactionChainID.isSome(): + result["verifyTransactionChainID"] = %self.verifyTransactionChainID.get() + + if self.networkID.isSome(): + result["networkID"] = %self.networkID.get() + + if self.torrentConfigEnabled.isSome(): + result["torrentConfigEnabled"] = %self.torrentConfigEnabled.get() + + if self.torrentConfigPort.isSome(): + result["torrentConfigPort"] = %self.torrentConfigPort.get() + + for key, value in self.walletSecretsConfig.toJson().pairs(): + result[key] = value + diff --git a/src/app_service/service/accounts/dto/image_crop_rectangle.nim b/src/app_service/service/accounts/dto/image_crop_rectangle.nim new file mode 100644 index 0000000000..9880c03c3a --- /dev/null +++ b/src/app_service/service/accounts/dto/image_crop_rectangle.nim @@ -0,0 +1,8 @@ +type + ImageCropRectangle* = object + ax*: int + ay*: int + bx*: int + by*: int + +# Default `toJson` works fine. diff --git a/src/app_service/service/accounts/dto/login_request.nim b/src/app_service/service/accounts/dto/login_request.nim new file mode 100644 index 0000000000..eb8cb20901 --- /dev/null +++ b/src/app_service/service/accounts/dto/login_request.nim @@ -0,0 +1,22 @@ +import json +import ./wallet_secretes_config + +type + LoginAccountRequest* = object + passwordHash*: string + keyUID*: string + kdfIterations*: int + runtimeLogLevel*: string + wakuV2Nameserver*: string + walletSecretsConfig*: WalletSecretsConfig + +proc toJson*(self: LoginAccountRequest): JsonNode = + result = %* { + "password": self.passwordHash, + "keyUid": self.keyUID, + "kdfIterations": self.kdfIterations, + "runtimeLogLevel": self.runtimeLogLevel, + "wakuV2Nameserver": self.wakuV2Nameserver + } + for key, value in self.walletSecretsConfig.toJson().pairs(): + result[key] = value diff --git a/src/app_service/service/accounts/dto/restore_account_request.nim b/src/app_service/service/accounts/dto/restore_account_request.nim new file mode 100644 index 0000000000..2cccd836c3 --- /dev/null +++ b/src/app_service/service/accounts/dto/restore_account_request.nim @@ -0,0 +1,18 @@ +import json +import create_account_request + +type + RestoreAccountRequest* = object + mnemonic*: string + fetchBackup*: bool + createAccountRequest*: CreateAccountRequest + +proc toJson*(self: RestoreAccountRequest): JsonNode = + result = %*{ + "mnemonic": self.mnemonic, + "fetchBackup": self.fetchBackup + } + + for key, value in self.createAccountRequest.toJson().pairs(): + result[key] = value + diff --git a/src/app_service/service/accounts/dto/wallet_secretes_config.nim b/src/app_service/service/accounts/dto/wallet_secretes_config.nim new file mode 100644 index 0000000000..3b42b0fedc --- /dev/null +++ b/src/app_service/service/accounts/dto/wallet_secretes_config.nim @@ -0,0 +1,38 @@ +import json + +type + WalletSecretsConfig* = object + poktToken*: string + infuraToken*: string + infuraSecret*: string + openseaApiKey*: string + raribleMainnetApiKey*: string + raribleTestnetApiKey*: string + alchemyEthereumMainnetToken*: string + alchemyEthereumGoerliToken*: string + alchemyEthereumSepoliaToken*: string + alchemyArbitrumMainnetToken*: string + alchemyArbitrumGoerliToken*: string + alchemyArbitrumSepoliaToken*: string + alchemyOptimismMainnetToken*: string + alchemyOptimismGoerliToken*: string + alchemyOptimismSepoliaToken*: string + +proc toJson*(self: WalletSecretsConfig): JsonNode = + return %* { + "poktToken": self.poktToken, + "infuraToken": self.infuraToken, + "infuraSecret": self.infuraSecret, + "openseaApiKey": self.openseaApiKey, + "raribleMainnetApiKey": self.raribleMainnetApiKey, + "raribleTestnetApiKey": self.raribleTestnetApiKey, + "alchemyEthereumMainnetToken": self.alchemyEthereumMainnetToken, + "alchemyEthereumGoerliToken": self.alchemyEthereumGoerliToken, + "alchemyEthereumSepoliaToken": self.alchemyEthereumSepoliaToken, + "alchemyArbitrumMainnetToken": self.alchemyArbitrumMainnetToken, + "alchemyArbitrumGoerliToken": self.alchemyArbitrumGoerliToken, + "alchemyArbitrumSepoliaToken": self.alchemyArbitrumSepoliaToken, + "alchemyOptimismMainnetToken": self.alchemyOptimismMainnetToken, + "alchemyOptimismGoerliToken": self.alchemyOptimismGoerliToken, + "alchemyOptimismSepoliaToken": self.alchemyOptimismSepoliaToken + } diff --git a/src/app_service/service/accounts/service.nim b/src/app_service/service/accounts/service.nim index 6059d8e514..34aeb3494d 100644 --- a/src/app_service/service/accounts/service.nim +++ b/src/app_service/service/accounts/service.nim @@ -1,9 +1,13 @@ -import NimQml, Tables, os, json, stew/shims/strformat, sequtils, strutils, uuids, times +import NimQml, Tables, os, json, stew/shims/strformat, sequtils, strutils, uuids, times, std/options import json_serialization, chronicles import ../../../app/global/global_singleton import ./dto/accounts as dto_accounts import ./dto/generated_accounts as dto_generated_accounts +import ./dto/login_request +import ./dto/create_account_request +import ./dto/restore_account_request + from ../keycard/service import KeycardEvent, KeyDetails import ../../../backend/general as status_general import ../../../backend/core as status_core @@ -29,7 +33,7 @@ const DEFAULT_WALLET_ACCOUNT_NAME = "Account 1" const PATHS = @[PATH_WALLET_ROOT, PATH_EIP_1581, PATH_WHISPER, PATH_DEFAULT_WALLET, PATH_ENCRYPTION] const ACCOUNT_ALREADY_EXISTS_ERROR* = "account already exists" const KDF_ITERATIONS* {.intdefine.} = 256_000 -const DEFAULT_COLORID_FOR_DEFAULT_WALLET_ACCOUNT = "primary" # to match `CustomizationColor` on the go side +const DEFAULT_CUSTOMIZATION_COLOR = "primary" # to match `CustomizationColor` on the go side # allow runtime override via environment variable. core contributors can set a # specific peer to set for testing messaging and mailserver functionality with squish. @@ -66,8 +70,6 @@ QtObject: defaultWalletEmoji: string tmpAccount: AccountDto tmpHashedPassword: string - tmpThumbnailImage: string - tmpLargeImage: string proc delete*(self: Service) = self.QObject.delete @@ -81,9 +83,17 @@ QtObject: result.keyStoreDir = main_constants.ROOTKEYSTOREDIR result.defaultWalletEmoji = "" + proc setLocalAccountSettingsFile(self: Service) = + if self.loggedInAccount.isValid(): + singletonInstance.localAccountSettings.setFileName(self.loggedInAccount.name) + proc getLoggedInAccount*(self: Service): AccountDto = return self.loggedInAccount + proc setLoggedInAccount*(self: Service, account: AccountDto) = + self.loggedInAccount = account + self.setLocalAccountSettingsFile() + proc updateLoggedInAccount*(self: Service, displayName: string, images: seq[Image]) = self.loggedInAccount.name = displayName self.loggedInAccount.images = images @@ -125,12 +135,12 @@ QtObject: self.loggedInAccount = AccountDto() self.importedAccount = GeneratedAccountDto() - proc validateMnemonic*(self: Service, mnemonic: string): string = + proc validateMnemonic*(self: Service, mnemonic: string): (string, string) = try: let response = status_general.validateMnemonic(mnemonic) if response.result.contains("error"): - return response.result["error"].getStr - return "" + return ("", response.result["error"].getStr) + return (response.result["keyUID"].getStr, "") except Exception as e: error "error: ", procName="validateMnemonic", errName = e.name, errDesription = e.msg @@ -153,47 +163,7 @@ QtObject: error "error: ", procName="openedAccounts", errName = e.name, errDesription = e.msg proc openedAccountsContainsKeyUid*(self: Service, keyUid: string): bool = - let openedAccounts = self.openedAccounts() - for acc in openedAccounts: - if acc.keyUid == keyUid: - return true - return false - - proc storeDerivedAccounts(self: Service, accountId, hashedPassword: string, - paths: seq[string]): DerivedAccounts = - let response = status_account.storeDerivedAccounts(accountId, hashedPassword, paths) - - if response.result.contains("error"): - raise newException(Exception, response.result["error"].getStr) - - result = toDerivedAccounts(response.result) - - proc storeAccount(self: Service, accountId, hashedPassword: string): GeneratedAccountDto = - let response = status_account.storeAccounts(accountId, hashedPassword) - - if response.result.contains("error"): - raise newException(Exception, response.result["error"].getStr) - - result = toGeneratedAccountDto(response.result) - - proc saveAccountAndLogin(self: Service, hashedPassword: string, account, - subaccounts, settings, config: JsonNode): AccountDto = - try: - let response = status_account.saveAccountAndLogin(hashedPassword, account, subaccounts, settings, config) - - var error = "response doesn't contain \"error\"" - if(response.result.contains("error")): - error = response.result["error"].getStr - if error == "": - debug "Account saved succesfully" - result = toAccountDto(account) - return - - let err = "Error saving account and logging in: " & error - error "error: ", procName="saveAccountAndLogin", errDesription = err - - except Exception as e: - error "error: ", procName="saveAccountAndLogin", errName = e.name, errDesription = e.msg + return (keyUID in self.openedAccounts().mapIt(it.keyUid)) proc saveKeycardAccountAndLogin(self: Service, chatKey, password: string, account, subaccounts, settings, config: JsonNode): AccountDto = @@ -214,31 +184,13 @@ QtObject: except Exception as e: error "error: ", procName="saveKeycardAccountAndLogin", errName = e.name, errDesription = e.msg - proc prepareAccountJsonObject(self: Service, account: GeneratedAccountDto, displayName: string): JsonNode = - result = %* { - "name": if displayName == "": account.alias else: displayName, - "address": account.address, - "key-uid": account.keyUid, - "keycard-pairing": nil, - "kdfIterations": KDF_ITERATIONS, - } - - proc getAccountDataForAccountId(self: Service, accountId: string, displayName: string): JsonNode = - for acc in self.generatedAccounts: - if(acc.id == accountId): - return self.prepareAccountJsonObject(acc, displayName) - - if(self.importedAccount.isValid()): - if(self.importedAccount.id == accountId): - return self.prepareAccountJsonObject(self.importedAccount, displayName) - proc prepareSubaccountJsonObject(self: Service, account: GeneratedAccountDto, displayName: string): JsonNode = result = %* [ { "public-key": account.derivedAccounts.defaultWallet.publicKey, "address": account.derivedAccounts.defaultWallet.address, - "colorId": DEFAULT_COLORID_FOR_DEFAULT_WALLET_ACCOUNT, + "colorId": DEFAULT_CUSTOMIZATION_COLOR, "wallet": true, "path": PATH_DEFAULT_WALLET, "name": DEFAULT_WALLET_ACCOUNT_NAME, @@ -312,6 +264,7 @@ QtObject: if(self.importedAccount.id == accountId): return self.prepareAccountSettingsJsonObject(self.importedAccount, installationId, displayName, withoutMnemonic) + # TODO: Remove after https://github.com/status-im/status-go/issues/4977 proc getDefaultNodeConfig*(self: Service, installationId: string, recoverAccount: bool): JsonNode = let fleet = Fleet.ShardsTest let dnsDiscoveryURL = "enrtree://AMOJVZX4V6EXP7NTJPMAYJYST2QP6AJXYW76IU6VGJS7UVSNDYZG4@boot.test.shards.nodes.status.im" @@ -352,6 +305,7 @@ QtObject: result["KeycardPairingDataFile"] = newJString(main_constants.KEYCARDPAIRINGDATAFILE) result["ProcessBackedupMessages"] = newJBool(recoverAccount) + # TODO: Remove after https://github.com/status-im/status-go/issues/4977 proc getLoginNodeConfig(self: Service): JsonNode = # To create appropriate NodeConfig for Login we set only params that maybe be set via env variables or cli flags result = %*{} @@ -394,10 +348,6 @@ QtObject: if STATUS_PORT != 0: result["ListenAddr"] = newJString("0.0.0.0:" & $main_constants.STATUS_PORT) - proc setLocalAccountSettingsFile(self: Service) = - if self.getLoggedInAccount.isValid(): - singletonInstance.localAccountSettings.setFileName(self.getLoggedInAccount.name) - proc addKeycardDetails(self: Service, kcInstance: string, settingsJson: var JsonNode, accountData: var JsonNode) = let keycardPairingJsonString = readFile(main_constants.KEYCARDPAIRINGDATAFILE) let keycardPairingJsonObj = keycardPairingJsonString.parseJSON @@ -412,38 +362,87 @@ QtObject: if not accountData.isNil: accountData["keycard-pairing"] = kcDataObj{"key"} - proc setupAccount*(self: Service, accountId, password, displayName: string, removeMnemonic: bool, recoverAccount: bool = false): string = + proc buildWalletSecrets(self: Service): WalletSecretsConfig = + return WalletSecretsConfig( + poktToken: POKT_TOKEN_RESOLVED, + infuraToken: INFURA_TOKEN_RESOLVED, + infuraSecret: INFURA_TOKEN_SECRET_RESOLVED, + openseaApiKey: OPENSEA_API_KEY_RESOLVED, + raribleMainnetApiKey: RARIBLE_MAINNET_API_KEY_RESOLVED, + raribleTestnetApiKey: RARIBLE_TESTNET_API_KEY_RESOLVED, + alchemyEthereumMainnetToken: ALCHEMY_ETHEREUM_MAINNET_TOKEN_RESOLVED, + alchemyEthereumGoerliToken: ALCHEMY_ETHEREUM_GOERLI_TOKEN_RESOLVED, + alchemyEthereumSepoliaToken: ALCHEMY_ETHEREUM_SEPOLIA_TOKEN_RESOLVED, + alchemyArbitrumMainnetToken: ALCHEMY_ARBITRUM_MAINNET_TOKEN_RESOLVED, + alchemyArbitrumGoerliToken: ALCHEMY_ARBITRUM_GOERLI_TOKEN_RESOLVED, + alchemyArbitrumSepoliaToken: ALCHEMY_ARBITRUM_SEPOLIA_TOKEN_RESOLVED, + alchemyOptimismMainnetToken: ALCHEMY_OPTIMISM_MAINNET_TOKEN_RESOLVED, + alchemyOptimismGoerliToken: ALCHEMY_OPTIMISM_GOERLI_TOKEN_RESOLVED, + alchemyOptimismSepoliaToken: ALCHEMY_OPTIMISM_SEPOLIA_TOKEN_RESOLVED, + ) + + proc buildCreateAccountRequest(self: Service, password: string, displayName: string, imagePath: string, imageCropRectangle: ImageCropRectangle): CreateAccountRequest = + return CreateAccountRequest( + backupDisabledDataDir: main_constants.STATUSGODIR, + kdfIterations: KDF_ITERATIONS, + password: hashPassword(password), + displayName: displayName, + imagePath: imagePath, + imageCropRectangle: imageCropRectangle, + customizationColor: DEFAULT_CUSTOMIZATION_COLOR, + emoji: self.defaultWalletEmoji, + logLevel: some(toStatusGoSupportedLogLevel(main_constants.LOG_LEVEL)), + wakuV2LightClient: false, + previewPrivacy: true, + torrentConfigEnabled: some(true), + torrentConfigPort: some(TORRENT_CONFIG_PORT), + walletSecretsConfig: self.buildWalletSecrets(), + ) + + proc createAccountAndLogin*(self: Service, password: string, displayName: string, imagePath: string, imageCropRectangle: ImageCropRectangle): string = try: - let installationId = $genUUID() - var accountDataJson = self.getAccountDataForAccountId(accountId, displayName) - self.setKeyStoreDir(accountDataJson{"key-uid"}.getStr) # must be called before `getDefaultNodeConfig` - let subaccountDataJson = self.getSubaccountDataForAccountId(accountId, displayName) - var settingsJson = self.getAccountSettings(accountId, installationId, displayName, removeMnemonic) - let nodeConfigJson = self.getDefaultNodeConfig(installationId, recoverAccount) + let request = self.buildCreateAccountRequest(password, displayName, imagePath, imageCropRectangle) + let response = status_account.createAccountAndLogin(request) + + if not response.result.contains("error"): + error "invalid status-go response", response + return "invalid response: no error field found" - if(accountDataJson.isNil or subaccountDataJson.isNil or settingsJson.isNil or - nodeConfigJson.isNil): - let description = "at least one json object is not prepared well" - error "error: ", procName="setupAccount", errDesription = description - return description - - let hashedPassword = hashPassword(password) - discard self.storeAccount(accountId, hashedPassword) - discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS) - self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, - accountDataJson, - subaccountDataJson, - settingsJson, - nodeConfigJson) - - self.setLocalAccountSettingsFile() - - if self.getLoggedInAccount.isValid(): + let error = response.result["error"].getStr + if error == "": + debug "Account saved succesfully" return "" - else: - return "logged in account is not valid" + + error "createAccountAndLogin status-go error: ", error + return "createAccountAndLogin failed: " & error + except Exception as e: - error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg + error "failed to create account or login", procName="createAccountAndLogin", errName = e.name, errDesription = e.msg + return e.msg + + proc importAccountAndLogin*(self: Service, mnemonic: string, password: string, recoverAccount: bool, displayName: string, imagePath: string, imageCropRectangle: ImageCropRectangle): string = + try: + let request = RestoreAccountRequest( + mnemonic: mnemonic, + fetchBackup: recoverAccount, + createAccountRequest: self.buildCreateAccountRequest(password, displayName, imagePath, imageCropRectangle), + ) + let response = status_account.restoreAccountAndLogin(request) + + if not response.result.contains("error"): + error "invalid status-go response", response + return "invalid response: no error field found" + + let error = response.result["error"].getStr + if error == "": + debug "Account saved succesfully" + return "" + + error "importAccountAndLogin status-go error: ", error + return "importAccountAndLogin failed: " & error + + except Exception as e: + error "failed to import account or login", procName="importAccountAndLogin", errName = e.name, errDesription = e.msg return e.msg proc setupAccountKeycard*(self: Service, keycardData: KeycardEvent, displayName: string, useImportedAcc: bool, @@ -491,7 +490,7 @@ QtObject: { "public-key": walletPublicKey, "address": walletAddress, - "colorId": DEFAULT_COLORID_FOR_DEFAULT_WALLET_ACCOUNT, + "colorId": DEFAULT_CUSTOMIZATION_COLOR, "wallet": true, "path": PATH_DEFAULT_WALLET, "name": DEFAULT_WALLET_ACCOUNT_NAME, @@ -658,35 +657,28 @@ QtObject: except Exception as e: error "error: ", procName="verifyDatabasePassword", errName = e.name, errDesription = e.msg - proc doLogin(self: Service, account: AccountDto, hashedPassword, thumbnailImage, largeImage: string) = - let nodeConfigJson = self.getLoginNodeConfig() - let response = status_account.login( - account.name, - account.keyUid, - account.kdfIterations, - hashedPassword, - thumbnailImage, - largeImage, - $nodeConfigJson + proc doLogin(self: Service, account: AccountDto, passwordHash: string) = + var request = LoginAccountRequest( + keyUid: account.keyUid, + kdfIterations: account.kdfIterations, + passwordHash: passwordHash, + walletSecretsConfig: self.buildWalletSecrets(), ) + + if main_constants.runtimeLogLevelSet(): + request.runtimeLogLevel = toStatusGoSupportedLogLevel(main_constants.LOG_LEVEL) + + let response = status_account.loginAccount(request) + if response.result{"error"}.getStr != "": self.events.emit(SIGNAL_LOGIN_ERROR, LoginErrorArgs(error: response.result{"error"}.getStr)) return - debug "Account logged in" - self.loggedInAccount = account + debug "account logged in" self.setLocalAccountSettingsFile() proc login*(self: Service, account: AccountDto, hashedPassword: string) = try: - var thumbnailImage: string - var largeImage: string - for img in account.images: - if(img.imgType == "thumbnail"): - thumbnailImage = img.uri - elif(img.imgType == "large"): - largeImage = img.uri - let keyStoreDir = joinPath(main_constants.ROOTKEYSTOREDIR, account.keyUid) & main_constants.sep if not dirExists(keyStoreDir): os.createDir(keyStoreDir) @@ -698,11 +690,11 @@ QtObject: let isOldHashPassword = self.verifyDatabasePassword(account.keyUid, hashedPasswordToUpperCase(hashedPassword)) if isOldHashPassword: + debug "database reencryption scheduled" + # Save tmp properties so that we can login after the timer self.tmpAccount = account self.tmpHashedPassword = hashedPassword - self.tmpThumbnailImage = thumbnailImage - self.tmpLargeImage = largeImage # Start a 1 second timer for the loading screen to appear let arg = TimerTaskArg( @@ -714,24 +706,25 @@ QtObject: self.threadpool.start(arg) return - self.doLogin(account, hashedPassword, thumbnailImage, largeImage) + self.doLogin(account, hashedPassword) + except Exception as e: - error "error: ", procName="login", errName = e.name, errDesription = e.msg + error "login failed", errName = e.name, errDesription = e.msg self.events.emit(SIGNAL_LOGIN_ERROR, LoginErrorArgs(error: e.msg)) proc onWaitForReencryptionTimeout(self: Service, response: string) {.slot.} = + debug "starting database reencryption" + # Reencryption (can freeze and take up to 30 minutes) let oldHashedPassword = hashedPasswordToUpperCase(self.tmpHashedPassword) discard status_privacy.changeDatabasePassword(self.tmpAccount.keyUid, oldHashedPassword, self.tmpHashedPassword) # Normal login after reencryption - self.doLogin(self.tmpAccount, self.tmpHashedPassword, self.tmpThumbnailImage, self.tmpLargeImage) + self.doLogin(self.tmpAccount, self.tmpHashedPassword) # Clear out the temp properties self.tmpAccount = AccountDto() self.tmpHashedPassword = "" - self.tmpThumbnailImage = "" - self.tmpLargeImage = "" proc loginAccountKeycard*(self: Service, accToBeLoggedIn: AccountDto, keycardData: KeycardEvent): string = try: @@ -758,7 +751,7 @@ QtObject: self.setLocalAccountSettingsFile() return except Exception as e: - error "error: ", procName="loginAccountKeycard", errName = e.name, errDesription = e.msg + error "keycard login failed", procName="loginAccountKeycard", errName = e.name, errDesription = e.msg return e.msg proc convertRegularProfileKeypairToKeycard*(self: Service, keycardUid, currentPassword: string, newPassword: string) = diff --git a/src/backend/accounts.nim b/src/backend/accounts.nim index aaafebaa18..57a1b19904 100644 --- a/src/backend/accounts.nim +++ b/src/backend/accounts.nim @@ -1,6 +1,9 @@ import json, json_serialization, chronicles, strutils import ./core, ../app_service/common/utils import ../app_service/service/wallet_account/dto/account_dto +import ../app_service/service/accounts/dto/login_request +import ../app_service/service/accounts/dto/create_account_request +import ../app_service/service/accounts/dto/restore_account_request import ./response_type import status_go @@ -310,36 +313,6 @@ proc openedAccounts*(path: string): RpcResponse[JsonNode] = error "error doing rpc request", methodName = "openedAccounts", exception=e.msg raise newException(RpcException, e.msg) -proc storeDerivedAccounts*(id, hashedPassword: string, paths: seq[string]): - RpcResponse[JsonNode] = - let payload = %* { - "accountID": id, - "paths": paths, - "password": hashedPassword - } - - try: - let response = status_go.multiAccountStoreDerivedAccounts($payload) - result.result = Json.decode(response, JsonNode) - - except RpcException as e: - error "error doing rpc request", methodName = "storeDerivedAccounts", exception=e.msg - raise newException(RpcException, e.msg) - -proc storeAccounts*(id, hashedPassword: string): RpcResponse[JsonNode] = - let payload = %* { - "accountID": id, - "password": hashedPassword - } - - try: - let response = status_go.multiAccountStoreAccount($payload) - result.result = Json.decode(response, JsonNode) - - except RpcException as e: - error "error doing rpc request", methodName = "storeAccounts", exception=e.msg - raise newException(RpcException, e.msg) - proc addPeer*(peer: string): RpcResponse[JsonNode] = try: let response = status_go.addPeer(peer) @@ -349,15 +322,24 @@ proc addPeer*(peer: string): RpcResponse[JsonNode] = error "error doing rpc request", methodName = "addPeer", exception=e.msg raise newException(RpcException, e.msg) -proc saveAccountAndLogin*(hashedPassword: string, account, subaccounts, settings, - config: JsonNode): RpcResponse[JsonNode] = +proc createAccountAndLogin*(request: CreateAccountRequest): RpcResponse[JsonNode] = try: - let response = status_go.saveAccountAndLogin($account, hashedPassword, - $settings, $config, $subaccounts) + let payload = request.toJson() + let response = status_go.createAccountAndLogin($payload) result.result = Json.decode(response, JsonNode) except RpcException as e: - error "error doing rpc request", methodName = "saveAccountAndLogin", exception=e.msg + error "error doing rpc request", methodName = "createAccountAndLogin", exception=e.msg + raise newException(RpcException, e.msg) + +proc restoreAccountAndLogin*(request: RestoreAccountRequest): RpcResponse[JsonNode] = + try: + let payload = request.toJson() + let response = status_go.restoreAccountAndLogin($payload) + result.result = Json.decode(response, JsonNode) + + except RpcException as e: + error "error doing rpc request", methodName = "restoreAccountAndLogin", exception=e.msg raise newException(RpcException, e.msg) proc saveAccountAndLoginWithKeycard*(chatKey, password: string, account, subaccounts, settings, config: JsonNode): @@ -388,25 +370,14 @@ proc convertKeycardProfileKeypairToRegular*(mnemonic: string, currPassword: stri error "error doing rpc request", methodName = "convertKeycardProfileKeypairToRegular", exception=e.msg raise newException(RpcException, e.msg) -proc login*(name, keyUid: string, kdfIterations: int, hashedPassword, thumbnail, large: string, nodeCfgObj: string): - RpcResponse[JsonNode] - = +proc loginAccount*(request: LoginAccountRequest): RpcResponse[JsonNode] = try: - var payload = %* { - "name": name, - "key-uid": keyUid, - "identityImage": newJNull(), - "kdfIterations": kdfIterations, - } - - if(thumbnail.len>0 and large.len > 0): - payload["identityImage"] = %* {"thumbnail": thumbnail, "large": large} - - let response = status_go.loginWithConfig($payload, hashedPassword, nodeCfgObj) + let payload = request.toJson() + let response = status_go.loginAccount($payload) result.result = Json.decode(response, JsonNode) - + except RpcException as e: - error "error doing rpc request", methodName = "login", exception=e.msg + error "loginAccount failed", exception=e.msg raise newException(RpcException, e.msg) proc loginWithKeycard*(chatKey, password: string, account, confNode: JsonNode): RpcResponse[JsonNode] = @@ -502,4 +473,4 @@ proc getNumOfAddressesToGenerateForKeypair*(keyUID: string): RpcResponse[JsonNod proc resolveSuggestedPathForKeypair*(keyUID: string): RpcResponse[JsonNode] = let payload = %* [keyUID] - result = core.callPrivateRPC("accounts_resolveSuggestedPathForKeypair", payload) \ No newline at end of file + result = core.callPrivateRPC("accounts_resolveSuggestedPathForKeypair", payload) diff --git a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml index 7efea804fb..e855a0d5bc 100644 --- a/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding/OnboardingLayout.qml @@ -51,9 +51,11 @@ OnboardingBasePage { return keysMainViewComponent case Constants.startupState.userProfileCreate: - case Constants.startupState.userProfileChatKey: return insertDetailsViewComponent + case Constants.startupState.userProfileChatKey: + return profileChatKeyViewComponent + case Constants.startupState.userProfileCreatePassword: return createPasswordViewComponent @@ -241,6 +243,13 @@ following the \"Add existing Status user\" flow, using your seed phrase.") } } + Component { + id: profileChatKeyViewComponent + ProfileChatKeyView { + startupStore: root.startupStore + } + } + Component { id: createPasswordViewComponent CreatePasswordView { diff --git a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml index 97f6d3a3bc..e24b060022 100644 --- a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml +++ b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml @@ -20,6 +20,7 @@ QtObject { readonly property string localPairingInstallationId: startupModuleInst ? startupModuleInst.localPairingInstallationId : "" readonly property string localPairingInstallationName: startupModuleInst ? startupModuleInst.localPairingInstallationName : "" readonly property string localPairingInstallationDeviceType: startupModuleInst ? startupModuleInst.localPairingInstallationDeviceType : "" + readonly property bool notificationsNeedsEnable: startupModuleInst ? startupModuleInst.notificationsNeedsEnable : false function backAction() { root.currentStartupState.backAction() diff --git a/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml b/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml index 7b26fe868d..d9ad25c40f 100644 --- a/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml +++ b/ui/app/AppLayouts/Onboarding/views/AllowNotificationsView.qml @@ -68,7 +68,7 @@ Item { leftPadding: Style.current.padding rightPadding: Style.current.padding font.weight: Font.Medium - text: qsTr("Ok, got it") + text: qsTr("Start using Status") onClicked: { root.startupStore.doPrimaryAction() } diff --git a/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml b/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml index 896876e062..6d8d3fcae9 100644 --- a/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml +++ b/ui/app/AppLayouts/Onboarding/views/InsertDetailsView.qml @@ -22,56 +22,23 @@ import "../shared" Item { id: root objectName: "onboardingInsertDetailsView" + property StartupStore startupStore - property string pubKey - property string address - property string displayName - signal createPassword() - Component.onCompleted: { - if (!!root.startupStore.startupModuleInst.importedAccountPubKey) { - root.address = root.startupStore.startupModuleInst.importedAccountAddress ; - root.pubKey = root.startupStore.startupModuleInst.importedAccountPubKey; - } nameInput.text = root.startupStore.getDisplayName(); userImage.asset.name = root.startupStore.getCroppedProfileImage(); - } - - onStateChanged: { - if (state === Constants.startupState.userProfileCreate) { - nameInput.input.edit.forceActiveFocus() - return - } - nextBtn.forceActiveFocus() - } - - Loader { - active: !root.startupStore.startupModuleInst.importedAccountPubKey - sourceComponent: StatusListView { - model: root.startupStore.startupModuleInst.generatedAccountsModel - delegate: Item { - Component.onCompleted: { - if (index === 0) { - root.address = model.address; - root.pubKey = model.pubKey; - } - } - } - } + nameInput.input.edit.forceActiveFocus() } QtObject { id: d function doAction() { - if(!nextBtn.enabled) { + if (!nextBtn.enabled) { return } - if (root.state === Constants.startupState.userProfileCreate) { - root.startupStore.setDisplayName(nameInput.text) - root.displayName = nameInput.text; - } + root.startupStore.setDisplayName(nameInput.text) root.startupStore.doPrimaryAction() } } @@ -109,19 +76,16 @@ Item { Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.topMargin: Style.current.bigPadding StatusSmartIdenticon { - anchors.left: parent.left id: userImage objectName: "welcomeScreenUserProfileImage" + anchors.left: parent.left asset.width: 86 asset.height: 86 asset.letterSize: 32 - asset.color: Utils.colorForPubkey(root.pubKey) + asset.color: Utils.colorForColorId(0) // We haven't generated the keys yet, show default color asset.charactersLen: 2 asset.isImage: !!asset.name asset.imgIsIdenticon: false - ringSettings { - ringSpecModel: Utils.getColorHashAsJson(root.pubKey) - } } StatusRoundButton { id: updatePicButton @@ -165,54 +129,6 @@ Item { } } - StyledText { - id: chatKeyTxt - objectName: "insertDetailsViewChatKeyTxt" - Layout.preferredHeight: 22 - color: Style.current.secondaryText - text: qsTr("Chatkey:") + " " + Utils.getCompressedPk(root.pubKey) - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.topMargin: 13 - font.pixelSize: 15 - } - - Item { - id: chainsChatKeyImg - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.topMargin: Style.current.padding - Layout.preferredWidth: 215 - Layout.preferredHeight: 77 - - Image { - id: imgChains - anchors.horizontalCenter: parent.horizontalCenter - source: Style.svg("onboarding/chains") - cache: false - } - EmojiHash { - anchors { - bottom: parent.bottom - left: parent.left - } - publicKey: root.pubKey - objectName: "publicKeyEmojiHash" - } - StatusSmartIdenticon { - id: userImageCopy - anchors { - bottom: parent.bottom - right: parent.right - rightMargin: 25 - } - asset.width: 44 - asset.height: 44 - asset.color: "transparent" - ringSettings { ringSpecModel: Utils.getColorHashAsJson(root.pubKey) } - } - } - Item { Layout.fillWidth: true Layout.fillHeight: true @@ -252,92 +168,4 @@ Item { } } } - - states: [ - State { - name: Constants.startupState.userProfileCreate - when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreate - PropertyChanges { - target: usernameText - text: qsTr("Your profile") - } - PropertyChanges { - target: txtDesc - text: qsTr("Longer and unusual names are better as they are less likely to be used by someone else.") - } - PropertyChanges { - target: chatKeyTxt - visible: false - } - PropertyChanges { - target: chainsChatKeyImg - opacity: 0.0 - } - PropertyChanges { - target: userImageCopy - opacity: 0.0 - } - PropertyChanges { - target: updatePicButton - opacity: 1.0 - } - PropertyChanges { - target: nameInputItem - enabled: true - visible: true - } - }, - State { - name: Constants.startupState.userProfileChatKey - when: root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileChatKey - PropertyChanges { - target: usernameText - text: qsTr("Your emojihash and identicon ring") - } - PropertyChanges { - target: txtDesc - text: qsTr("This set of emojis and coloured ring around your avatar are unique and represent your chat key, so your friends can easily distinguish you from potential impersonators.") - } - PropertyChanges { - target: chatKeyTxt - visible: true - } - PropertyChanges { - target: chainsChatKeyImg - opacity: 1.0 - } - PropertyChanges { - target: userImageCopy - opacity: 1.0 - } - PropertyChanges { - target: updatePicButton - opacity: 0.0 - } - PropertyChanges { - target: nameInputItem - enabled: false - visible: false - } - } - ] - - transitions: [ - Transition { - from: "*" - to: "*" - SequentialAnimation { - PropertyAction { - target: root - property: "opacity" - value: 0.0 - } - PropertyAction { - target: root - property: "opacity" - value: 1.0 - } - } - } - ] } diff --git a/ui/app/AppLayouts/Onboarding/views/ProfileChatKeyView.qml b/ui/app/AppLayouts/Onboarding/views/ProfileChatKeyView.qml new file mode 100644 index 0000000000..f340a65f14 --- /dev/null +++ b/ui/app/AppLayouts/Onboarding/views/ProfileChatKeyView.qml @@ -0,0 +1,169 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.14 +import QtQuick.Dialogs 1.3 + +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups 0.1 + +import shared.panels 1.0 +import shared 1.0 +import shared.popups 1.0 +import shared.controls 1.0 +import utils 1.0 + +import "../popups" +import "../stores" +import "../shared" + +Item { + id: root + objectName: "onboardingProfileChatKeyView" + + property StartupStore startupStore + + Component.onCompleted: { + nextBtn.forceActiveFocus() + } + + QtObject { + id: d + + readonly property string publicKey: root.startupStore.startupModuleInst.loggedInAccountPublicKey + readonly property string displayName: root.startupStore.startupModuleInst.loggedInAccountDisplayName + readonly property string image: root.startupStore.startupModuleInst.loggedInAccountImage + + function doAction() { + if (!nextBtn.enabled) { + return + } + root.startupStore.doPrimaryAction() + } + } + + ColumnLayout { + height: 461 + anchors.centerIn: parent + + StyledText { + id: usernameText + objectName: "onboardingHeaderText" + text: qsTr("Your emojihash and identicon ring") + font.weight: Font.Bold + font.pixelSize: 22 + Layout.alignment: Qt.AlignHCenter + } + + StyledText { + id: txtDesc + Layout.preferredWidth: root.state === Constants.startupState.userProfileCreate? 338 : 643 + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.topMargin: Style.current.smallPadding + color: Style.current.secondaryText + text: qsTr("This set of emojis and coloured ring around your avatar are unique and represent your chat key, so your friends can easily distinguish you from potential impersonators.") + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + font.pixelSize: 15 + lineHeight: 1.2 + font.letterSpacing: -0.2 + } + + Item { + Layout.preferredWidth: 86 + Layout.preferredHeight: 86 + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.topMargin: Style.current.bigPadding + StatusSmartIdenticon { + id: userImage + objectName: "welcomeScreenUserProfileImage" + anchors.left: parent.left + asset.width: 86 + asset.height: 86 + asset.letterSize: 32 + asset.color: Utils.colorForPubkey(d.publicKey) + asset.charactersLen: 2 + asset.isImage: !!asset.name + asset.imgIsIdenticon: false + asset.name: d.image + name: d.displayName + ringSettings { + ringSpecModel: Utils.getColorHashAsJson(d.publicKey) + } + } + } + + StyledText { + id: chatKeyTxt + objectName: "profileChatKeyViewChatKeyTxt" + Layout.preferredHeight: 22 + color: Style.current.secondaryText + text: qsTr("Chatkey:") + " " + Utils.getCompressedPk(d.publicKey) + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.topMargin: 13 + font.pixelSize: 15 + visible: true + } + + Item { + id: chainsChatKeyImg + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.topMargin: Style.current.padding + Layout.preferredWidth: 215 + Layout.preferredHeight: 77 + + Image { + id: imgChains + anchors.horizontalCenter: parent.horizontalCenter + source: Style.svg("onboarding/chains") + cache: false + } + EmojiHash { + anchors { + bottom: parent.bottom + left: parent.left + } + publicKey: d.publicKey + objectName: "publicKeyEmojiHash" + } + StatusSmartIdenticon { + id: userImageCopy + anchors { + bottom: parent.bottom + right: parent.right + rightMargin: 25 + } + asset.width: 44 + asset.height: 44 + asset.color: "transparent" + ringSettings { ringSpecModel: Utils.getColorHashAsJson(d.publicKey) } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + StatusButton { + id: nextBtn + objectName: "onboardingDetailsViewNextButton" + Layout.alignment: Qt.AlignHCenter + font.weight: Font.Medium + text: root.startupStore.notificationsNeedsEnable ? qsTr("Next") : qsTr("Start using Status") + onClicked: { + d.doAction() + } + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + event.accepted = true + d.doAction() + } + } + } + } +} diff --git a/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml b/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml index 1231988e2e..94bbfa1d9c 100644 --- a/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml +++ b/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml @@ -22,6 +22,11 @@ Item { property int counter: Constants.onboarding.profileFetching.timeout + // NOTE: This block can be removed when fetching backup is optimized + // https://github.com/status-im/status-go/issues/5022 + readonly property bool minimumWaitingTimePassed: (Constants.onboarding.profileFetching.timeout - counter) > 30000 + readonly property bool fetching: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching || !d.minimumWaitingTimePassed + readonly property string fetchingDataText: qsTr("Fetching data...") readonly property string continueText: qsTr("Continue") } @@ -147,12 +152,12 @@ Item { id: button Layout.alignment: Qt.AlignHCenter focus: true - enabled: root.state !== Constants.startupState.profileFetching + enabled: !d.fetching Timer { id: timer interval: 1000 - running: root.state === Constants.startupState.profileFetching + running: d.fetching repeat: true onTriggered: { d.counter = d.counter - 1000 // decrease 1000 ms @@ -176,7 +181,7 @@ Item { states: [ State { name: Constants.startupState.profileFetching - when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching + when: d.fetching PropertyChanges { target: title text: d.fetchingDataText diff --git a/ui/app/AppLayouts/Onboarding/views/SeedPhraseWordsInputView.qml b/ui/app/AppLayouts/Onboarding/views/SeedPhraseWordsInputView.qml index 0d4b86a56e..5ff2369aa1 100644 --- a/ui/app/AppLayouts/Onboarding/views/SeedPhraseWordsInputView.qml +++ b/ui/app/AppLayouts/Onboarding/views/SeedPhraseWordsInputView.qml @@ -174,7 +174,7 @@ Item { StatusButton { Layout.alignment: Qt.AlignHCenter enabled: d.allEntriesValid - text: qsTr("Finish") + text: qsTr("Next") onClicked: { root.startupStore.doPrimaryAction() } diff --git a/vendor/nim-status-go b/vendor/nim-status-go index 294ee4bf49..d38b1147f8 160000 --- a/vendor/nim-status-go +++ b/vendor/nim-status-go @@ -1 +1 @@ -Subproject commit 294ee4bf49df10af5c877cfdc4d36a6688c0f0ca +Subproject commit d38b1147f8b4d0c4abdb1dbf353aff36454e02ed diff --git a/vendor/status-go b/vendor/status-go index 78db9054fc..68bc9d39fd 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 78db9054fc4fac565f5fb6cdd711d22e5870d0a1 +Subproject commit 68bc9d39fd9e42e75448d1536e2825a0fbc3d19a