Onboarding: saving credentials to Keychain after account creation

Closes: #17085
This commit is contained in:
Michał Cieślak 2025-02-18 18:48:10 +01:00
parent c9ba6a25d6
commit cf2c221c02
9 changed files with 51 additions and 37 deletions

View File

@ -543,7 +543,8 @@ proc finishAppLoading*(self: AppController) =
self.startupModule = nil self.startupModule = nil
if not self.onboardingModule.isNil: if not self.onboardingModule.isNil:
self.onboardingModule.onAppLoaded() let account = self.accountsService.getLoggedInAccount()
self.onboardingModule.onAppLoaded(account.keyUid)
self.onboardingModule = nil self.onboardingModule = nil
self.mainModule.checkAndPerformProfileMigrationIfNeeded() self.mainModule.checkAndPerformProfileMigrationIfNeeded()

View File

@ -10,7 +10,7 @@ import app/modules/onboarding/post_onboarding/task
method delete*(self: AccessInterface) {.base.} = method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onAppLoaded*(self: AccessInterface) {.base.} = method onAppLoaded*(self: AccessInterface, keyUid: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, settings: SettingsDto) {.base.} = method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, settings: SettingsDto) {.base.} =

View File

@ -65,8 +65,8 @@ method delete*[T](self: Module[T]) =
self.viewVariant.delete self.viewVariant.delete
self.controller.delete self.controller.delete
method onAppLoaded*[T](self: Module[T]) = method onAppLoaded*[T](self: Module[T], keyUid: string) =
self.view.appLoaded() self.view.appLoaded(keyUid)
singletonInstance.engine.setRootContextProperty("onboardingModule", newQVariant()) singletonInstance.engine.setRootContextProperty("onboardingModule", newQVariant())
self.view.delete self.view.delete
self.view = nil self.view = nil

View File

@ -33,7 +33,7 @@ QtObject:
### QtSignals ### ### QtSignals ###
proc appLoaded*(self: View) {.signal.} proc appLoaded*(self: View, keyUid: string) {.signal.}
proc accountLoginError*(self: View, error: string, wrongPassword: bool) {.signal.} proc accountLoginError*(self: View, error: string, wrongPassword: bool) {.signal.}
### QtProperties ### ### QtProperties ###
@ -164,4 +164,4 @@ QtObject:
self.delegate.loginRequested(keyUid, loginFlow, dataJson) self.delegate.loginRequested(keyUid, loginFlow, dataJson)
proc startKeycardFactoryReset(self: View) {.slot.} = proc startKeycardFactoryReset(self: View) {.slot.} =
self.delegate.startKeycardFactoryReset() self.delegate.startKeycardFactoryReset()

View File

@ -163,11 +163,9 @@ SplitView {
signal accountLoginError(string error, bool wrongPassword) signal accountLoginError(string error, bool wrongPassword)
} }
biometricsAvailable: ctrlBiometrics.checked keychain: keychain
isBiometricsLogin: ctrlTouchIdUser.checked
onBiometricsRequested: (profileId) => biometricsPopup.open() biometricsAvailable: ctrlBiometrics.checked
onDismissBiometricsRequested: biometricsPopup.close()
onFinished: (flow, data) => { onFinished: (flow, data) => {
console.warn("!!! ONBOARDING FINISHED; flow:", flow, "; data:", JSON.stringify(data)) console.warn("!!! ONBOARDING FINISHED; flow:", flow, "; data:", JSON.stringify(data))
@ -337,16 +335,22 @@ SplitView {
} }
} }
BiometricsPopup { KeychainMock {
id: biometricsPopup 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 const isKeycard = onboarding.currentPage instanceof LoginScreen
&& onboarding.currentPage.selectedProfileIsKeycard && onboarding.currentPage.selectedProfileIsKeycard
onboarding.setBiometricResponse(isKeycard ? mockDriver.pin : mockDriver.password) keychain.saveCredential(account, isKeycard ? mockDriver.pin : mockDriver.password)
return touchIdChecked ? Keychain.StatusSuccess
: Keychain.StatusNotFound
} }
} }

View File

