diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 2a98ccb405..8f755df715 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -1,6 +1,7 @@ import NimQml, os, strformat import ../../app_service/service/local_settings/service as local_settings_service +import ../../app_service/service/keychain/service as keychain_service import ../../app_service/service/accounts/service as accounts_service import ../../app_service/service/contacts/service as contact_service import ../../app_service/service/chat/service as chat_service @@ -14,6 +15,8 @@ import global_singleton # and improved some services, like EventsService which should implement # provider/subscriber principe: import ../../app_service/[main] +import eventemitter +import status/[fleet] ################################################# # At the end of refactoring this will be moved to @@ -49,6 +52,7 @@ type appService: AppService # Services localSettingsService: local_settings_service.Service + keychainService: keychain_service.Service accountsService: accounts_service.Service contactService: contact_service.Service chatService: chat_service.Service @@ -100,13 +104,15 @@ proc newAppController*(appService: AppService): AppController = result.appService = appService # Services result.localSettingsService = local_settings_service.newService() + result.keychainService = keychain_service.newService(result.localSettingsService, appService.status.events) result.accountsService = accounts_service.newService() result.contactService = contact_service.newService() result.chatService = chat_service.newService() result.communityService = community_service.newService(result.chatService) # Modules - result.startupModule = startup_module.newModule[AppController](result, appService, - result.accountsService) + result.startupModule = startup_module.newModule[AppController](result, + appService.status.events, appService.status.fleet, result.localSettingsService, + result.keychainService, result.accountsService) result.mainModule = main_module.newModule[AppController](result, result.chatService, result.communityService) diff --git a/src/app/modules/startup/controller.nim b/src/app/modules/startup/controller.nim index 86c50436ee..81c023599e 100644 --- a/src/app/modules/startup/controller.nim +++ b/src/app/modules/startup/controller.nim @@ -3,10 +3,13 @@ import Tables, chronicles import controller_interface import io_interface -import status/[signals] -import ../../../app_service/[main] +import ../../../app_service/service/local_settings/service as local_settings_service +import ../../../app_service/service/keychain/service as keychain_service import ../../../app_service/service/accounts/service_interface as accounts_service +import eventemitter +import status/[signals] + export controller_interface logScope: @@ -15,35 +18,39 @@ logScope: type Controller* = ref object of controller_interface.AccessInterface delegate: io_interface.AccessInterface - appService: AppService + events: EventEmitter + localSettingsService: local_settings_service.Service + keychainService: keychain_service.Service accountsService: accounts_service.ServiceInterface proc newController*(delegate: io_interface.AccessInterface, - appService: AppService, + events: EventEmitter, + localSettingsService: local_settings_service.Service, + keychainService: keychain_service.Service, accountsService: accounts_service.ServiceInterface): Controller = result = Controller() result.delegate = delegate - result.appService = appService + result.events = events result.accountsService = accountsService method delete*(self: Controller) = discard method init*(self: Controller) = - self.appService.status.events.on(SignalType.NodeLogin.event) do(e:Args): + self.events.on(SignalType.NodeLogin.event) do(e:Args): let signal = NodeSignal(e) if signal.event.error == "": self.delegate.userLoggedIn() else: error "error: ", methodName="init", errDesription = "login error " & signal.event.error - self.appService.status.events.on(SignalType.NodeStopped.event) do(e:Args): + self.events.on(SignalType.NodeStopped.event) do(e:Args): echo "-NEW-EVENT-- NodeStopped: ", repr(e) #self.status.events.emit("nodeStopped", Args()) #self.view.onLoggedOut() - self.appService.status.events.on(SignalType.NodeReady.event) do(e:Args): + self.events.on(SignalType.NodeReady.event) do(e:Args): echo "-NEW-EVENT-- NodeReady: ", repr(e) #self.status.events.emit("nodeReady", Args()) diff --git a/src/app/modules/startup/login/controller.nim b/src/app/modules/startup/login/controller.nim index b44a389ca8..65b4f27068 100644 --- a/src/app/modules/startup/login/controller.nim +++ b/src/app/modules/startup/login/controller.nim @@ -3,33 +3,34 @@ import Tables import controller_interface import io_interface -import status/[signals] -import ../../../../app_service/[main] import ../../../../app_service/service/accounts/service_interface as accounts_service +import eventemitter +import status/[signals] + export controller_interface type Controller* = ref object of controller_interface.AccessInterface delegate: io_interface.AccessInterface - appService: AppService + events: EventEmitter accountsService: accounts_service.ServiceInterface selectedAccountKeyUid: string proc newController*(delegate: io_interface.AccessInterface, - appService: AppService, + events: EventEmitter, accountsService: accounts_service.ServiceInterface): Controller = result = Controller() result.delegate = delegate - result.appService = appService + result.events = events result.accountsService = accountsService method delete*(self: Controller) = discard method init*(self: Controller) = - self.appService.status.events.on(SignalType.NodeLogin.event) do(e:Args): + self.events.on(SignalType.NodeLogin.event) do(e:Args): let signal = NodeSignal(e) if signal.event.error != "": self.delegate.loginAccountError(signal.event.error) diff --git a/src/app/modules/startup/login/module.nim b/src/app/modules/startup/login/module.nim index 362d425e36..af3db64670 100644 --- a/src/app/modules/startup/login/module.nim +++ b/src/app/modules/startup/login/module.nim @@ -4,9 +4,10 @@ import ../io_interface as delegate_interface import view, controller, item import ../../../../app/boot/global_singleton -import ../../../../app_service/[main] import ../../../../app_service/service/accounts/service_interface as accounts_service +import eventemitter + export io_interface type @@ -18,14 +19,14 @@ type moduleLoaded: bool proc newModule*(delegate: delegate_interface.AccessInterface, - appService: AppService, + events: EventEmitter, accountsService: accounts_service.ServiceInterface): Module = result = Module() result.delegate = delegate result.view = view.newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, appService, accountsService) + result.controller = controller.newController(result, events, accountsService) result.moduleLoaded = false singletonInstance.engine.setRootContextProperty("loginModule", result.viewVariant) diff --git a/src/app/modules/startup/module.nim b/src/app/modules/startup/module.nim index 4a0689878e..1efd4ce791 100644 --- a/src/app/modules/startup/module.nim +++ b/src/app/modules/startup/module.nim @@ -7,9 +7,13 @@ import ../../../app/boot/global_singleton import onboarding/module as onboarding_module import login/module as login_module -import ../../../app_service/[main] +import ../../../app_service/service/local_settings/service as local_settings_service +import ../../../app_service/service/keychain/service as keychain_service import ../../../app_service/service/accounts/service_interface as accounts_service +import eventemitter +import status/[fleet] + export io_interface type @@ -22,18 +26,22 @@ type loginModule: login_module.AccessInterface proc newModule*[T](delegate: T, - appService: AppService, + events: EventEmitter, + fleet: FleetModel, + localSettingsService: local_settings_service.Service, + keychainService: keychain_service.Service, accountsService: accounts_service.ServiceInterface): Module[T] = result = Module[T]() result.delegate = delegate result.view = view.newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, appService, accountsService) + result.controller = controller.newController(result, events, localSettingsService, + keychainService, accountsService) # Submodules - result.onboardingModule = onboarding_module.newModule(result, appService, accountsService) - result.loginModule = login_module.newModule(result, appService, accountsService) + result.onboardingModule = onboarding_module.newModule(result, events, fleet, accountsService) + result.loginModule = login_module.newModule(result, events, accountsService) method delete*[T](self: Module[T]) = self.onboardingModule.delete diff --git a/src/app/modules/startup/onboarding/controller.nim b/src/app/modules/startup/onboarding/controller.nim index 521c707b23..145c54db97 100644 --- a/src/app/modules/startup/onboarding/controller.nim +++ b/src/app/modules/startup/onboarding/controller.nim @@ -3,10 +3,11 @@ import Tables, chronicles import controller_interface import io_interface -import status/[signals] -import ../../../../app_service/[main] import ../../../../app_service/service/accounts/service_interface as accounts_service +import eventemitter +import status/[signals, fleet] + export controller_interface logScope: @@ -16,24 +17,27 @@ type Controller* = ref object of controller_interface.AccessInterface delegate: io_interface.AccessInterface - appService: AppService + events: EventEmitter + fleet: FleetModel accountsService: accounts_service.ServiceInterface selectedAccountId: string proc newController*(delegate: io_interface.AccessInterface, - appService: AppService, + events: EventEmitter, + fleet: FleetModel, accountsService: accounts_service.ServiceInterface): Controller = result = Controller() result.delegate = delegate - result.appService = appService + result.events = events + result.fleet = fleet result.accountsService = accountsService method delete*(self: Controller) = discard method init*(self: Controller) = - self.appService.status.events.on(SignalType.NodeLogin.event) do(e:Args): + self.events.on(SignalType.NodeLogin.event) do(e:Args): let signal = NodeSignal(e) if signal.event.error != "": self.delegate.setupAccountError() @@ -49,7 +53,7 @@ method setSelectedAccountByIndex*(self: Controller, index: int) = self.selectedAccountId = accounts[index].id method storeSelectedAccountAndLogin*(self: Controller, password: string) = - if(not self.accountsService.setupAccount(self.appService.status.fleet.config, + if(not self.accountsService.setupAccount(self.fleet.config, self.selectedAccountId, password)): self.delegate.setupAccountError() diff --git a/src/app/modules/startup/onboarding/module.nim b/src/app/modules/startup/onboarding/module.nim index 92507109de..6f5ad72013 100644 --- a/src/app/modules/startup/onboarding/module.nim +++ b/src/app/modules/startup/onboarding/module.nim @@ -4,9 +4,11 @@ import ../io_interface as delegate_interface import view, controller, item import ../../../../app/boot/global_singleton -import ../../../../app_service/[main] import ../../../../app_service/service/accounts/service_interface as accounts_service +import eventemitter +import status/[fleet] + export io_interface type @@ -18,14 +20,16 @@ type moduleLoaded: bool proc newModule*(delegate: delegate_interface.AccessInterface, - appService: AppService, + events: EventEmitter, + fleet: FleetModel, accountsService: accounts_service.ServiceInterface): Module = result = Module() result.delegate = delegate result.view = view.newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, appService, accountsService) + result.controller = controller.newController(result, events, fleet, + accountsService) result.moduleLoaded = false singletonInstance.engine.setRootContextProperty("onboardingModule", result.viewVariant) diff --git a/src/app_service/service/keychain/service.nim b/src/app_service/service/keychain/service.nim new file mode 100644 index 0000000000..6857e9398c --- /dev/null +++ b/src/app_service/service/keychain/service.nim @@ -0,0 +1,88 @@ +import NimQml, chronicles +#Tables, json, sequtils, strutils, strformat, uuids +#import json_serialization, + +import ../local_settings/service as local_settings_service + +import eventemitter + +logScope: + topics = "keychain-service" + +# Local Account Settings keys: +const LS_KEY_STORE_TO_KEYCHAIN* = "storeToKeychain" +# Local Account Settings values: +const LS_VALUE_STORE* = "store" + +const ERROR_TYPE_AUTHENTICATION = "authentication" +const ERROR_TYPE_KEYCHAIN = "keychain" + +type + KeyChainServiceArg* = ref object of Args + data*: string + errCode: int + errType: string + errDescription: string + +QtObject: + type Service* = ref object of QObject + localSettingsService: local_settings_service.Service + events: EventEmitter + keychainManager: StatusKeychainManager + + proc setup(self: Service) = + self.QObject.setup + self.keychainManager = newStatusKeychainManager("StatusDesktop", "authenticate you") + signalConnect(self.keychainManager, "success(QString)", self, + "onKeychainManagerSuccess(QString)", 2) + signalConnect(self.keychainManager, "error(QString, int, QString)", self, + "onKeychainManagerError(QString, int, QString)", 2) + + proc delete*(self: Service) = + self.keychainManager.delete + self.QObject.delete + + proc newService*(localSettingsService: local_settings_service.Service, + events: EventEmitter): + Service = + new(result, delete) + result.setup() + result.localSettingsService = localSettingsService + result.events = events + + proc storePassword*(self: Service, username: string, password: string) = + let value = self.localSettingsService.getAccountValue( + LS_KEY_STORE_TO_KEYCHAIN).stringVal + + if (value != LS_VALUE_STORE or username.len == 0): + return + + self.keychainManager.storeDataAsync(username, password) + + proc tryToObtainPassword*(self: Service, username: string) = + let value = self.localSettingsService.getAccountValue( + LS_KEY_STORE_TO_KEYCHAIN).stringVal + + if (value != LS_VALUE_STORE): + return + + self.keychainManager.readDataAsync(username) + + proc onKeychainManagerError*(self: Service, errorType: string, errorCode: int, + errorDescription: string) {.slot.} = + ## This slot is called in case an error occured while we're dealing with + ## KeychainManager. So far we're just logging the error. + info "KeychainManager stopped: ", msg = errorCode, errorDescription + if (errorType == ERROR_TYPE_AUTHENTICATION): + return + + # We are notifying user only about keychain errors. + self.localSettingsService.removeAccountValue(LS_KEY_STORE_TO_KEYCHAIN) + let arg = KeyChainServiceArg(errCode: errorCode, errType: errorType, + errDescription: errorDescription) + self.events.emit("obtainingPasswordError", arg) + + proc onKeychainManagerSuccess*(self: Service, data: string) {.slot.} = + ## This slot is called in case a password is successfully retrieved from the + ## Keychain. In this case @data contains required password. + self.events.emit("obtainingPasswordSuccess", KeyChainServiceArg(data: data)) \ No newline at end of file diff --git a/src/app_service/service/keychain/service_interface.nim b/src/app_service/service/keychain/service_interface.nim new file mode 100644 index 0000000000..6728458be5 --- /dev/null +++ b/src/app_service/service/keychain/service_interface.nim @@ -0,0 +1,7 @@ +# We cannot have interface defined here, bacause of Nim limitation in terms of +# multiple inheritances, since this service already inherits from QtObject + +# Concepts cannot be used also because of other limitation cause we cannot +# forward this class to appropriate submodule, cause Nim doesn't support +# nested types which depends on concepts. + diff --git a/src/app_service/service/local_settings/service.nim b/src/app_service/service/local_settings/service.nim index f4b51d49d8..80bdde3c28 100644 --- a/src/app_service/service/local_settings/service.nim +++ b/src/app_service/service/local_settings/service.nim @@ -1,11 +1,6 @@ import NimQml, os, chronicles import ../../../constants -# Local Account Settings keys: -const LS_KEY_STORE_TO_KEYCHAIN* = "storeToKeychain" -# Local Account Settings values: -const LS_VALUE_STORE* = "store" - const UNKNOWN_ACCOUNT = "unknownAccount" const UNKNOWN_PROFILE = "unknownProfile" diff --git a/ui/app/AppLayouts/WalletV2/stores/RootStore.qml b/ui/app/AppLayouts/WalletV2/stores/RootStore.qml index 58c1be2096..ee87a4722b 100644 --- a/ui/app/AppLayouts/WalletV2/stores/RootStore.qml +++ b/ui/app/AppLayouts/WalletV2/stores/RootStore.qml @@ -11,7 +11,11 @@ QtObject { property var walletModelV2Inst: walletV2Model property var profileModelInst: profileModel property var chatsModelInst: chatsModel - property var onboardingModelInst: onboardingModel + // This should be exposed to the UI via "walletModule", WalletModule should use + // Accounts Service which keeps the info about that. Then in the View of WalletModule + // we may have either QtProperty or Q_INVOKABLE function (proc marked as slot) + // depends on logic/need. + // property var onboardingModelInst: onboardingModel property int selectedAccount: 0 function getSavedAddressErrorText(savedAddresses, error) { @@ -57,9 +61,12 @@ QtObject { property bool loadingAccounts: false function seedPhraseNotFound(text) { - var seedValidationError = root.onboardingModelInst.validateMnemonic(text); - var regex = new RegExp('word [a-z]+ not found in the dictionary', 'i'); - return regex.test(seedValidationError); + // Read in above +// var seedValidationError = root.onboardingModelInst.validateMnemonic(text); +// var regex = new RegExp('word [a-z]+ not found in the dictionary', 'i'); +// return regex.test(seedValidationError); + + return "" } function validateAddAccountPopup(text, model, keyOrSeedValid, accountNameValid) {