feat(@desktop/syncing): generating connection string and inserting connection string for transferring keystore files introduced

Refers to the third part of #11779
This commit is contained in:
Sale Djenic 2023-08-21 12:56:55 +02:00 committed by saledjenic
parent c207a4aefc
commit 85d4bfdfea
12 changed files with 255 additions and 75 deletions

View File

@ -215,7 +215,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.privacyService = privacy_service.newService(statusFoundation.events, result.settingsService,
result.accountsService)
result.savedAddressService = saved_address_service.newService(statusFoundation.events, result.networkService, result.settingsService)
result.devicesService = devices_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService)
result.devicesService = devices_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.accountsService, result.walletAccountService)
result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration)
result.nodeService = node_service.newService(statusFoundation.events, result.settingsService, result.nodeConfigurationService)

View File

@ -1,8 +1,8 @@
import json, tables, chronicles
import json, tables, sequtils, sugar, chronicles
import base
import ../../../../app_service/service/accounts/dto/accounts
import ../../../../app_service/service/devices/dto/installation
import ../../../../app_service/service/devices/dto/local_pairing_event
import app_service/service/accounts/dto/accounts
import app_service/service/devices/dto/installation
import app_service/service/devices/dto/local_pairing_event
type LocalPairingSignal* = ref object of Signal
@ -11,6 +11,7 @@ type LocalPairingSignal* = ref object of Signal
error*: string
accountData*: LocalPairingAccountData
installation*: InstallationDto
transferredKeypairs*: seq[string] ## seq[keypair_key_uid]
proc fromEvent*(T: type LocalPairingSignal, event: JsonNode): LocalPairingSignal =
result = LocalPairingSignal()
@ -29,5 +30,7 @@ proc fromEvent*(T: type LocalPairingSignal, event: JsonNode): LocalPairingSignal
result.accountData = e["data"].toLocalPairingAccountData()
of EventReceivedInstallation:
result.installation = e["data"].toInstallationDto()
of EventReceivedKeystoreFiles:
result.transferredKeypairs = map(e["data"].getElems(), x => x.getStr())
else:
discard

View File

@ -27,7 +27,7 @@ QtObject:
result.devicesLoadingError = false
result.model = newModel()
result.modelVariant = newQVariant(result.model)
result.localPairingStatus = newLocalPairingStatus()
result.localPairingStatus = newLocalPairingStatus(PairingType.AppSync, LocalPairingMode.Receiver)
proc load*(self: View) =
self.delegate.viewDidLoad()

View File

@ -69,7 +69,7 @@ QtObject:
result.loginAccountsModel = login_acc_model.newModel()
result.loginAccountsModelVariant = newQVariant(result.loginAccountsModel)
result.remainingAttempts = -1
result.localPairingStatus = newLocalPairingStatus()
result.localPairingStatus = newLocalPairingStatus(PairingType.AppSync, LocalPairingMode.Receiver)
signalConnect(result.currentStartupState, "backActionClicked()", result, "onBackActionClicked()", 2)
signalConnect(result.currentStartupState, "primaryActionClicked()", result, "onPrimaryActionClicked()", 2)

View File

@ -18,3 +18,8 @@ const asyncInputConnectionStringTask: Task = proc(argEncoded: string) {.gcsafe,
let arg = decode[AsyncInputConnectionStringArg](argEncoded)
let response = status_go.inputConnectionStringForBootstrapping(arg.connectionString, arg.configJSON)
arg.finish(response)
const asyncInputConnectionStringForImportingKeystoreTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncInputConnectionStringArg](argEncoded)
let response = status_go.inputConnectionStringForImportingKeypairsKeystores(arg.connectionString, arg.configJSON)
arg.finish(response)

View File

