From cf2c221c026fda41d286f60918d83f4dfba79077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Tue, 18 Feb 2025 18:48:10 +0100 Subject: [PATCH] Onboarding: saving credentials to Keychain after account creation Closes: #17085 --- src/app/boot/app_controller.nim | 3 +- src/app/modules/onboarding/io_interface.nim | 2 +- src/app/modules/onboarding/module.nim | 4 +- src/app/modules/onboarding/view.nim | 4 +- storybook/pages/OnboardingLayoutPage.qml | 22 ++++++---- .../AppLayouts/Onboarding2/OnboardingFlow.qml | 5 ++- .../Onboarding2/OnboardingLayout.qml | 4 +- .../Onboarding2/stores/OnboardingStore.qml | 2 +- ui/main.qml | 42 +++++++++++-------- 9 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 7e697e30d4..831496d9e0 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -543,7 +543,8 @@ proc finishAppLoading*(self: AppController) = self.startupModule = nil if not self.onboardingModule.isNil: - self.onboardingModule.onAppLoaded() + let account = self.accountsService.getLoggedInAccount() + self.onboardingModule.onAppLoaded(account.keyUid) self.onboardingModule = nil self.mainModule.checkAndPerformProfileMigrationIfNeeded() diff --git a/src/app/modules/onboarding/io_interface.nim b/src/app/modules/onboarding/io_interface.nim index bfc6f12284..f2f6e497b5 100644 --- a/src/app/modules/onboarding/io_interface.nim +++ b/src/app/modules/onboarding/io_interface.nim @@ -10,7 +10,7 @@ import app/modules/onboarding/post_onboarding/task method delete*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method onAppLoaded*(self: AccessInterface) {.base.} = +method onAppLoaded*(self: AccessInterface, keyUid: string) {.base.} = raise newException(ValueError, "No implementation available") method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, settings: SettingsDto) {.base.} = diff --git a/src/app/modules/onboarding/module.nim b/src/app/modules/onboarding/module.nim index d5163024fd..4f8dedb6cf 100644 --- a/src/app/modules/onboarding/module.nim +++ b/src/app/modules/onboarding/module.nim @@ -65,8 +65,8 @@ method delete*[T](self: Module[T]) = self.viewVariant.delete self.controller.delete -method onAppLoaded*[T](self: Module[T]) = - self.view.appLoaded() +method onAppLoaded*[T](self: Module[T], keyUid: string) = + self.view.appLoaded(keyUid) singletonInstance.engine.setRootContextProperty("onboardingModule", newQVariant()) self.view.delete self.view = nil diff --git a/src/app/modules/onboarding/view.nim b/src/app/modules/onboarding/view.nim index ecf0ee6c2c..63f7588b82 100644 --- a/src/app/modules/onboarding/view.nim +++ b/src/app/modules/onboarding/view.nim @@ -33,7 +33,7 @@ QtObject: ### QtSignals ### - proc appLoaded*(self: View) {.signal.} + proc appLoaded*(self: View, keyUid: string) {.signal.} proc accountLoginError*(self: View, error: string, wrongPassword: bool) {.signal.} ### QtProperties ### @@ -164,4 +164,4 @@ QtObject: self.delegate.loginRequested(keyUid, loginFlow, dataJson) proc startKeycardFactoryReset(self: View) {.slot.} = - self.delegate.startKeycardFactoryReset() \ No newline at end of file + self.delegate.startKeycardFactoryReset() diff --git a/storybook/pages/OnboardingLayoutPage.qml b/storybook/pages/OnboardingLayoutPage.qml index 6d70aa2d1f..95789e4dcf 100644 --- a/storybook/pages/OnboardingLayoutPage.qml +++ b/storybook/pages/OnboardingLayoutPage.qml @@ -163,11 +163,9 @@ SplitView { signal accountLoginError(string error, bool wrongPassword) } - biometricsAvailable: ctrlBiometrics.checked - isBiometricsLogin: ctrlTouchIdUser.checked + keychain: keychain - onBiometricsRequested: (profileId) => biometricsPopup.open() - onDismissBiometricsRequested: biometricsPopup.close() + biometricsAvailable: ctrlBiometrics.checked onFinished: (flow, data) => { console.warn("!!! ONBOARDING FINISHED; flow:", flow, "; data:", JSON.stringify(data)) @@ -337,16 +335,22 @@ SplitView { } } - BiometricsPopup { - id: biometricsPopup + KeychainMock { + id: keychain - x: root.Window.width - width + parent: root - onObtainingPasswordSuccess: { + readonly property alias touchIdChecked: ctrlTouchIdUser.checked + onTouchIdCheckedChanged: onboarding.keychainChanged() + + function hasCredential(account) { const isKeycard = onboarding.currentPage instanceof LoginScreen && onboarding.currentPage.selectedProfileIsKeycard - onboarding.setBiometricResponse(isKeycard ? mockDriver.pin : mockDriver.password) + keychain.saveCredential(account, isKeycard ? mockDriver.pin : mockDriver.password) + + return touchIdChecked ? Keychain.StatusSuccess + : Keychain.StatusNotFound } } diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml index d8a2e5b241..db637c0836 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml @@ -31,7 +31,7 @@ OnboardingStackView { // functions required property var generateMnemonic - required property var isBiometricsLogin + required property var isBiometricsLogin // (string account) => bool required property var passwordStrengthScoreFunction required property var isSeedPhraseValid required property var validateConnectionString @@ -190,7 +190,8 @@ OnboardingStackView { keycardRemainingPukAttempts: root.remainingPukAttempts loginAccountsModel: root.loginAccountsModel - isBiometricsLogin: root.isBiometricsLogin(loginScreen.selectedProfileKeyId) + isBiometricsLogin: root.biometricsAvailable && + root.isBiometricsLogin(loginScreen.selectedProfileKeyId) onBiometricsRequested: (profileId) => root.biometricsRequested(profileId) onDismissBiometricsRequested: root.dismissBiometricsRequested() diff --git a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml index fd37ba2417..79070e9caf 100644 --- a/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml +++ b/ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml @@ -20,9 +20,9 @@ Page { required property OnboardingStore onboardingStore required property Keychain keychain - property bool biometricsAvailable: Qt.platform.os === Constants.mac - + property bool biometricsAvailable property bool networkChecksEnabled: true + property alias keycardPinInfoPageDelay: onboardingFlow.keycardPinInfoPageDelay readonly property alias stack: onboardingFlow diff --git a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml index 81b9b161d5..79864d4154 100644 --- a/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml +++ b/ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml @@ -7,7 +7,7 @@ import AppLayouts.Onboarding.enums 1.0 QtObject { id: root - signal appLoaded + signal appLoaded(string keyUid) readonly property QtObject d: StatusQUtils.QObject { id: d diff --git a/ui/main.qml b/ui/main.qml index b6220af83f..3a5cdc872d 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -420,7 +420,7 @@ StatusWindow { Keychain { service: "StatusDesktop" - id: keychain + id: appKeychain } Component { @@ -444,21 +444,42 @@ StatusWindow { anchors.fill: parent networkChecksEnabled: true + + // TODO: cover case when biometrics is globally disabled on mac biometricsAvailable: Qt.platform.os === Constants.mac - onboardingStore: onboardingStore - keychain: keychain + onboardingStore: OnboardingStore { + id: onboardingStore + + onAppLoaded: { + applicationWindow.appIsReady = true + applicationWindow.storeAppState() + moveToAppMain() + } + onAccountLoginError: function (error, wrongPassword) { + onboardingLayout.stack.pop() + } + } + + keychain: appKeychain onFinished: (flow, data) => { const error = onboardingStore.finishOnboardingFlow(flow, data) - if (error != "") { + if (error !== "") { // We should never be here since everything should be validated already console.error("!!! ONBOARDING FINISHED WITH ERROR:", error) return } stack.clear() stack.push(splashScreenV2, { runningProgressAnimation: true }) + + if (!data.enableBiometrics) + return + + onboardingStore.appLoaded.connect((keyUid) => { + appKeychain.saveCredential(keyUid, data.password || data.pin) + }) } onLoginRequested: function (keyUid, method, data) { @@ -473,19 +494,6 @@ StatusWindow { } } onCurrentPageNameChanged: Global.addCentralizedMetricIfEnabled("navigation", {viewId: currentPageName}) - - OnboardingStore { - id: onboardingStore - - onAppLoaded: { - applicationWindow.appIsReady = true - applicationWindow.storeAppState() - moveToAppMain() - } - onAccountLoginError: function (error, wrongPassword) { - onboardingLayout.stack.pop() - } - } } }