fix(@desktop/keycard): migrating keypair looks somehow stucked for a while before switching to `Migrating key pair to Keycard` state

Fixes: #8177
This commit is contained in:
Sale Djenic 2022-11-14 11:24:16 +01:00 committed by saledjenic
parent db7769b072
commit 557703543c
11 changed files with 232 additions and 62 deletions

View File

@ -138,7 +138,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration,
result.settingsService)
result.keychainService = keychain_service.newService(statusFoundation.events)
result.accountsService = accounts_service.newService(statusFoundation.fleetConfiguration)
result.accountsService = accounts_service.newService(statusFoundation.events, statusFoundation.threadpool,
statusFoundation.fleetConfiguration)
result.networkService = network_service.newService(statusFoundation.events, result.settingsService)
result.contactsService = contacts_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService,

View File

@ -49,6 +49,8 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
let args = KeycardActivityArgs(e)
if not args.success:
return
self.delegate.onNewKeycardSet(args.keyPair)
self.events.on(SIGNAL_KEYCARD_LOCKED) do(e: Args):

View File

@ -167,6 +167,9 @@ method load*(self: Module) =
self.refreshWalletAccounts()
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
let args = KeycardActivityArgs(e)
if not args.success:
return
self.refreshWalletAccounts()
self.controller.init()

View File