@ -6,23 +6,25 @@ import installation
type
EventType* {.pure.} = enum
EventUnknown = -1,
EventConnectionError = 0,
EventConnectionSuccess = 1,
EventTransferError = 2,
EventTransferSuccess = 3,
EventReceivedAccount = 4,
EventReceivedInstallation = 5
EventProcessSuccess = 6,
EventProcessError = 7
EventUnknown = -1
EventConnectionError
EventConnectionSuccess
EventTransferError
EventTransferSuccess
EventReceivedAccount
EventReceivedInstallation
EventProcessSuccess
EventProcessError
EventReceivedKeystoreFiles
type
Action* {.pure.} = enum
ActionUnknown = 0
ActionConnect = 1,
ActionPairingAccount = 2,
ActionSyncDevice = 3,
ActionPairingInstallation = 4,
ActionConnect
ActionPairingAccount
ActionSyncDevice
ActionPairingInstallation
ActionKeystoreFilesTransfer
type
LocalPairingAccountData* = ref object
@ -38,6 +40,7 @@ type
error*: string
accountData*: LocalPairingAccountData
installation*: InstallationDto
transferredKeypairs*: seq[string] ## seq[keypair_key_uid]
proc parse*(self: string): EventType =
case self:
@ -57,6 +60,8 @@ proc parse*(self: string): EventType =
return EventReceivedAccount
of "received-installation":
return EventReceivedInstallation
of "received-keystore-files":
return EventReceivedKeystoreFiles
else:
return EventUnknown

View File

@ -3,6 +3,11 @@ import ../../accounts/dto/accounts
import installation
import local_pairing_event
type
PairingType* {.pure.} = enum
AppSync = 0
KeypairSync
type
LocalPairingState* {.pure.} = enum
Idle = 0
@ -18,29 +23,25 @@ type
type
LocalPairingStatus* = ref object of Args
pairingType*: PairingType
mode*: LocalPairingMode
state*: LocalPairingState
account*: AccountDto
password*: string
chatKey*: string
installation*: InstallationDto
transferredKeypairs*: seq[string] ## seq[keypair_key_uid]
error*: string
proc reset*(self: LocalPairingStatus) =
self.mode = LocalPairingMode.Idle
self.state = LocalPairingState.Idle
self.error = ""
self.installation = InstallationDto()
proc setup(self: LocalPairingStatus) =
self.reset()
proc delete*(self: LocalPairingStatus) =
discard
proc newLocalPairingStatus*(): LocalPairingStatus =
proc newLocalPairingStatus*(pairingType: PairingType, mode: LocalPairingMode): LocalPairingStatus =
new(result, delete)
result.setup()
result.pairingType = pairingType
result.mode = mode
result.state = LocalPairingState.Idle
result.installation = InstallationDto()
proc update*(self: LocalPairingStatus, data: LocalPairingEventArgs) =
@ -54,6 +55,8 @@ proc update*(self: LocalPairingStatus, data: LocalPairingEventArgs) =
self.chatKey = data.accountData.chatKey
of EventReceivedInstallation:
self.installation = data.installation
of EventReceivedKeystoreFiles:
self.transferredKeypairs = data.transferredKeypairs
of EventConnectionError:
self.state = LocalPairingState.Error
of EventTransferError:
@ -67,15 +70,19 @@ proc update*(self: LocalPairingStatus, data: LocalPairingEventArgs) =
return
# Detect finished state
if (self.mode == LocalPairingMode.Sender and
data.eventType == EventProcessSuccess and
data.action == ActionPairingInstallation):
self.state = LocalPairingState.Finished
if self.mode == LocalPairingMode.Sender and
data.eventType == EventProcessSuccess:
if self.pairingType == PairingType.AppSync and
data.action == ActionPairingInstallation or
self.pairingType == PairingType.KeypairSync:
self.state = LocalPairingState.Finished
if (self.mode == LocalPairingMode.Receiver and
data.eventType == EventTransferSuccess and
data.action == ActionPairingInstallation):
self.state = LocalPairingState.Finished
if self.mode == LocalPairingMode.Receiver and
data.eventType == EventTransferSuccess:
if self.pairingType == PairingType.AppSync and
data.action == ActionPairingInstallation or
self.pairingType == PairingType.KeypairSync:
self.state = LocalPairingState.Finished
if self.state == LocalPairingState.Finished:
return

View File