@ -31,7 +31,7 @@ OnboardingStackView {
// functions // functions
required property var generateMnemonic required property var generateMnemonic
required property var isBiometricsLogin required property var isBiometricsLogin // (string account) => bool
required property var passwordStrengthScoreFunction required property var passwordStrengthScoreFunction
required property var isSeedPhraseValid required property var isSeedPhraseValid
required property var validateConnectionString required property var validateConnectionString
@ -190,7 +190,8 @@ OnboardingStackView {
keycardRemainingPukAttempts: root.remainingPukAttempts keycardRemainingPukAttempts: root.remainingPukAttempts
loginAccountsModel: root.loginAccountsModel loginAccountsModel: root.loginAccountsModel
isBiometricsLogin: root.isBiometricsLogin(loginScreen.selectedProfileKeyId) isBiometricsLogin: root.biometricsAvailable &&
root.isBiometricsLogin(loginScreen.selectedProfileKeyId)
onBiometricsRequested: (profileId) => root.biometricsRequested(profileId) onBiometricsRequested: (profileId) => root.biometricsRequested(profileId)
onDismissBiometricsRequested: root.dismissBiometricsRequested() onDismissBiometricsRequested: root.dismissBiometricsRequested()

View File

@ -20,9 +20,9 @@ Page {
required property OnboardingStore onboardingStore required property OnboardingStore onboardingStore
required property Keychain keychain required property Keychain keychain
property bool biometricsAvailable: Qt.platform.os === Constants.mac property bool biometricsAvailable
property bool networkChecksEnabled: true property bool networkChecksEnabled: true
property alias keycardPinInfoPageDelay: onboardingFlow.keycardPinInfoPageDelay property alias keycardPinInfoPageDelay: onboardingFlow.keycardPinInfoPageDelay
readonly property alias stack: onboardingFlow readonly property alias stack: onboardingFlow

View File

@ -7,7 +7,7 @@ import AppLayouts.Onboarding.enums 1.0
QtObject { QtObject {
id: root id: root
signal appLoaded signal appLoaded(string keyUid)
readonly property QtObject d: StatusQUtils.QObject { readonly property QtObject d: StatusQUtils.QObject {
id: d id: d

View File

@ -420,7 +420,7 @@ StatusWindow {
Keychain { Keychain {
service: "StatusDesktop" service: "StatusDesktop"
id: keychain id: appKeychain
} }
Component { Component {
@ -444,21 +444,42 @@ StatusWindow {
anchors.fill: parent anchors.fill: parent
networkChecksEnabled: true networkChecksEnabled: true
// TODO: cover case when biometrics is globally disabled on mac
biometricsAvailable: Qt.platform.os === Constants.mac biometricsAvailable: Qt.platform.os === Constants.mac
onboardingStore: onboardingStore onboardingStore: OnboardingStore {
keychain: keychain id: onboardingStore
onAppLoaded: {
applicationWindow.appIsReady = true
applicationWindow.storeAppState()
moveToAppMain()
}
onAccountLoginError: function (error, wrongPassword) {
onboardingLayout.stack.pop()
}
}
keychain: appKeychain
onFinished: (flow, data) => { onFinished: (flow, data) => {
const error = onboardingStore.finishOnboardingFlow(flow, data) const error = onboardingStore.finishOnboardingFlow(flow, data)
if (error != "") { if (error !== "") {
// We should never be here since everything should be validated already // We should never be here since everything should be validated already
console.error("!!! ONBOARDING FINISHED WITH ERROR:", error) console.error("!!! ONBOARDING FINISHED WITH ERROR:", error)
return return
} }
stack.clear() stack.clear()
stack.push(splashScreenV2, { runningProgressAnimation: true }) 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) { onLoginRequested: function (keyUid, method, data) {
@ -473,19 +494,6 @@ StatusWindow {
} }
} }
onCurrentPageNameChanged: Global.addCentralizedMetricIfEnabled("navigation", {viewId: currentPageName}) onCurrentPageNameChanged: Global.addCentralizedMetricIfEnabled("navigation", {viewId: currentPageName})
OnboardingStore {
id: onboardingStore
onAppLoaded: {
applicationWindow.appIsReady = true
applicationWindow.storeAppState()
moveToAppMain()
}
onAccountLoginError: function (error, wrongPassword) {
onboardingLayout.stack.pop()
}
}
} }
} }