@ -51,6 +51,8 @@ type
tmpUsePinFromBiometrics: bool
tmpOfferToStoreUpdatedPinToKeychain: bool
tmpKeycardUid: string
tmpAddingMigratedKeypairSuccess: bool
tmpConvertingProfileSuccess: bool
proc newController*(delegate: io_interface.AccessInterface,
uniqueIdentifier: string,
@ -78,6 +80,8 @@ proc newController*(delegate: io_interface.AccessInterface,
result.tmpSeedPhraseLength = 0
result.tmpSelectedKeyPairIsProfile = false
result.tmpUsePinFromBiometrics = false
result.tmpAddingMigratedKeypairSuccess = false
result.tmpConvertingProfileSuccess = false
proc serviceApplicable[T](service: T): bool =
if not service.isNil:
@ -141,6 +145,16 @@ proc init*(self: Controller) =
self.delegate.onUserAuthenticated(args.password, args.pin)
self.connectionIds.add(handlerId)
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
let args = KeycardActivityArgs(e)
self.tmpAddingMigratedKeypairSuccess = args.success
self.delegate.onSecondaryActionClicked()
self.events.on(SIGNAL_CONVERTING_PROFILE_KEYPAIR) do(e: Args):
let args = ResultArgs(e)
self.tmpConvertingProfileSuccess = args.success
self.delegate.onSecondaryActionClicked()
proc getKeycardData*(self: Controller): string =
return self.delegate.getKeycardData()
@ -289,15 +303,18 @@ proc verifyPassword*(self: Controller, password: string): bool =
return
return self.accountsService.verifyPassword(password)
proc convertSelectedKeyPairToKeycardAccount*(self: Controller, password: string): bool =
proc convertSelectedKeyPairToKeycardAccount*(self: Controller, password: string) =
if not serviceApplicable(self.accountsService):
return
let acc = self.accountsService.createAccountFromMnemonic(self.getSeedPhrase(), includeEncryption = true)
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW)
return self.accountsService.convertToKeycardAccount(self.tmpSelectedKeyPairDto.keyUid,
self.accountsService.convertToKeycardAccount(self.tmpSelectedKeyPairDto.keyUid,
currentPassword = password,
newPassword = acc.derivedAccounts.encryption.publicKey)
proc getConvertingProfileSuccess*(self: Controller): bool =
return self.tmpConvertingProfileSuccess
proc getLoggedInAccount*(self: Controller): AccountDto =
if not serviceApplicable(self.accountsService):
return
@ -429,8 +446,8 @@ proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) =
let (_, flowEvent) = self.getLastReceivedKeycardData()
var data = SharedKeycarModuleFlowTerminatedArgs(uniqueIdentifier: self.uniqueIdentifier,
lastStepInTheCurrentFlow: lastStepInTheCurrentFlow)
let exportedEncryptionPubKey = flowEvent.generatedWalletAccount.publicKey
if lastStepInTheCurrentFlow:
let exportedEncryptionPubKey = flowEvent.generatedWalletAccount.publicKey
data.password = if exportedEncryptionPubKey.len > 0: exportedEncryptionPubKey else: self.getPassword()
data.pin = self.getPin()
data.keyUid = flowEvent.keyUid
@ -452,13 +469,16 @@ proc getBalanceForAddress*(self: Controller, address: string): float64 =
return
return self.walletAccountService.fetchBalanceForAddress(address)
proc addMigratedKeyPair*(self: Controller, keyPair: KeyPairDto): bool =
proc addMigratedKeyPair*(self: Controller, keyPair: KeyPairDto) =
if not serviceApplicable(self.walletAccountService):
return
if not serviceApplicable(self.accountsService):
return
let keystoreDir = self.accountsService.getKeyStoreDir()
return self.walletAccountService.addMigratedKeyPair(keyPair, keystoreDir)
self.walletAccountService.addMigratedKeyPairAsync(keyPair, keystoreDir)
proc getAddingMigratedKeypairSuccess*(self: Controller): bool =
return self.tmpAddingMigratedKeypairSuccess
proc getAllMigratedKeyPairs*(self: Controller): seq[KeyPairDto] =
if not serviceApplicable(self.walletAccountService):

View File

@ -1,33 +1,36 @@
type
MigratingKeyPairState* = ref object of State
migrationSuccess: bool
authenticationDone: bool
authenticationOk: bool
addingMigratedKeypairDone: bool
addingMigratedKeypairOk: bool
profileConversionDone: bool
profileConversionOk: bool
proc newMigratingKeyPairState*(flowType: FlowType, backState: State): MigratingKeyPairState =
result = MigratingKeyPairState()
result.setup(flowType, StateType.MigratingKeyPair, backState)
result.migrationSuccess = false
result.authenticationDone = false
result.authenticationOk = false
result.addingMigratedKeypairDone = false
result.addingMigratedKeypairOk = false
result.profileConversionDone = false
result.profileConversionOk = false
proc delete*(self: MigratingKeyPairState) =
self.State.delete
proc doMigration(self: MigratingKeyPairState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
let password = controller.getPassword()
controller.setPassword("")
if controller.getSelectedKeyPairIsProfile():
self.migrationSuccess = controller.verifyPassword(password)
if not self.migrationSuccess:
return
let selectedKeyPairDto = controller.getSelectedKeyPairDto()
self.migrationSuccess = controller.addMigratedKeyPair(selectedKeyPairDto)
if not self.migrationSuccess:
return
if controller.getSelectedKeyPairIsProfile():
self.migrationSuccess = self.migrationSuccess and controller.convertSelectedKeyPairToKeycardAccount(password)
if not self.migrationSuccess:
return
controller.runStoreMetadataFlow(selectedKeyPairDto.keycardName, controller.getPin(),
controller.getSelectedKeyPairWalletPaths())
controller.addMigratedKeyPair(selectedKeyPairDto)
proc doConversion(self: MigratingKeyPairState, controller: Controller) =
let password = controller.getPassword()
controller.convertSelectedKeyPairToKeycardAccount(password)
proc runStoreMetadataFlow(self: MigratingKeyPairState, controller: Controller) =
let selectedKeyPairDto = controller.getSelectedKeyPairDto()
controller.runStoreMetadataFlow(selectedKeyPairDto.keycardName, controller.getPin(), controller.getSelectedKeyPairWalletPaths())
method executePrePrimaryStateCommand*(self: MigratingKeyPairState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard:
@ -37,12 +40,39 @@ method executePrePrimaryStateCommand*(self: MigratingKeyPairState, controller: C
self.doMigration(controller)
method executePreSecondaryStateCommand*(self: MigratingKeyPairState, controller: Controller) =
## Secondary action is called after each async action during migration process.
if self.flowType == FlowType.SetupNewKeycard:
if controller.getSelectedKeyPairIsProfile():
if not self.authenticationDone:
self.authenticationDone = true
let password = controller.getPassword()
self.authenticationOk = controller.verifyPassword(password)
if self.authenticationOk:
self.doMigration(controller)
return
if not self.addingMigratedKeypairDone:
self.addingMigratedKeypairDone = true
self.addingMigratedKeypairOk = controller.getAddingMigratedKeypairSuccess()
if self.addingMigratedKeypairOk:
self.doConversion(controller)
return
if not self.profileConversionDone:
self.profileConversionDone = true
self.profileConversionOk = controller.getConvertingProfileSuccess()
if self.profileConversionOk:
self.runStoreMetadataFlow(controller)
else:
if not self.addingMigratedKeypairDone:
self.addingMigratedKeypairDone = true
self.addingMigratedKeypairOk = controller.getAddingMigratedKeypairSuccess()
if self.addingMigratedKeypairOk:
self.runStoreMetadataFlow(controller)
method getNextSecondaryState*(self: MigratingKeyPairState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard:
if not self.migrationSuccess:
if self.authenticationDone and not self.authenticationOk or
self.addingMigratedKeypairDone and not self.addingMigratedKeypairOk or
self.profileConversionDone and not self.profileConversionOk:
return createState(StateType.KeyPairMigrateFailure, self.flowType, nil)
method resolveKeycardNextState*(self: MigratingKeyPairState, keycardFlowType: string, keycardEvent: KeycardEvent,

View File

@ -0,0 +1,21 @@
#################################################
# Async convert profile keypair
#################################################
type
ConvertToKeycardAccountTaskArg* = ref object of QObjectTaskArg
accountDataJson: JsonNode
settingsJson: JsonNode
hashedCurrentPassword: string
newPassword: string
keyStoreDir: string
const convertToKeycardAccountTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[ConvertToKeycardAccountTaskArg](argEncoded)
try:
let response = status_account.convertToKeycardAccount(arg.keyStoreDir, arg.accountDataJson, arg.settingsJson,
arg.hashedCurrentPassword, arg.newPassword)
arg.finish(response)
except Exception as e:
error "error converting profile keypair: ", message = e.msg
arg.finish("")

View File

@ -8,6 +8,9 @@ from ../keycard/service import KeycardEvent, KeyDetails
import ../../../backend/general as status_general
import ../../../backend/core as status_core
import ../../../app/core/eventemitter
import ../../../app/core/signals/types
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/fleets/fleet_configuration
import ../../common/[account_constants, network_constants, utils, string_utils]
import ../../../constants as main_constants
@ -30,10 +33,18 @@ const KDF_ITERATIONS* {.intdefine.} = 256_000
# specific peer to set for testing messaging and mailserver functionality with squish.
let TEST_PEER_ENR = getEnv("TEST_PEER_ENR").string
const SIGNAL_CONVERTING_PROFILE_KEYPAIR* = "convertingProfileKeypair"
type ResultArgs* = ref object of Args
success*: bool
include utils
include async_tasks
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
fleetConfiguration: FleetConfiguration
generatedAccounts: seq[GeneratedAccountDto]
accounts: seq[AccountDto]
@ -46,10 +57,11 @@ QtObject:
proc delete*(self: Service) =
self.QObject.delete
proc newService*(fleetConfiguration: FleetConfiguration): Service =
result = Service()
proc newService*(events: EventEmitter, threadpool: ThreadPool, fleetConfiguration: FleetConfiguration): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.fleetConfiguration = fleetConfiguration
result.isFirstTimeAccountLogin = false
result.keyStoreDir = main_constants.ROOTKEYSTOREDIR
@ -677,8 +689,7 @@ QtObject:
error "error: ", procName="verifyAccountPassword", errName = e.name, errDesription = e.msg
proc convertToKeycardAccount*(self: Service, keyUid: string, currentPassword: string, newPassword: string): bool =
try:
proc convertToKeycardAccount*(self: Service, keyUid: string, currentPassword: string, newPassword: string) =
var accountDataJson = %* {
"name": self.getLoggedInAccount().name,
"key-uid": keyUid
@ -695,18 +706,31 @@ QtObject:
return
let hashedCurrentPassword = hashString(currentPassword)
let response = status_account.convertToKeycardAccount(self.keyStoreDir, accountDataJson, settingsJson,
hashedCurrentPassword, newPassword)
let arg = ConvertToKeycardAccountTaskArg(
tptr: cast[ByteAddress](convertToKeycardAccountTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onConvertToKeycardAccount",
accountDataJson: accountDataJson,
settingsJson: settingsJson,
keyStoreDir: self.keyStoreDir,
hashedCurrentPassword: hashedCurrentPassword,
newPassword: newPassword
)
self.threadpool.start(arg)
if(response.result.contains("error")):
let errMsg = response.result["error"].getStr
proc onConvertToKeycardAccount*(self: Service, response: string) {.slot.} =
var result = false
try:
let rpcResponse = Json.decode(response, RpcResponse[JsonNode])
if(rpcResponse.result.contains("error")):
let errMsg = rpcResponse.result["error"].getStr
if(errMsg.len == 0):
return true
result = true
else:
error "error: ", procName="convertToKeycardAccount", errDesription = errMsg
return false
except Exception as e:
error "error: ", procName="convertToKeycardAccount", errName = e.name, errDesription = e.msg
error "error handilng migrated keypair response", errDesription=e.msg
self.events.emit(SIGNAL_CONVERTING_PROFILE_KEYPAIR, ResultArgs(success: result))
proc verifyPassword*(self: Service, password: string): bool =
try:

View File

@ -443,3 +443,26 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
arg.finish(builtTokensPerAccount)
#################################################
# Async add migrated keypair
#################################################
type
AddMigratedKeyPairTaskArg* = ref object of QObjectTaskArg
keyPair: KeyPairDto
keyStoreDir: string
const addMigratedKeyPairTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AddMigratedKeyPairTaskArg](argEncoded)
try:
let response = backend.addMigratedKeyPair(
arg.keyPair.keycardUid,
arg.keyPair.keycardName,
arg.keyPair.keyUid,
arg.keyPair.accountsAddresses,
arg.keyStoreDir
)
arg.finish(response)
except Exception as e:
error "error adding new keypair: ", message = e.msg
arg.finish("")

View File

@ -89,6 +89,7 @@ type TokensPerAccountArgs* = ref object of Args
accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens]
type KeycardActivityArgs* = ref object of Args
success*: bool
keycardUid*: string
keycardNewUid*: string
keycardNewName*: string
@ -113,6 +114,7 @@ QtObject:
walletAccounts: OrderedTable[string, WalletAccountDto]
timerStartTimeInSeconds: int64
priceCache: TimedCache
processedKeyPair: KeyPairDto
# Forward declaration
proc buildAllTokens(self: Service, calledFromTimerOrInit = false)
@ -547,6 +549,28 @@ QtObject:
error "error: ", procName=procName, errDesription = errMsg
return false
proc addMigratedKeyPairAsync*(self: Service, keyPair: KeyPairDto, keyStoreDir: string) =
let arg = AddMigratedKeyPairTaskArg(
tptr: cast[ByteAddress](addMigratedKeyPairTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onMigratedKeyPairAdded",
keyPair: keyPair,
keyStoreDir: keyStoreDir
)
self.processedKeyPair = keyPair
self.threadpool.start(arg)
proc onMigratedKeyPairAdded*(self: Service, response: string) {.slot.} =
var result = false
try:
let rpcResponse = Json.decode(response, RpcResponse[JsonNode])
result = self.responseHasNoErrors("addMigratedKeyPair", rpcResponse)
except Exception as e:
error "error handilng migrated keypair response", errDesription=e.msg
let data = KeycardActivityArgs(success: result, keyPair: self.processedKeyPair)
self.processedKeyPair = KeyPairDto()
self.events.emit(SIGNAL_NEW_KEYCARD_SET, data)
proc addMigratedKeyPair*(self: Service, keyPair: KeyPairDto, keyStoreDir: string): bool =
try:
let response = backend.addMigratedKeyPair(
@ -558,7 +582,7 @@ QtObject:
)
result = self.responseHasNoErrors("addMigratedKeyPair", response)
if result:
self.events.emit(SIGNAL_NEW_KEYCARD_SET, KeycardActivityArgs(keyPair: keyPair))
self.events.emit(SIGNAL_NEW_KEYCARD_SET, KeycardActivityArgs(success: true, keyPair: keyPair))
except Exception as e:
error "error: ", procName="addMigratedKeyPair", errName = e.name, errDesription = e.msg

View File

@ -26,6 +26,7 @@ Item {
id: d
readonly property bool hideKeyPair: root.sharedKeycardModule.keycardData & Constants.predefinedKeycardData.hideKeyPair
readonly property bool continuousProcessingAnimation: root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.migratingKeyPair
}
Timer {
@ -484,13 +485,25 @@ Item {
}
PropertyChanges {
target: image
pattern: Constants.keycardAnimations.warning.pattern
pattern: d.continuousProcessingAnimation?
Constants.keycardAnimations.processing.pattern :
Constants.keycardAnimations.warning.pattern
source: ""
startImgIndexForTheFirstLoop: Constants.keycardAnimations.warning.startImgIndexForTheFirstLoop
startImgIndexForOtherLoops: Constants.keycardAnimations.warning.startImgIndexForOtherLoops
endImgIndex: Constants.keycardAnimations.warning.endImgIndex
duration: Constants.keycardAnimations.warning.duration
loops: Constants.keycardAnimations.warning.loops
startImgIndexForTheFirstLoop: d.continuousProcessingAnimation?
Constants.keycardAnimations.processing.startImgIndexForTheFirstLoop :
Constants.keycardAnimations.warning.startImgIndexForTheFirstLoop
startImgIndexForOtherLoops: d.continuousProcessingAnimation?
Constants.keycardAnimations.processing.startImgIndexForOtherLoops :
Constants.keycardAnimations.warning.startImgIndexForOtherLoops
endImgIndex: d.continuousProcessingAnimation?
Constants.keycardAnimations.processing.endImgIndex :
Constants.keycardAnimations.warning.endImgIndex
duration: d.continuousProcessingAnimation?
Constants.keycardAnimations.processing.duration :
Constants.keycardAnimations.warning.duration
loops: d.continuousProcessingAnimation?
Constants.keycardAnimations.processing.loops :
Constants.keycardAnimations.warning.loops
}
PropertyChanges {
target: message

View File

@ -196,6 +196,15 @@ QtObject {
readonly property int loops: 1
}
readonly property QtObject processing: QtObject {
readonly property string pattern: "keycard/warning/img-%1"
readonly property int startImgIndexForTheFirstLoop: 0
readonly property int startImgIndexForOtherLoops: 18
readonly property int endImgIndex: 47
readonly property int duration: 1500
readonly property int loops: -1
}
readonly property QtObject strongError: QtObject {
readonly property string pattern: "keycard/strong_error/img-%1"
readonly property int startImgIndexForTheFirstLoop: 0