@ -8,6 +8,7 @@ import ./dto/local_pairing_status
import app_service/service/settings/service as settings_service
import app_service/service/accounts/service as accounts_service
import app_service/service/wallet_account/service as wallet_account_service
import app/global/global_singleton
import app/core/[main]
@ -55,23 +56,26 @@ QtObject:
threadpool: ThreadPool
settingsService: settings_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
localPairingStatus: LocalPairingStatus
proc delete*(self: Service) =
self.QObject.delete
self.localPairingStatus.delete
if not self.localPairingStatus.isNil:
self.localPairingStatus.delete
proc newService*(events: EventEmitter,
threadpool: ThreadPool,
settingsService: settings_service.Service,
accountsService: accounts_service.Service): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.settingsService = settingsService
result.accountsService = accountsService
result.localPairingStatus = newLocalPairingStatus()
threadpool: ThreadPool,
settingsService: settings_service.Service,
accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.settingsService = settingsService
result.accountsService = accountsService
result.walletAccountService = walletAccountService
proc updateLocalPairingStatus(self: Service, data: LocalPairingEventArgs) =
self.localPairingStatus.update(data)
@ -98,15 +102,23 @@ QtObject:
self.events.on(SignalType.LocalPairing.event) do(e:Args):
let signalData = LocalPairingSignal(e)
if not signalData.accountData.isNil and signalData.accountData.keycardPairings.len > 0:
createKeycardPairingFile(signalData.accountData.keycardPairings)
let data = LocalPairingEventArgs(
eventType: signalData.eventType,
action: signalData.action,
accountData: signalData.accountData,
installation: signalData.installation,
error: signalData.error)
self.updateLocalPairingStatus(data)
if self.localPairingStatus.pairingType == PairingType.AppSync:
if not signalData.accountData.isNil and signalData.accountData.keycardPairings.len > 0:
createKeycardPairingFile(signalData.accountData.keycardPairings)
let data = LocalPairingEventArgs(
eventType: signalData.eventType,
action: signalData.action,
accountData: signalData.accountData,
installation: signalData.installation,
error: signalData.error)
self.updateLocalPairingStatus(data)
elif self.localPairingStatus.pairingType == PairingType.KeypairSync:
let data = LocalPairingEventArgs(
eventType: signalData.eventType,
action: signalData.action,
error: signalData.error,
transferredKeypairs: signalData.transferredKeypairs)
self.updateLocalPairingStatus(data)
proc init*(self: Service) =
self.doConnect()
@ -185,6 +197,10 @@ QtObject:
self.updateLocalPairingStatus(data)
proc validateConnectionString*(self: Service, connectionString: string): string =
if connectionString.len == 0:
result = "an empty connection string provided"
error "error", msg=result
return
return status_go.validateConnectionString(connectionString)
proc getConnectionStringForBootstrappingAnotherDevice*(self: Service, password: string, chatKey: string): string =
@ -209,8 +225,7 @@ QtObject:
"timeout": 5 * 60 * 1000,
}
}
self.localPairingStatus.reset()
self.localPairingStatus.mode = LocalPairingMode.Sender
self.localPairingStatus = newLocalPairingStatus(PairingType.AppSync, LocalPairingMode.Sender)
return status_go.getConnectionStringForBootstrappingAnotherDevice($configJSON)
proc inputConnectionStringForBootstrapping*(self: Service, connectionString: string): string =
@ -226,8 +241,7 @@ QtObject:
},
"clientConfig": %* {}
}
self.localPairingStatus.reset()
self.localPairingStatus.mode = LocalPairingMode.Receiver
self.localPairingStatus = newLocalPairingStatus(PairingType.AppSync, LocalPairingMode.Receiver)
let arg = AsyncInputConnectionStringArg(
tptr: cast[ByteAddress](asyncInputConnectionStringTask),
@ -237,3 +251,127 @@ QtObject:
configJSON: $configJSON
)
self.threadpool.start(arg)
proc validateKeyUids*(self: Service, keyUids: seq[string], validateForExport: bool): tuple[finalKeyUids: seq[string], err: string] =
if keyUids.len > 0:
for keyUid in keyUids:
let kp = self.walletAccountService.getKeypairByKeyUid(keyUid)
if kp.isNil:
result.err = "cannot resolve keypair for provided keyUid"
return
let migratedToKeycard = kp.keycards.len > 0
if migratedToKeycard or kp.keypairType == KeypairTypeProfile:
result.err = "keypair is migrated to a keycard or refers to a profile keypair"
return
if validateForExport:
if kp.getOperability() == AccountNonOperable:
result.err = "cannot export non operable keypair"
return
elif kp.getOperability() != AccountNonOperable:
result.err = "keypair is already fully or partially operable"
return
result.finalKeyUids.add(kp.keyUid)
else:
let keypairs = self.walletAccountService.getKeypairs()
for kp in keypairs:
let migratedToKeycard = kp.keycards.len > 0
if migratedToKeycard or
kp.keypairType == KeypairTypeProfile or
validateForExport and kp.getOperability() == AccountNonOperable or
not validateForExport and kp.getOperability() != AccountNonOperable:
continue
result.finalKeyUids.add(kp.keyUid)
if result.finalKeyUids.len == 0:
result.err = "there is no valid keypair"
## Providing an empty array of keyUids means generating a connection string and transferring all non operable keypairs
proc generateConnectionStringForExportingKeypairsKeystores*(self: Service, keyUids: seq[string], password: string): tuple[res: string, err: string] =
if password.len == 0:
result.err = "emtpy password provided"
error "error", msg=result.err
return
let(finalKeyUids, err) = self.validateKeyUids(keyUids, validateForExport=true)
if err.len > 0:
result.err = err
error "error", msg=err
return
let loggedInUserKeyUid = singletonInstance.userProfile.getKeyUid()
let keycardUser = singletonInstance.userProfile.getIsKeycardUser()
var finalPassword = utils.hashPassword(password)
if keycardUser:
finalPassword = password
let configJSON = %* {
"senderConfig": %* {
"keystorePath": joinPath(main_constants.ROOTKEYSTOREDIR, loggedInUserKeyUid),
"loggedInKeyUid": loggedInUserKeyUid,
"password": finalPassword,
"keypairsToExport": finalKeyUids,
},
"serverConfig": %* {
"timeout": 5 * 60 * 1000,
}
}
self.localPairingStatus = newLocalPairingStatus(PairingType.KeypairSync, LocalPairingMode.Sender)
let response = status_go.getConnectionStringForExportingKeypairsKeystores($configJSON)
try:
let jsonObj = response.parseJson
if jsonObj.hasKey("error"):
return ("", jsonObj["error"].getStr)
except Exception:
return (response, "")
proc inputConnectionStringForImportingKeystoreFinished*(self: Service, responseJson: string) {.slot.} =
try:
let jsonObj = responseJson.parseJson
if jsonObj.hasKey("error") and jsonObj["error"].getStr.len == 0:
info "keystore files successfully transferred"
self.walletAccountService.updateKeypairOperabilityInLocalStoreAndNotify(self.localPairingStatus.transferredKeypairs)
return
let errorDescription = jsonObj["error"].getStr
error "failed to start transferring keystore files", errorDescription
self.events.emit(SIGNAL_IMPORTED_KEYPAIRS, KeypairsArgs(error: errorDescription))
except Exception as e:
error "unexpected error", msg=e.msg
## Providing an empty array of keyUids means expecting keystore files for all non operable keypairs to be received
proc inputConnectionStringForImportingKeypairsKeystores*(self: Service, keyUids: seq[string], connectionString: string, password: string): string =
if password.len == 0:
result = "emtpy password provided"
error "error", msg=result
return
let(finalKeyUids, err) = self.validateKeyUids(keyUids, validateForExport=false)
if err.len > 0:
result = err
error "error", msg=result
return
let loggedInUserKeyUid = singletonInstance.userProfile.getKeyUid()
let keycardUser = singletonInstance.userProfile.getIsKeycardUser()
var finalPassword = utils.hashPassword(password)
if keycardUser:
finalPassword = password
let configJSON = %* {
"receiverConfig": %* {
"keystorePath": main_constants.ROOTKEYSTOREDIR,
"loggedInKeyUid": loggedInUserKeyUid,
"password": finalPassword,
"keypairsToImport": finalKeyUids,
},
"clientConfig": %* {}
}
self.localPairingStatus = newLocalPairingStatus(PairingType.KeypairSync, LocalPairingMode.Receiver)
let arg = AsyncInputConnectionStringArg(
tptr: cast[ByteAddress](asyncInputConnectionStringForImportingKeystoreTask),
vptr: cast[ByteAddress](self.vptr),
slot: "inputConnectionStringForImportingKeystoreFinished",
connectionString: connectionString,
configJSON: $configJSON
)
self.threadpool.start(arg)

