mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-15 08:08:49 +00:00
feat: Integrate new logins flows happy paths (#17137)
* feat(login): integrate basic login flows happy paths Fixes #17137 * fix: rebase issues and pr comments * chore: switch status-keycard-go to master branch * fix: tests --------- Co-authored-by: Igor Sirotin <sirotin@status.im>
This commit is contained in:
parent
2d549f42be
commit
bf5de4087e
@ -148,12 +148,9 @@ proc connect(self: AppController) =
|
||||
elif defined(production):
|
||||
setLogLevel(chronicles.LogLevel.INFO)
|
||||
|
||||
# TODO remove these functions once we have only the new onboarding module
|
||||
proc shouldStartWithOnboardingScreen(self: AppController): bool =
|
||||
return self.accountsService.openedAccounts().len == 0
|
||||
# TODO remove this function once we have only the new onboarding module
|
||||
proc shouldUseTheNewOnboardingModule(self: AppController): bool =
|
||||
# Only the onboarding for new users is implemented in the new module for now
|
||||
return singletonInstance.featureFlags().getOnboardingV2Enabled() and self.shouldStartWithOnboardingScreen()
|
||||
return singletonInstance.featureFlags().getOnboardingV2Enabled()
|
||||
|
||||
proc newAppController*(statusFoundation: StatusFoundation): AppController =
|
||||
result = AppController()
|
||||
|
@ -9,6 +9,7 @@ import app_service/service/accounts/service as accounts_service
|
||||
import app_service/service/accounts/dto/image_crop_rectangle
|
||||
import app_service/service/devices/service as devices_service
|
||||
import app_service/service/keycardV2/service as keycard_serviceV2
|
||||
import app_service/common/utils
|
||||
from app_service/service/keycardV2/dto import KeycardExportedKeysDto
|
||||
|
||||
logScope:
|
||||
@ -86,14 +87,29 @@ proc init*(self: Controller) =
|
||||
self.delegate.onKeycardLoadMnemonicSuccess(args.keyUID)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE) do(e: Args):
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE) do(e: Args):
|
||||
let args = KeycardErrorArg(e)
|
||||
self.delegate.onKeycardExportKeysFailure(args.error)
|
||||
self.delegate.onKeycardExportRestoreKeysFailure(args.error)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS) do(e: Args):
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_SUCCESS) do(e: Args):
|
||||
let args = KeycardExportedKeysArg(e)
|
||||
self.delegate.onKeycardExportKeysSuccess(args.exportedKeys)
|
||||
self.delegate.onKeycardExportRestoreKeysSuccess(args.exportedKeys)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE) do(e: Args):
|
||||
let args = KeycardErrorArg(e)
|
||||
self.delegate.onKeycardExportLoginKeysFailure(args.error)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_SUCCESS) do(e: Args):
|
||||
let args = KeycardExportedKeysArg(e)
|
||||
self.delegate.onKeycardExportLoginKeysSuccess(args.exportedKeys)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_LOGIN_ERROR) do(e: Args):
|
||||
let args = LoginErrorArgs(e)
|
||||
self.delegate.onAccountLoginError(args.error)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
proc initialize*(self: Controller, pin: string) =
|
||||
@ -174,3 +190,40 @@ proc generateMnemonic*(self: Controller, length: int): string =
|
||||
|
||||
proc exportRecoverKeysFromKeycard*(self: Controller) =
|
||||
self.keycardServiceV2.asyncExportRecoverKeys()
|
||||
|
||||
proc exportLoginKeysFromKeycard*(self: Controller) =
|
||||
self.keycardServiceV2.asyncExportLoginKeys()
|
||||
|
||||
proc getOpenedAccounts*(self: Controller): seq[AccountDto] =
|
||||
return self.accountsService.openedAccounts()
|
||||
|
||||
proc getAccountByKeyUid*(self: Controller, keyUid: string): AccountDto =
|
||||
return self.accountsService.getAccountByKeyUid(keyUid)
|
||||
|
||||
proc login*(
|
||||
self: Controller,
|
||||
account: AccountDto,
|
||||
password: string,
|
||||
keycard: bool = false,
|
||||
publicEncryptionKey: string = "",
|
||||
privateWhisperKey: string = "",
|
||||
mnemonic: string = "",
|
||||
keycardReplacement: bool = false,
|
||||
) =
|
||||
var passwordHash, chatPrivateKey = ""
|
||||
|
||||
if not keycard:
|
||||
passwordHash = hashPassword(password)
|
||||
else:
|
||||
passwordHash = publicEncryptionKey
|
||||
chatPrivateKey = privateWhisperKey
|
||||
|
||||
# if keycard and keycardReplacement:
|
||||
# self.delegate.applyKeycardReplacementAfterLogin()
|
||||
|
||||
self.accountsService.login(
|
||||
account,
|
||||
passwordHash,
|
||||
chatPrivateKey,
|
||||
mnemonic,
|
||||
)
|
||||
|
@ -45,6 +45,9 @@ method loadMnemonic*(self: AccessInterface, dataJson: string) {.base.} =
|
||||
method finishOnboardingFlow*(self: AccessInterface, flowInt: int, dataJson: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method loginRequested*(self: AccessInterface, keyUid: string, loginFlow: int, dataJson: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
@ -63,10 +66,19 @@ method onKeycardLoadMnemonicFailure*(self: AccessInterface, error: string) {.bas
|
||||
method onKeycardLoadMnemonicSuccess*(self: AccessInterface, keyUID: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardExportKeysFailure*(self: AccessInterface, error: string) {.base.} =
|
||||
method onKeycardExportRestoreKeysFailure*(self: AccessInterface, error: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardExportKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
|
||||
method onKeycardExportRestoreKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardExportLoginKeysFailure*(self: AccessInterface, error: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onKeycardExportLoginKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onAccountLoginError*(self: AccessInterface, error: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method exportRecoverKeys*(self: AccessInterface) {.base.} =
|
||||
|
@ -1,4 +1,4 @@
|
||||
import NimQml, chronicles, json
|
||||
import NimQml, chronicles, json, strutils
|
||||
import logging
|
||||
|
||||
import io_interface
|
||||
@ -14,12 +14,14 @@ from app_service/service/settings/dto/settings import SettingsDto
|
||||
from app_service/service/accounts/dto/accounts import AccountDto
|
||||
from app_service/service/keycardV2/dto import KeycardEventDto, KeycardExportedKeysDto, KeycardState
|
||||
|
||||
import ../startup/models/login_account_item as login_acc_item
|
||||
|
||||
export io_interface
|
||||
|
||||
logScope:
|
||||
topics = "onboarding-module"
|
||||
|
||||
type SecondaryFlow* {.pure} = enum
|
||||
type OnboardingFlow* {.pure} = enum
|
||||
Unknown = 0,
|
||||
CreateProfileWithPassword,
|
||||
CreateProfileWithSeedphrase,
|
||||
@ -28,13 +30,17 @@ type SecondaryFlow* {.pure} = enum
|
||||
LoginWithSeedphrase,
|
||||
LoginWithSyncing,
|
||||
LoginWithKeycard,
|
||||
ActualLogin, # TODO get the real name and value for this when it's implemented on the front-end
|
||||
|
||||
type LoginMethod* {.pure} = enum
|
||||
Unknown = 0,
|
||||
Password,
|
||||
Keycard,
|
||||
|
||||
type ProgressState* {.pure.} = enum
|
||||
Idle,
|
||||
InProgress,
|
||||
Success,
|
||||
Failed
|
||||
Failed,
|
||||
|
||||
type
|
||||
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
|
||||
@ -43,7 +49,8 @@ type
|
||||
viewVariant: QVariant
|
||||
controller: Controller
|
||||
localPairingStatus: LocalPairingStatus
|
||||
currentFlow: SecondaryFlow
|
||||
loginFlow: LoginMethod
|
||||
onboardingFlow: OnboardingFlow
|
||||
exportedKeys: KeycardExportedKeysDto
|
||||
|
||||
proc newModule*[T](
|
||||
@ -58,6 +65,8 @@ proc newModule*[T](
|
||||
result.delegate = delegate
|
||||
result.view = view.newView(result)
|
||||
result.viewVariant = newQVariant(result.view)
|
||||
result.onboardingFlow = OnboardingFlow.Unknown
|
||||
result.loginFlow = LoginMethod.Unknown
|
||||
result.controller = controller.newController(
|
||||
result,
|
||||
events,
|
||||
@ -87,6 +96,20 @@ method onAppLoaded*[T](self: Module[T]) =
|
||||
method load*[T](self: Module[T]) =
|
||||
singletonInstance.engine.setRootContextProperty("onboardingModule", self.viewVariant)
|
||||
self.controller.init()
|
||||
|
||||
let openedAccounts = self.controller.getOpenedAccounts()
|
||||
if openedAccounts.len > 0:
|
||||
var items: seq[login_acc_item.Item]
|
||||
for i in 0..<openedAccounts.len:
|
||||
let acc = openedAccounts[i]
|
||||
var thumbnailImage: string
|
||||
var largeImage: string
|
||||
acc.extractImages(thumbnailImage, largeImage)
|
||||
items.add(login_acc_item.initItem(order = i, acc.name, icon = "", thumbnailImage, largeImage, acc.keyUid, acc.colorHash,
|
||||
acc.colorId, acc.keycardPairing))
|
||||
|
||||
self.view.setLoginAccountsModelItems(items)
|
||||
|
||||
self.delegate.onboardingDidLoad()
|
||||
|
||||
method initialize*[T](self: Module[T], pin: string) =
|
||||
@ -118,7 +141,7 @@ method loadMnemonic*[T](self: Module[T], mnemonic: string) =
|
||||
|
||||
method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string): string =
|
||||
try:
|
||||
self.currentFlow = SecondaryFlow(flowInt)
|
||||
self.onboardingFlow = OnboardingFlow(flowInt)
|
||||
|
||||
let data = parseJson(dataJson)
|
||||
let password = data["password"].str
|
||||
@ -126,18 +149,18 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
|
||||
|
||||
var err = ""
|
||||
|
||||
case self.currentFlow:
|
||||
case self.onboardingFlow:
|
||||
# CREATE PROFILE FLOWS
|
||||
of SecondaryFlow.CreateProfileWithPassword:
|
||||
of OnboardingFlow.CreateProfileWithPassword:
|
||||
err = self.controller.createAccountAndLogin(password)
|
||||
of SecondaryFlow.CreateProfileWithSeedphrase:
|
||||
of OnboardingFlow.CreateProfileWithSeedphrase:
|
||||
err = self.controller.restoreAccountAndLogin(
|
||||
password,
|
||||
seedPhrase,
|
||||
recoverAccount = false,
|
||||
keycardInstanceUID = "",
|
||||
)
|
||||
of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase:
|
||||
of OnboardingFlow.CreateProfileWithKeycardNewSeedphrase:
|
||||
# New user with a seedphrase we showed them
|
||||
let keycardEvent = self.view.getKeycardEvent()
|
||||
err = self.controller.restoreAccountAndLogin(
|
||||
@ -146,7 +169,7 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
|
||||
recoverAccount = false,
|
||||
keycardInstanceUID = keycardEvent.keycardInfo.instanceUID,
|
||||
)
|
||||
of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase:
|
||||
of OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase:
|
||||
# New user who entered their own seed phrase
|
||||
let keycardEvent = self.view.getKeycardEvent()
|
||||
err = self.controller.restoreAccountAndLogin(
|
||||
@ -157,21 +180,21 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
|
||||
)
|
||||
|
||||
# LOGIN FLOWS
|
||||
of SecondaryFlow.LoginWithSeedphrase:
|
||||
of OnboardingFlow.LoginWithSeedphrase:
|
||||
err = self.controller.restoreAccountAndLogin(
|
||||
password,
|
||||
seedPhrase,
|
||||
recoverAccount = true,
|
||||
keycardInstanceUID = "",
|
||||
)
|
||||
of SecondaryFlow.LoginWithSyncing:
|
||||
of OnboardingFlow.LoginWithSyncing:
|
||||
# The pairing was already done directly through inputConnectionStringForBootstrapping, we can login
|
||||
self.controller.loginLocalPairingAccount(
|
||||
self.localPairingStatus.account,
|
||||
self.localPairingStatus.password,
|
||||
self.localPairingStatus.chatKey,
|
||||
)
|
||||
of SecondaryFlow.LoginWithKeycard:
|
||||
of OnboardingFlow.LoginWithKeycard:
|
||||
err = self.controller.restoreKeycardAccountAndLogin(
|
||||
self.view.getKeycardEvent().keycardInfo.keyUID,
|
||||
self.view.getKeycardEvent().keycardInfo.instanceUID,
|
||||
@ -179,31 +202,56 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
|
||||
recoverAccount = true
|
||||
)
|
||||
else:
|
||||
raise newException(ValueError, "Unknown flow: " & $self.currentFlow)
|
||||
raise newException(ValueError, "Unknown flow: " & $self.onboardingFlow)
|
||||
|
||||
return err
|
||||
except Exception as e:
|
||||
error "Error finishing Onboarding Flow", msg = e.msg
|
||||
return e.msg
|
||||
|
||||
method loginRequested*[T](self: Module[T], keyUid: string, loginFlow: int, dataJson: string) =
|
||||
try:
|
||||
self.loginFlow = LoginMethod(loginFlow)
|
||||
|
||||
let data = parseJson(dataJson)
|
||||
let account = self.controller.getAccountByKeyUid(keyUid)
|
||||
|
||||
case self.loginFlow:
|
||||
of LoginMethod.Password:
|
||||
self.controller.login(account, data["password"].str)
|
||||
of LoginMethod.Keycard:
|
||||
self.authorize(data["pin"].str)
|
||||
# We will continue the flow when the card is authorized in onKeycardStateUpdated
|
||||
else:
|
||||
raise newException(ValueError, "Unknown flow: " & $self.onboardingFlow)
|
||||
|
||||
except Exception as e:
|
||||
error "Error finishing Login Flow", msg = e.msg
|
||||
self.view.accountLoginError(e.msg, wrongPassword = false)
|
||||
|
||||
proc finishAppLoading2[T](self: Module[T]) =
|
||||
self.delegate.appReady()
|
||||
|
||||
# TODO get the flow to send the right metric
|
||||
var eventType = "user-logged-in"
|
||||
if self.currentFlow != SecondaryFlow.ActualLogin:
|
||||
if self.loginFlow == LoginMethod.Unknown:
|
||||
eventType = "onboarding-completed"
|
||||
singletonInstance.globalEvents.addCentralizedMetricIfEnabled(eventType,
|
||||
$(%*{"flowType": repr(self.currentFlow)}))
|
||||
$(%*{"flowType": repr(self.onboardingFlow)}))
|
||||
|
||||
self.controller.stopKeycardService()
|
||||
|
||||
self.delegate.finishAppLoading()
|
||||
|
||||
|
||||
method onAccountLoginError*[T](self: Module[T], error: string) =
|
||||
# SQLITE_NOTADB: "file is not a database"
|
||||
var wrongPassword = false
|
||||
if error.contains("file is not a database"):
|
||||
wrongPassword = true
|
||||
self.view.accountLoginError(error, wrongPassword)
|
||||
|
||||
method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) =
|
||||
if error.len != 0:
|
||||
# TODO: Handle error
|
||||
echo "ERROR from onNodeLogin: ", error
|
||||
self.onAccountLoginError(error)
|
||||
return
|
||||
|
||||
self.controller.setLoggedInAccount(account)
|
||||
@ -221,6 +269,11 @@ method onLocalPairingStatusUpdate*[T](self: Module[T], status: LocalPairingStatu
|
||||
method onKeycardStateUpdated*[T](self: Module[T], keycardEvent: KeycardEventDto) =
|
||||
self.view.setKeycardEvent(keycardEvent)
|
||||
|
||||
if keycardEvent.state == KeycardState.Authorized and self.loginFlow == LoginMethod.Keycard:
|
||||
# After authorizing, we export the keys
|
||||
self.controller.exportLoginKeysFromKeycard()
|
||||
# We will login once we have the keys in onKeycardExportLoginKeysSuccess
|
||||
|
||||
if keycardEvent.state == KeycardState.NotEmpty and self.view.getPinSettingState() == ProgressState.InProgress.int:
|
||||
# We just finished setting the pin
|
||||
self.view.setPinSettingState(ProgressState.Success.int)
|
||||
@ -235,19 +288,39 @@ method onKeycardSetPinFailure*[T](self: Module[T], error: string) =
|
||||
method onKeycardAuthorizeFailure*[T](self: Module[T], error: string) =
|
||||
self.view.setAuthorizationState(ProgressState.Failed.int)
|
||||
|
||||
if self.loginFlow == LoginMethod.Keycard:
|
||||
# We were trying to login and the authorization failed
|
||||
var wrongPassword = false
|
||||
if error.contains("wrong pin"):
|
||||
wrongPassword = true
|
||||
self.view.accountLoginError(error, wrongPassword)
|
||||
|
||||
method onKeycardLoadMnemonicFailure*[T](self: Module[T], error: string) =
|
||||
self.view.setAddKeyPairState(ProgressState.Failed.int)
|
||||
|
||||
method onKeycardLoadMnemonicSuccess*[T](self: Module[T], keyUID: string) =
|
||||
self.view.setAddKeyPairState(ProgressState.Success.int)
|
||||
|
||||
method onKeycardExportKeysFailure*[T](self: Module[T], error: string) =
|
||||
method onKeycardExportRestoreKeysFailure*[T](self: Module[T], error: string) =
|
||||
self.view.setRestoreKeysExportState(ProgressState.Failed.int)
|
||||
|
||||
method onKeycardExportKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
|
||||
method onKeycardExportRestoreKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
|
||||
self.exportedKeys = exportedKeys
|
||||
self.view.setRestoreKeysExportState(ProgressState.Success.int)
|
||||
|
||||
method onKeycardExportLoginKeysFailure*[T](self: Module[T], error: string) =
|
||||
self.view.accountLoginError(error, wrongPassword = false)
|
||||
|
||||
method onKeycardExportLoginKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
|
||||
# We got the keys, now we can login. If everything goes well, we will finish the app loading
|
||||
self.controller.login(
|
||||
self.controller.getAccountByKeyUid(self.view.getKeycardEvent.keycardInfo.keyUID),
|
||||
password = "",
|
||||
keycard = true,
|
||||
publicEncryptionKey = exportedKeys.encryptionKey.publicKey,
|
||||
privateWhisperKey = exportedKeys.whisperKey.privateKey,
|
||||
)
|
||||
|
||||
method exportRecoverKeys*[T](self: Module[T]) =
|
||||
self.view.setRestoreKeysExportState(ProgressState.InProgress.int)
|
||||
self.controller.exportRecoverKeysFromKeycard()
|
||||
|
@ -2,6 +2,10 @@ import NimQml
|
||||
import io_interface
|
||||
from app_service/service/keycardV2/dto import KeycardEventDto
|
||||
|
||||
# TODO move these files to this module when we remove the old onboarding
|
||||
import ../startup/models/login_account_model as login_acc_model
|
||||
import ../startup/models/login_account_item as login_acc_item
|
||||
|
||||
QtObject:
|
||||
type
|
||||
View* = ref object of QObject
|
||||
@ -12,15 +16,20 @@ QtObject:
|
||||
pinSettingState: int
|
||||
authorizationState: int
|
||||
restoreKeysExportState: int
|
||||
loginAccountsModel: login_acc_model.Model
|
||||
loginAccountsModelVariant: QVariant
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.QObject.delete
|
||||
self.loginAccountsModel.delete
|
||||
self.loginAccountsModelVariant.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
new(result, delete)
|
||||
result.QObject.setup
|
||||
result.delegate = delegate
|
||||
|
||||
result.loginAccountsModel = login_acc_model.newModel()
|
||||
result.loginAccountsModelVariant = newQVariant(result.loginAccountsModel)
|
||||
|
||||
### QtSignals ###
|
||||
|
||||
@ -109,6 +118,16 @@ QtObject:
|
||||
proc getKeycardEvent*(self: View): KeycardEventDto =
|
||||
return self.keycardEvent
|
||||
|
||||
proc loginAccountsModelChanged*(self: View) {.signal.}
|
||||
proc getLoginAccountsModel(self: View): QVariant {.slot.} =
|
||||
return self.loginAccountsModelVariant
|
||||
proc setLoginAccountsModelItems*(self: View, accounts: seq[login_acc_item.Item]) =
|
||||
self.loginAccountsModel.setItems(accounts)
|
||||
self.loginAccountsModelChanged()
|
||||
QtProperty[QVariant] loginAccountsModel:
|
||||
read = getLoginAccountsModel
|
||||
notify = loginAccountsModelChanged
|
||||
|
||||
### slots ###
|
||||
|
||||
proc setPin(self: View, pin: string) {.slot.} =
|
||||
@ -140,3 +159,6 @@ QtObject:
|
||||
|
||||
proc finishOnboardingFlow(self: View, flowInt: int, dataJson: string): string {.slot.} =
|
||||
self.delegate.finishOnboardingFlow(flowInt, dataJson)
|
||||
|
||||
proc loginRequested(self: View, keyUid: string, loginFlow: int, dataJson: string): string {.slot.} =
|
||||
self.delegate.loginRequested(keyUid, loginFlow, dataJson)
|
||||
|
@ -73,3 +73,10 @@ proc toWakuBackedUpProfileDto*(jsonObj: JsonNode): WakuBackedUpProfileDto =
|
||||
if(jsonObj.getProp("images", obj) and obj.kind == JArray):
|
||||
for imgObj in obj:
|
||||
result.images.add(toImage(imgObj))
|
||||
|
||||
proc extractImages*(account: AccountDto, thumbnailImage: var string, largeImage: var string) =
|
||||
for img in account.images:
|
||||
if(img.imgType == "thumbnail"):
|
||||
thumbnailImage = img.uri
|
||||
elif(img.imgType == "large"):
|
||||
largeImage = img.uri
|
||||
|
@ -123,6 +123,9 @@ QtObject:
|
||||
error "error: ", procName="validateMnemonic", errName = e.name, errDesription = e.msg
|
||||
|
||||
proc openedAccounts*(self: Service): seq[AccountDto] =
|
||||
if self.accounts.len > 0:
|
||||
return self.accounts
|
||||
|
||||
try:
|
||||
let response = status_account.openedAccounts(main_constants.STATUSGODIR)
|
||||
|
||||
@ -136,6 +139,11 @@ QtObject:
|
||||
proc openedAccountsContainsKeyUid*(self: Service, keyUid: string): bool =
|
||||
return (keyUID in self.openedAccounts().mapIt(it.keyUid))
|
||||
|
||||
proc getAccountByKeyUid*(self: Service, keyUid: string): AccountDto =
|
||||
for account in self.openedAccounts():
|
||||
if account.keyUid == keyUid:
|
||||
return account
|
||||
|
||||
# FIXME: remove this method, settings should be processed in status-go
|
||||
# https://github.com/status-im/status-go/issues/5359
|
||||
proc addKeycardDetails(self: Service, kcInstance: string, settingsJson: var JsonNode, accountData: var JsonNode) =
|
||||
|
@ -69,3 +69,20 @@ proc asyncExportRecoverKeysTask(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
arg.finish(%* {
|
||||
"error": e.msg,
|
||||
})
|
||||
|
||||
type
|
||||
AsyncExportLoginKeysArg = ref object of QObjectTaskArg
|
||||
rpcCounter: int
|
||||
|
||||
proc asyncExportLoginKeysTask(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncExportLoginKeysArg](argEncoded)
|
||||
try:
|
||||
let response = callRPC(arg.rpcCounter, "ExportLoginKeys")
|
||||
arg.finish(%*{
|
||||
"response": response,
|
||||
"error": ""
|
||||
})
|
||||
except Exception as e:
|
||||
arg.finish(%* {
|
||||
"error": e.msg,
|
||||
})
|
||||
|
@ -31,8 +31,10 @@ const SIGNAL_KEYCARD_SET_PIN_FAILURE* = "keycardSetPinFailure"
|
||||
const SIGNAL_KEYCARD_AUTHORIZE_FAILURE* = "keycardAuthorizeFailure"
|
||||
const SIGNAL_KEYCARD_LOAD_MNEMONIC_FAILURE* = "keycardLoadMnemonicFailure"
|
||||
const SIGNAL_KEYCARD_LOAD_MNEMONIC_SUCCESS* = "keycardLoadMnemonicSuccess"
|
||||
const SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE* = "keycardExportKeysFailure"
|
||||
const SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS* = "keycardExportKeysSuccess"
|
||||
const SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE* = "keycardExportRestoreKeysFailure"
|
||||
const SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_SUCCESS* = "keycardExportRestoreKeysSuccess"
|
||||
const SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE* = "keycardExportLoginKeysFailure"
|
||||
const SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_SUCCESS* = "keycardExportLoginKeysSuccess"
|
||||
|
||||
type
|
||||
KeycardEventArg* = ref object of Args
|
||||
@ -157,10 +159,9 @@ QtObject:
|
||||
|
||||
let rpcResponseObj = responseObj["response"].getStr().parseJson()
|
||||
if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "":
|
||||
let error = Json.decode(rpcResponseObj["error"].getStr, RpcError)
|
||||
raise newException(RpcException, "Error authorizing: " & error.message)
|
||||
raise newException(RpcException, rpcResponseObj["error"].getStr)
|
||||
except Exception as e:
|
||||
error "error set pin: ", msg = e.msg
|
||||
error "error during authorize: ", msg = e.msg
|
||||
self.events.emit(SIGNAL_KEYCARD_AUTHORIZE_FAILURE, KeycardErrorArg(error: e.msg))
|
||||
|
||||
proc receiveKeycardSignalV2(self: Service, signal: string) {.slot.} =
|
||||
@ -231,9 +232,37 @@ QtObject:
|
||||
raise newException(RpcException, "Error authorizing: " & error.message)
|
||||
|
||||
let keys = rpcResponseObj["result"]["keys"].toKeycardExportedKeysDto()
|
||||
self.events.emit(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS, KeycardExportedKeysArg(exportedKeys: keys))
|
||||
self.events.emit(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_SUCCESS, KeycardExportedKeysArg(exportedKeys: keys))
|
||||
except Exception as e:
|
||||
error "error exporting recover keys", msg = e.msg
|
||||
self.events.emit(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE, KeycardErrorArg(error: e.msg))
|
||||
self.events.emit(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE, KeycardErrorArg(error: e.msg))
|
||||
|
||||
proc asyncExportLoginKeys*(self: Service) =
|
||||
self.rpcCounter += 1
|
||||
let arg = AsyncExportLoginKeysArg(
|
||||
tptr: asyncExportLoginKeysTask,
|
||||
vptr: cast[uint](self.vptr),
|
||||
slot: "onAsyncExportLoginKeys",
|
||||
rpcCounter: self.rpcCounter,
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
proc onAsyncExportLoginKeys*(self: Service, response: string) {.slot.} =
|
||||
try:
|
||||
let responseObj = response.parseJson
|
||||
|
||||
if responseObj{"error"}.kind != JNull and responseObj{"error"}.getStr != "":
|
||||
raise newException(CatchableError, responseObj{"error"}.getStr)
|
||||
|
||||
let rpcResponseObj = responseObj["response"].getStr().parseJson()
|
||||
if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "":
|
||||
let error = Json.decode(rpcResponseObj["error"].getStr, RpcError)
|
||||
raise newException(RpcException, "Error authorizing: " & error.message)
|
||||
|
||||
let keys = rpcResponseObj["result"]["keys"].toKeycardExportedKeysDto()
|
||||
self.events.emit(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_SUCCESS, KeycardExportedKeysArg(exportedKeys: keys))
|
||||
except Exception as e:
|
||||
error "error exporting login keys", msg = e.msg
|
||||
self.events.emit(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE, KeycardErrorArg(error: e.msg))
|
||||
|
||||
|
@ -63,6 +63,7 @@ SplitView {
|
||||
property int authorizationState: Onboarding.ProgressState.Idle
|
||||
property int restoreKeysExportState: Onboarding.ProgressState.Idle
|
||||
property int syncState: Onboarding.ProgressState.Idle
|
||||
property var loginAccountsModel: ctrlLoginScreen.checked ? loginAccountsModel : emptyModel
|
||||
|
||||
property int keycardRemainingPinAttempts: 2
|
||||
property int keycardRemainingPukAttempts: 3
|
||||
@ -142,8 +143,6 @@ SplitView {
|
||||
signal obtainingPasswordError(string errorDescription, string errorType /* Constants.keychain.errorType.* */, bool wrongFingerprint)
|
||||
}
|
||||
|
||||
loginAccountsModel: ctrlLoginScreen.checked ? loginAccountsModel : emptyModel
|
||||
|
||||
biometricsAvailable: ctrlBiometrics.checked
|
||||
isBiometricsLogin: localAccountSettings.storeToKeychainValue === Constants.keychain.storedValue.store
|
||||
onBiometricsRequested: biometricsPopup.open()
|
||||
|
@ -53,7 +53,6 @@ Item {
|
||||
biometricsAvailable: mockDriver.biometricsAvailable
|
||||
keycardPinInfoPageDelay: 0
|
||||
|
||||
loginAccountsModel: emptyModel
|
||||
isBiometricsLogin: biometricsAvailable
|
||||
|
||||
onboardingStore: OnboardingStore {
|
||||
@ -62,6 +61,8 @@ Item {
|
||||
readonly property int authorizationState: mockDriver.authorizationState // enum Onboarding.ProgressState
|
||||
readonly property int restoreKeysExportState: mockDriver.restoreKeysExportState // enum Onboarding.ProgressState
|
||||
property int keycardRemainingPinAttempts: 5
|
||||
property int keycardRemainingPukAttempts: 5
|
||||
property var loginAccountsModel: emptyModel
|
||||
|
||||
function setPin(pin: string) {
|
||||
const valid = pin === mockDriver.existingPin
|
||||
@ -316,7 +317,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithPassword)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithPassword)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, mockDriver.dummyNewPassword)
|
||||
@ -414,7 +415,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithSeedphrase)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithSeedphrase)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, mockDriver.dummyNewPassword)
|
||||
@ -563,7 +564,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithKeycardNewSeedphrase)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithKeycardNewSeedphrase)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, "")
|
||||
@ -666,7 +667,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, "")
|
||||
@ -759,7 +760,7 @@ Item {
|
||||
}
|
||||
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithSeedphrase)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithSeedphrase)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, mockDriver.dummyNewPassword)
|
||||
@ -851,7 +852,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithSyncing)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithSyncing)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, "")
|
||||
@ -924,7 +925,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithKeycard)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithKeycard)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, "")
|
||||
@ -950,7 +951,7 @@ Item {
|
||||
}
|
||||
function test_loginScreen(data) {
|
||||
verify(!!controlUnderTest)
|
||||
controlUnderTest.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.onboardingStore.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.biometricsAvailable = data.biometrics // both available _and_ enabled for this profile
|
||||
controlUnderTest.restartFlow()
|
||||
|
||||
@ -1017,6 +1018,7 @@ Item {
|
||||
compare(resultData.password, data.password)
|
||||
|
||||
// verify validation & pass error
|
||||
console.log("---- passwords:", data.password, mockDriver.dummyNewPassword)
|
||||
tryCompare(passwordInput, "hasError", data.password !== mockDriver.dummyNewPassword)
|
||||
} else if (!!data.pin) { // keycard profile
|
||||
mockDriver.keycardState = Onboarding.KeycardState.NotEmpty // happy path; keycard ready
|
||||
@ -1053,7 +1055,7 @@ Item {
|
||||
} else { // manual PIN
|
||||
keyClickSequence(data.pin)
|
||||
if (data.pin !== mockDriver.existingPin) {
|
||||
expectFail(data.tag, "Wrong PIN entered, expected to fail to login")
|
||||
// Everything will still be called as with a good pin, the wrong pin return is async
|
||||
}
|
||||
}
|
||||
|
||||
@ -1076,7 +1078,7 @@ Item {
|
||||
}
|
||||
function test_loginScreen_launchesExternalFlow(data) {
|
||||
verify(!!controlUnderTest)
|
||||
controlUnderTest.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.onboardingStore.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.restartFlow()
|
||||
|
||||
const page = getCurrentPage(controlUnderTest.stack, LoginScreen)
|
||||
@ -1100,7 +1102,7 @@ Item {
|
||||
|
||||
function test_loginScreenLostKeycardSeedphraseLoginFlow() {
|
||||
verify(!!controlUnderTest)
|
||||
controlUnderTest.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.onboardingStore.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.biometricsAvailable = false
|
||||
controlUnderTest.restartFlow()
|
||||
|
||||
@ -1174,7 +1176,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithLostKeycardSeedphrase)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithLostKeycardSeedphrase)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.password, mockDriver.dummyNewPassword)
|
||||
@ -1185,7 +1187,7 @@ Item {
|
||||
|
||||
function test_loginScreenLostKeycardCreateReplacementFlow() {
|
||||
verify(!!controlUnderTest)
|
||||
controlUnderTest.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.onboardingStore.loginAccountsModel = loginAccountsModel
|
||||
controlUnderTest.biometricsAvailable = false
|
||||
controlUnderTest.restartFlow()
|
||||
|
||||
@ -1260,7 +1262,7 @@ Item {
|
||||
|
||||
// FINISH
|
||||
tryCompare(finishedSpy, "count", 1)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.SecondaryFlow.LoginWithRestoredKeycard)
|
||||
compare(finishedSpy.signalArguments[0][0], Onboarding.OnboardingFlow.LoginWithRestoredKeycard)
|
||||
const resultData = finishedSpy.signalArguments[0][1]
|
||||
verify(!!resultData)
|
||||
compare(resultData.enableBiometrics, false)
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
Login
|
||||
};
|
||||
|
||||
enum class SecondaryFlow {
|
||||
enum class OnboardingFlow {
|
||||
Unknown,
|
||||
|
||||
CreateProfileWithPassword,
|
||||
@ -50,6 +50,7 @@ public:
|
||||
};
|
||||
|
||||
enum class LoginMethod {
|
||||
Unknown,
|
||||
Password,
|
||||
Keycard,
|
||||
};
|
||||
@ -82,7 +83,7 @@ public:
|
||||
|
||||
private:
|
||||
Q_ENUM(PrimaryFlow)
|
||||
Q_ENUM(SecondaryFlow)
|
||||
Q_ENUM(OnboardingFlow)
|
||||
Q_ENUM(LoginMethod)
|
||||
Q_ENUM(KeycardState)
|
||||
Q_ENUM(ProgressState)
|
||||
|
@ -166,7 +166,7 @@ SQUtils.QObject {
|
||||
CreateProfilePage {
|
||||
onCreateProfileWithPasswordRequested: createNewProfileFlow.init()
|
||||
onCreateProfileWithSeedphraseRequested: {
|
||||
d.flow = Onboarding.SecondaryFlow.CreateProfileWithSeedphrase
|
||||
d.flow = Onboarding.OnboardingFlow.CreateProfileWithSeedphrase
|
||||
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.NewProfile)
|
||||
}
|
||||
onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init()
|
||||
@ -183,7 +183,7 @@ SQUtils.QObject {
|
||||
onLoginWithKeycardRequested: loginWithKeycardFlow.init()
|
||||
|
||||
onLoginWithSeedphraseRequested: {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithSeedphrase
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithSeedphrase
|
||||
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.Login)
|
||||
}
|
||||
}
|
||||
@ -194,12 +194,12 @@ SQUtils.QObject {
|
||||
|
||||
KeycardLostPage {
|
||||
onCreateReplacementKeycardRequested: {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithRestoredKeycard
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithRestoredKeycard
|
||||
keycardCreateReplacementFlow.init()
|
||||
}
|
||||
|
||||
onUseProfileWithoutKeycardRequested: {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithLostKeycardSeedphrase
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithLostKeycardSeedphrase
|
||||
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.KeycardRecovery)
|
||||
}
|
||||
}
|
||||
@ -213,7 +213,7 @@ SQUtils.QObject {
|
||||
|
||||
onFinished: (password) => {
|
||||
root.setPasswordRequested(password)
|
||||
d.flow = Onboarding.SecondaryFlow.CreateProfileWithPassword
|
||||
d.flow = Onboarding.OnboardingFlow.CreateProfileWithPassword
|
||||
d.pushOrSkipBiometricsPage()
|
||||
}
|
||||
}
|
||||
@ -263,8 +263,8 @@ SQUtils.QObject {
|
||||
|
||||
onFinished: (withNewSeedphrase) => {
|
||||
d.flow = withNewSeedphrase
|
||||
? Onboarding.SecondaryFlow.CreateProfileWithKeycardNewSeedphrase
|
||||
: Onboarding.SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase
|
||||
? Onboarding.OnboardingFlow.CreateProfileWithKeycardNewSeedphrase
|
||||
: Onboarding.OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase
|
||||
|
||||
d.pushOrSkipBiometricsPage()
|
||||
}
|
||||
@ -281,12 +281,12 @@ SQUtils.QObject {
|
||||
root.syncProceedWithConnectionString(connectionString)
|
||||
|
||||
onLoginWithSeedphraseRequested: {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithSeedphrase
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithSeedphrase
|
||||
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.Login)
|
||||
}
|
||||
|
||||
onFinished: {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithSyncing
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithSyncing
|
||||
d.pushOrSkipBiometricsPage()
|
||||
}
|
||||
}
|
||||
@ -314,7 +314,7 @@ SQUtils.QObject {
|
||||
onUnblockWithPukRequested: unblockWithPukFlow.init()
|
||||
|
||||
onFinished: {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithKeycard
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithKeycard
|
||||
d.pushOrSkipBiometricsPage()
|
||||
}
|
||||
}
|
||||
@ -365,7 +365,7 @@ SQUtils.QObject {
|
||||
root.loginRequested(root.loginScreen.selectedProfileKeyId,
|
||||
Onboarding.LoginMethod.Keycard, { pin })
|
||||
} else {
|
||||
d.flow = Onboarding.SecondaryFlow.LoginWithKeycard
|
||||
d.flow = Onboarding.OnboardingFlow.LoginWithKeycard
|
||||
d.pushOrSkipBiometricsPage()
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,6 @@ Page {
|
||||
|
||||
required property OnboardingStore onboardingStore
|
||||
|
||||
// [{keyUid:string, username:string, thumbnailImage:string, colorId:int, colorHash:var, order:int, keycardCreatedAccount:bool}]
|
||||
// NB: this also decides whether we show the Login screen (if not empty), or the Onboarding
|
||||
required property var loginAccountsModel
|
||||
|
||||
property bool biometricsAvailable: Qt.platform.os === Constants.mac
|
||||
property bool isBiometricsLogin // FIXME should come from the loginAccountsModel for each profile separately?
|
||||
signal biometricsRequested() // emitted when the user wants to try the biometrics prompt again
|
||||
@ -33,7 +29,7 @@ Page {
|
||||
|
||||
signal shareUsageDataRequested(bool enabled)
|
||||
|
||||
// flow: Onboarding.SecondaryFlow
|
||||
// flow: Onboarding.OnboardingFlow
|
||||
signal finished(int flow, var data)
|
||||
|
||||
// -> "keyUid:string": User ID to login; "method:int": password or keycard (cf Onboarding.LoginMethod.*) enum;
|
||||
@ -153,7 +149,7 @@ Page {
|
||||
|
||||
stackView: stack
|
||||
|
||||
loginAccountsModel: root.loginAccountsModel
|
||||
loginAccountsModel: root.onboardingStore.loginAccountsModel
|
||||
|
||||
keycardState: root.onboardingStore.keycardState
|
||||
pinSettingState: root.onboardingStore.pinSettingState
|
||||
@ -220,22 +216,10 @@ Page {
|
||||
function onAccountLoginError(error: string, wrongPassword: bool) {
|
||||
const loginScreen = onboardingFlow.loginScreen
|
||||
|
||||
if (!error || !loginScreen || loginScreen.currentProfileIsKeycard)
|
||||
if (!loginScreen)
|
||||
return
|
||||
|
||||
let validationError
|
||||
let detailedError
|
||||
|
||||
// SQLITE_NOTADB: "file is not a database"
|
||||
if (error.includes("file is not a database") || wrongPassword) {
|
||||
validationError = qsTr("Password incorrect. %1").arg("<a href='#password'>" + qsTr("Forgot password?") + "</a>")
|
||||
detailedError = ""
|
||||
} else {
|
||||
validationError = qsTr("Login failed. %1").arg("<a href='#details'>" + qsTr("Show details.") + "</a>")
|
||||
detailedError = error
|
||||
}
|
||||
|
||||
loginScreen.setError(validationError, detailedError)
|
||||
loginScreen.setAccountLoginError(error, wrongPassword)
|
||||
}
|
||||
|
||||
// biometrics
|
||||
|
@ -14,9 +14,9 @@ Control {
|
||||
id: root
|
||||
|
||||
required property int keycardState
|
||||
property var tryToSetPinFunction: (pin) => { console.error("LoginKeycardBox::tryToSetPinFunction: IMPLEMENT ME"); return false }
|
||||
required property int keycardRemainingPinAttempts
|
||||
required property int keycardRemainingPukAttempts
|
||||
property string loginError
|
||||
|
||||
required property bool isBiometricsLogin
|
||||
required property bool biometricsSuccessful
|
||||
@ -37,6 +37,12 @@ Control {
|
||||
pinInputField.forceFocus()
|
||||
}
|
||||
|
||||
function markAsWrongPin() {
|
||||
d.wrongPin = true
|
||||
pinInputField.statesInitialization()
|
||||
pinInputField.forceFocus()
|
||||
}
|
||||
|
||||
function setPin(pin: string) {
|
||||
pinInputField.setPin(pin)
|
||||
}
|
||||
@ -106,14 +112,7 @@ Control {
|
||||
|
||||
onPinInputChanged: {
|
||||
if (pinInput.length === 6) {
|
||||
if (root.tryToSetPinFunction(pinInput)) {
|
||||
root.loginRequested(pinInput)
|
||||
d.wrongPin = false
|
||||
} else {
|
||||
d.wrongPin = true
|
||||
pinInputField.statesInitialization()
|
||||
pinInputField.forceFocus()
|
||||
}
|
||||
root.loginRequested(pinInput)
|
||||
}
|
||||
}
|
||||
onPinEditedManually: {
|
||||
@ -157,7 +156,7 @@ Control {
|
||||
PropertyChanges {
|
||||
target: infoText
|
||||
color: Theme.palette.dangerColor1
|
||||
text: qsTr("Oops this isn’t a Keycard.<br>Remove card and insert a Keycard.")
|
||||
text: qsTr("Oops this isn't a Keycard.<br>Remove card and insert a Keycard.")
|
||||
}
|
||||
},
|
||||
State {
|
||||
@ -212,6 +211,16 @@ Control {
|
||||
text: qsTr("PIN incorrect. %n attempt(s) remaining.", "", root.keycardRemainingPinAttempts)
|
||||
}
|
||||
},
|
||||
State {
|
||||
// TODO this is a deadend. We should never end up here, but I still don't know what it should look like
|
||||
name: "errorDuringLogin"
|
||||
when: !!root.loginError
|
||||
PropertyChanges {
|
||||
target: infoText
|
||||
color: Theme.palette.dangerColor1
|
||||
text: qsTr("Error during login: %1").arg(root.loginError)
|
||||
}
|
||||
},
|
||||
// exit states
|
||||
State {
|
||||
name: "notEmpty"
|
||||
|
@ -121,6 +121,35 @@ OnboardingPage {
|
||||
passwordBox.detailedError = detailedError
|
||||
}
|
||||
|
||||
// (password) login
|
||||
function setAccountLoginError(error: string, wrongPassword: bool) {
|
||||
if (!error) {
|
||||
return
|
||||
}
|
||||
|
||||
if (d.currentProfileIsKeycard) {
|
||||
// Login with keycard
|
||||
if (wrongPassword) {
|
||||
keycardBox.markAsWrongPin()
|
||||
} else {
|
||||
keycardBox.loginError = error
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Login with password
|
||||
if (wrongPassword) {
|
||||
passwordBox.validationError = qsTr("Password incorrect. %1").arg("<a href='#password'>" + qsTr("Forgot password?") + "</a>")
|
||||
passwordBox.detailedError = ""
|
||||
} else {
|
||||
passwordBox.validationError = qsTr("Login failed. %1").arg("<a href='#details'>" + qsTr("Show details.") + "</a>")
|
||||
passwordBox.detailedError = error
|
||||
}
|
||||
|
||||
passwordBox.clear()
|
||||
passwordBox.forceActiveFocus()
|
||||
}
|
||||
|
||||
padding: 40
|
||||
|
||||
contentItem: Item {
|
||||
@ -202,7 +231,6 @@ OnboardingPage {
|
||||
biometricsSuccessful: d.biometricsSuccessful
|
||||
biometricsFailed: d.biometricsFailed
|
||||
keycardState: root.keycardState
|
||||
tryToSetPinFunction: root.tryToSetPinFunction
|
||||
keycardRemainingPinAttempts: root.keycardRemainingPinAttempts
|
||||
keycardRemainingPukAttempts: root.keycardRemainingPukAttempts
|
||||
onUnblockWithSeedphraseRequested: root.unblockWithSeedphraseRequested()
|
||||
|
@ -8,6 +8,7 @@ QtObject {
|
||||
id: root
|
||||
|
||||
signal appLoaded
|
||||
|
||||
readonly property QtObject d: StatusQUtils.QObject {
|
||||
id: d
|
||||
readonly property var onboardingModuleInst: onboardingModule
|
||||
@ -18,6 +19,8 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var loginAccountsModel: d.onboardingModuleInst.loginAccountsModel
|
||||
|
||||
// keycard
|
||||
readonly property int keycardState: d.onboardingModuleInst.keycardState // cf. enum Onboarding.KeycardState
|
||||
readonly property int pinSettingState: d.onboardingModuleInst.pinSettingState // cf. enum Onboarding.ProgressState
|
||||
@ -30,6 +33,10 @@ QtObject {
|
||||
return d.onboardingModuleInst.finishOnboardingFlow(flow, JSON.stringify(data))
|
||||
}
|
||||
|
||||
function loginRequested(keyUid: string, method: int, data: Object) { // -> void
|
||||
d.onboardingModuleInst.loginRequested(keyUid, method, JSON.stringify(data))
|
||||
}
|
||||
|
||||
function setPin(pin: string) {
|
||||
d.onboardingModuleInst.setPin(pin)
|
||||
}
|
||||
|
35
ui/main.qml
35
ui/main.qml
@ -193,7 +193,6 @@ StatusWindow {
|
||||
}
|
||||
startupOnboardingLoader.item.unload()
|
||||
startupOnboardingLoader.active = false
|
||||
onboardingStoreLoader.active = false
|
||||
|
||||
Theme.changeTheme(localAppSettings.theme, systemPalette.isCurrentSystemThemeDark())
|
||||
Theme.changeFontSize(localAccountSensitiveSettings.fontSize)
|
||||
@ -407,15 +406,6 @@ StatusWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: onboardingStoreLoader
|
||||
active: featureFlagsStore.onboardingV2Enabled
|
||||
|
||||
sourceComponent: OnboardingStore {
|
||||
onAppLoaded: moveToAppMain()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: startupOnboardingLoader
|
||||
anchors.fill: parent
|
||||
@ -443,32 +433,33 @@ StatusWindow {
|
||||
id: onboardingV2
|
||||
|
||||
Onboarding2.OnboardingLayout {
|
||||
id: onboardingLayout
|
||||
objectName: "startupOnboardingLayout"
|
||||
anchors.fill: parent
|
||||
|
||||
// TODO implement those two
|
||||
loginAccountsModel: ListModel {}
|
||||
isBiometricsLogin: false
|
||||
networkChecksEnabled: true
|
||||
biometricsAvailable: Qt.platform.os === Constants.mac
|
||||
|
||||
onboardingStore: onboardingStoreLoader.item
|
||||
onboardingStore: onboardingStore
|
||||
|
||||
onFinished: (flow, data) => {
|
||||
console.warn("!!! ONBOARDING FINISHED; flow:", flow, "; data:", JSON.stringify(data))
|
||||
|
||||
let error = onboardingStoreLoader.item.finishOnboardingFlow(flow, data)
|
||||
const error = onboardingStore.finishOnboardingFlow(flow, data)
|
||||
|
||||
if (error != "") {
|
||||
// We should never be here since everything should be validated already
|
||||
console.error("!!! ONBOARDING FINISHED WITH ERROR:", error)
|
||||
// TODO show error
|
||||
return
|
||||
}
|
||||
console.warn("!!! Onboarding completed!")
|
||||
stack.clear()
|
||||
stack.push(splashScreenV2, { runningProgressAnimation: true })
|
||||
}
|
||||
|
||||
onLoginRequested: function (keyUid, method, data) {
|
||||
stack.push(splashScreenV2, { runningProgressAnimation: true })
|
||||
onboardingStore.loginRequested(keyUid, method, data)
|
||||
}
|
||||
|
||||
onShareUsageDataRequested: {
|
||||
applicationWindow.metricsStore.toggleCentralizedMetrics(enabled)
|
||||
if (enabled) {
|
||||
@ -476,6 +467,14 @@ StatusWindow {
|
||||
}
|
||||
}
|
||||
onCurrentPageNameChanged: Global.addCentralizedMetricIfEnabled("navigation", {viewId: currentPageName})
|
||||
|
||||
OnboardingStore {
|
||||
id: onboardingStore
|
||||
onAppLoaded: moveToAppMain()
|
||||
onAccountLoginError: function (error, wrongPassword) {
|
||||
onboardingLayout.stack.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
2
vendor/status-keycard-go
vendored
2
vendor/status-keycard-go
vendored
@ -1 +1 @@
|
||||
Subproject commit 84f3577ca011b094578643d5f206848cbb20178e
|
||||
Subproject commit 75e09c9ec1911b1422ff1a4371732500ae0ad60a
|
Loading…
x
Reference in New Issue
Block a user