View File

@ -1,4 +1,4 @@
import tables, json, strformat, strutils, chronicles
import tables, json, strformat, strutils, sequtils, sugar, chronicles
import account_dto, keycard_dto
@ -73,3 +73,10 @@ proc `$`*(self: KeypairDto): string =
"""
result &= """
]"""
proc getOperability*(self: KeypairDto): string =
if self.accounts.any(x => x.operable == AccountNonOperable):
return AccountNonOperable
if self.accounts.any(x => x.operable == AccountPartiallyOperable):
return AccountPartiallyOperable
return AccountFullyOperable

View File

@ -215,14 +215,24 @@ proc removeAccountFromLocalStoreAndNotify(self: Service, address: string, notify
if notify:
self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountArgs(account: acc))
proc updateKeypairOperabilityInLocalStoreAndNotify(self: Service, keyUid: string) =
let kp = self.getKeypairByKeyUid(keyUid)
if kp.isNil:
error "there is no known keypair", keyUid=keyUid, procName="updateKeypairOperabilityInLocalStoreAndNotify"
proc updateKeypairOperabilityInLocalStoreAndNotify*(self: Service, importedKeyUids: seq[string]) =
var updatedKeypairs: seq[KeypairDto]
let localKeypairs = self.getKeypairs()
for keyUid in importedKeyUids:
var foundKp: KeypairDto = nil
for kp in localKeypairs:
if keyUid == kp.keyUid:
foundKp = kp
break
if foundKp.isNil:
error "there is no known keypair", keyUid=keyUid, procName="updateKeypairOperabilityInLocalStoreAndNotify"
return
for acc in foundKp.accounts:
acc.operable = AccountFullyOperable
updatedKeypairs.add(foundKp)
if updatedKeypairs.len == 0:
return
for acc in kp.accounts:
acc.operable = AccountFullyOperable
self.events.emit(SIGNAL_KEYPAIR_OPERABILITY_CHANGED, KeypairArgs(keypair: kp))
self.events.emit(SIGNAL_IMPORTED_KEYPAIRS, KeypairsArgs(keypairs: updatedKeypairs))
proc updateAccountsPositions(self: Service) =
let dbAccounts = getAccountsFromDb()
@ -326,7 +336,7 @@ proc makePrivateKeyKeypairFullyOperable*(self: Service, keyUid, privateKey, pass
if not response.error.isNil:
error "status-go error", procName="makePrivateKeyKeypairFullyOperable", errCode=response.error.code, errDesription=response.error.message
return response.error.message
self.updateKeypairOperabilityInLocalStoreAndNotify(keyUid)
self.updateKeypairOperabilityInLocalStoreAndNotify(@[keyUid])
return ""
except Exception as e:
error "error: ", procName="makePrivateKeyKeypairFullyOperable", errName=e.name, errDesription=e.msg
@ -367,7 +377,7 @@ proc makeSeedPhraseKeypairFullyOperable*(self: Service, keyUid, mnemonic, passwo
if not response.error.isNil:
error "status-go error", procName="makeSeedPhraseKeypairFullyOperable", errCode=response.error.code, errDesription=response.error.message
return response.error.message
self.updateKeypairOperabilityInLocalStoreAndNotify(keyUid)
self.updateKeypairOperabilityInLocalStoreAndNotify(@[keyUid])
return ""
except Exception as e:
error "error: ", procName="makeSeedPhraseKeypairFullyOperable", errName=e.name, errDesription=e.msg

View File

@ -19,7 +19,7 @@ const SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED* = "walletAccount/p
const SIGNAL_KEYPAIR_SYNCED* = "keypairSynced"
const SIGNAL_KEYPAIR_NAME_CHANGED* = "keypairNameChanged"
const SIGNAL_KEYPAIR_DELETED* = "keypairDeleted"
const SIGNAL_KEYPAIR_OPERABILITY_CHANGED* = "keypairOperabilityChanged"
const SIGNAL_IMPORTED_KEYPAIRS* = "importedKeypairs"
const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet"
const SIGNAL_KEYCARD_REBUILD* = "keycardRebuild"
@ -43,6 +43,10 @@ type KeypairArgs* = ref object of Args
keyPairName*: string
oldKeypairName*: string
type KeypairsArgs* = ref object of Args
keypairs*: seq[KeypairDto]
error*: string
type KeycardArgs* = ref object of Args
success*: bool
oldKeycardUid*: string

@ -1 +1 @@
Subproject commit 0a0b5f0f320a7ed3adf53218e5b18fc41d0fbef0
Subproject commit f75bbc383157285d8260365e3b95def7b259e340