Skip to content

Commit

Permalink
feature(@desktop/keycard): sync a Keycard state on every usage
Browse files Browse the repository at this point in the history
Closes: #8759
  • Loading branch information
saledjenic committed Jan 13, 2023
1 parent 93c90b8 commit cae2a5b
Show file tree
Hide file tree
Showing 43 changed files with 794 additions and 280 deletions.
9 changes: 9 additions & 0 deletions src/app/modules/main/controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ logScope:
topics = "main-module-controller"

const UNIQUE_MAIN_MODULE_IDENTIFIER* = "MainModule"
const UNIQUE_MAIN_MODULE_KEYCARD_SYNC_IDENTIFIER* = "MainModule-KeycardSyncPurpose"

type
Controller* = ref object of RootObj
Expand Down Expand Up @@ -270,6 +271,10 @@ proc init*(self: Controller) =

self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED) do(e: Args):
let args = SharedKeycarModuleFlowTerminatedArgs(e)
if args.uniqueIdentifier == UNIQUE_MAIN_MODULE_KEYCARD_SYNC_IDENTIFIER:
self.delegate.onSharedKeycarModuleKeycardSyncPurposeTerminated(args.lastStepInTheCurrentFlow)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_KEYCARD_SYNC_TERMINATED, Args())
return
if args.uniqueIdentifier != UNIQUE_MAIN_MODULE_IDENTIFIER or
self.authenticateUserFlowRequestedBy.len == 0:
return
Expand All @@ -293,6 +298,10 @@ proc init*(self: Controller) =
self.authenticateUserFlowRequestedBy = args.uniqueIdentifier
self.delegate.runAuthenticationPopup(args.keyUid)

self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_TRY_KEYCARD_SYNC) do(e: Args):
let args = SharedKeycarModuleArgs(e)
self.delegate.tryKeycardSync(args.keyUid, args.pin)

proc isConnected*(self: Controller): bool =
return self.nodeService.isConnected()

Expand Down
6 changes: 6 additions & 0 deletions src/app/modules/main/io_interface.nim
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ method activateStatusDeepLink*(self: AccessInterface, statusDeepLink: string) {.
method setCommunityIdToSpectate*(self: AccessInterface, commnityId: string) {.base.} =
raise newException(ValueError, "No implementation available")

method tryKeycardSync*(self: AccessInterface, keyUid: string, pin: string) {.base.} =
raise newException(ValueError, "No implementation available")

method onSharedKeycarModuleKeycardSyncPurposeTerminated*(self: AccessInterface, lastStepInTheCurrentFlow: bool) {.base.} =
raise newException(ValueError, "No implementation available")

# This way (using concepts) is used only for the modules managed by AppController
type
DelegateInterface* = concept c
Expand Down
16 changes: 16 additions & 0 deletions src/app/modules/main/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type
nodeSectionModule: node_section_module.AccessInterface
networksModule: networks_module.AccessInterface
keycardSharedModule: keycard_shared_module.AccessInterface
keycardSharedModuleKeycardSyncPurpose: keycard_shared_module.AccessInterface
moduleLoaded: bool
statusUrlCommunityToSpectate: string

Expand Down Expand Up @@ -209,6 +210,8 @@ method delete*[T](self: Module[T]) =
self.networksModule.delete
if not self.keycardSharedModule.isNil:
self.keycardSharedModule.delete
if not self.keycardSharedModuleKeycardSyncPurpose.isNil:
self.keycardSharedModuleKeycardSyncPurpose.delete
self.view.delete
self.viewVariant.delete
self.controller.delete
Expand Down Expand Up @@ -983,6 +986,19 @@ method runAuthenticationPopup*[T](self: Module[T], keyUid: string) =
return
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.Authentication, keyUid)

method onSharedKeycarModuleKeycardSyncPurposeTerminated*[T](self: Module[T], lastStepInTheCurrentFlow: bool) =
if not self.keycardSharedModuleKeycardSyncPurpose.isNil:
self.keycardSharedModuleKeycardSyncPurpose.delete
self.keycardSharedModuleKeycardSyncPurpose = nil

method tryKeycardSync*[T](self: Module[T], keyUid: string, pin: string) =
self.keycardSharedModuleKeycardSyncPurpose = keycard_shared_module.newModule[Module[T]](self, UNIQUE_MAIN_MODULE_KEYCARD_SYNC_IDENTIFIER,
self.events, self.keycardService, self.settingsService, self.privacyService, self.accountsService,
self.walletAccountService, self.keychainService)
if self.keycardSharedModuleKeycardSyncPurpose.isNil:
return
self.keycardSharedModuleKeycardSyncPurpose.syncKeycardBasedOnAppState(keyUid, pin)

method onDisplayKeycardSharedModuleFlow*[T](self: Module[T]) =
self.view.emitDisplayKeycardSharedModuleFlow()

Expand Down
18 changes: 14 additions & 4 deletions src/app/modules/main/profile_section/keycard/controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,29 @@ proc init*(self: Controller) =

self.events.on(SIGNAL_KEYCARD_LOCKED) do(e: Args):
let args = KeycardActivityArgs(e)
self.delegate.onKeycardLocked(args.keycardUid)
self.delegate.onKeycardLocked(args.keyPair.keyUid, args.keyPair.keycardUid)

self.events.on(SIGNAL_KEYCARD_UNLOCKED) do(e: Args):
let args = KeycardActivityArgs(e)
self.delegate.onKeycardUnlocked(args.keycardUid)
self.delegate.onKeycardUnlocked(args.keyPair.keyUid, args.keyPair.keycardUid)

self.events.on(SIGNAL_KEYCARD_NAME_CHANGED) do(e: Args):
let args = KeycardActivityArgs(e)
self.delegate.onKeycardNameChanged(args.keycardUid, args.keycardNewName)
self.delegate.onKeycardNameChanged(args.keyPair.keycardUid, args.keyPair.keycardName)

self.events.on(SIGNAL_KEYCARD_UID_UPDATED) do(e: Args):
let args = KeycardActivityArgs(e)
self.delegate.onKeycardUidUpdated(args.keycardUid, args.keycardNewUid)
self.delegate.onKeycardUidUpdated(args.oldKeycardUid, args.keyPair.keycardUid)

self.events.on(SIGNAL_KEYCARD_ACCOUNTS_REMOVED) do(e: Args):
let args = KeycardActivityArgs(e)
if not args.success:
return
self.delegate.onKeycardAccountsRemoved(args.keyPair.keyUid, args.keyPair.keycardUid, args.keyPair.accountsAddresses)

self.events.on(SIGNAL_WALLET_ACCOUNT_UPDATED) do(e: Args):
let args = WalletAccountUpdated(e)
self.delegate.onWalletAccountUpdated(args.account)

proc getAllMigratedKeyPairs*(self: Controller): seq[KeyPairDto] =
return self.walletAccountService.getAllMigratedKeyPairs()
Expand Down
11 changes: 9 additions & 2 deletions src/app/modules/main/profile_section/keycard/io_interface.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import NimQml
from ../../../../../app_service/service/wallet_account/service import KeyPairDto
from ../../../../../app_service/service/wallet_account/service import WalletAccountDto

type
AccessInterface* {.pure inheritable.} = ref object of RootObj
Expand Down Expand Up @@ -68,10 +69,10 @@ method onLoggedInUserImageChanged*(self: AccessInterface) {.base.} =
method onNewKeycardSet*(self: AccessInterface, keyPair: KeyPairDto) {.base.} =
raise newException(ValueError, "No implementation available")

method onKeycardLocked*(self: AccessInterface, keycardUid: string) {.base.} =
method onKeycardLocked*(self: AccessInterface, keyUid: string, keycardUid: string) {.base.} =
raise newException(ValueError, "No implementation available")

method onKeycardUnlocked*(self: AccessInterface, keycardUid: string) {.base.} =
method onKeycardUnlocked*(self: AccessInterface, keyUid: string, keycardUid: string) {.base.} =
raise newException(ValueError, "No implementation available")

method onKeycardNameChanged*(self: AccessInterface, keycardUid: string, keycardNewName: string) {.base.} =
Expand All @@ -80,6 +81,12 @@ method onKeycardNameChanged*(self: AccessInterface, keycardUid: string, keycardN
method onKeycardUidUpdated*(self: AccessInterface, keycardUid: string, keycardNewUid: string) {.base.} =
raise newException(ValueError, "No implementation available")

method onKeycardAccountsRemoved*(self: AccessInterface, keyUid: string, keycardUid: string, accountsToRemove: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available")

method onWalletAccountUpdated*(self: AccessInterface, account: WalletAccountDto) {.base.} =
raise newException(ValueError, "No implementation available")

method prepareKeycardDetailsModel*(self: AccessInterface, keyUid: string) {.base.} =
raise newException(ValueError, "No implementation available")

Expand Down
101 changes: 74 additions & 27 deletions src/app/modules/main/profile_section/keycard/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,18 @@ method viewDidLoad*(self: Module) =
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant

proc isSharedKeycardModuleFlowRunning(self: Module): bool =
return not self.keycardSharedModule.isNil

method getKeycardSharedModule*(self: Module): QVariant =
return self.keycardSharedModule.getModuleAsVariant()
if self.isSharedKeycardModuleFlowRunning():
return self.keycardSharedModule.getModuleAsVariant()

proc createSharedKeycardModule(self: Module) =
self.keycardSharedModule = keycard_shared_module.newModule[Module](self, UNIQUE_SETTING_KEYCARD_MODULE_IDENTIFIER,
self.events, self.keycardService, self.settingsService, self.privacyService, self.accountsService,
self.walletAccountService, self.keychainService)

proc isSharedKeycardModuleFlowRunning(self: Module): bool =
return not self.keycardSharedModule.isNil

method onSharedKeycarModuleFlowTerminated*(self: Module, lastStepInTheCurrentFlow: bool) =
if self.isSharedKeycardModuleFlowRunning():
self.view.emitDestroyKeycardSharedModuleFlow()
Expand Down Expand Up @@ -181,26 +182,29 @@ method runCreateNewPairingCodePopup*(self: Module, keyUid: string) =
return
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.ChangePairingCode, keyUid)

proc findAccountByAccountAddress(accounts: seq[WalletAccountDto], address: string): WalletAccountDto =
for i in 0 ..< accounts.len:
if cmpIgnoreCase(accounts[i].address, address) == 0:
return accounts[i]
return nil

proc buildKeycardItem(self: Module, walletAccounts: seq[WalletAccountDto], keyPair: KeyPairDto, reason: BuildItemReason):
KeycardItem =
let findAccountByAccountAddress = proc(accounts: seq[WalletAccountDto], address: string): WalletAccountDto =
for i in 0 ..< accounts.len:
if cmpIgnoreCase(accounts[i].address, address) == 0:
return accounts[i]
return nil

let isAccountInKnownAccounts = proc(knownAccounts: seq[WalletAccountDto], address: string): bool =
for i in 0 ..< knownAccounts.len:
if cmpIgnoreCase(knownAccounts[i].address, address) == 0:
return true
return false

var knownAccounts: seq[WalletAccountDto]
var unknownAccountsAddresses: seq[string]
for accAddr in keyPair.accountsAddresses:
let account = findAccountByAccountAddress(walletAccounts, accAddr)
if account.isNil:
## we should never be here cause we need to remove deleted accounts from the `keypairs` table and sync
## that state accross different app instances
## We are here if the keycard is not sync yet with the app's state. That may happen if there are more copies of the
## same keycard, then deleting an account for a keypair syncs the inserted keycard, but other copies of the card
## remain with that account till the moment they are synced.
unknownAccountsAddresses.add(accAddr)
continue
if reason == BuildItemReason.MainView and
(isAccountInKnownAccounts(knownAccounts, accAddr) or
Expand Down Expand Up @@ -230,6 +234,12 @@ proc buildKeycardItem(self: Module, walletAccounts: seq[WalletAccountDto], keyPa
item.setPairType(KeyPairType.PrivateKeyImport.int)
item.setIcon("keycard")
item.addAccount(newKeyPairAccountItem(ka.name, ka.path, ka.address, ka.publicKey, ka.emoji, ka.color, icon = icon, balance = 0.0))
if reason == BuildItemReason.DetailsView:
var i = 0
for ua in unknownAccountsAddresses:
i.inc
let name = "acc" & $i
item.addAccount(newKeyPairAccountItem(name, path = "", ua, pubKey = "", emoji = "", color = "#939BA1", icon = "wallet", balance = 0.0))
return item

proc areAllKnownKeycardsLockedForKeypair(self: Module, keyUid: string): bool =
Expand Down Expand Up @@ -260,39 +270,76 @@ method onLoggedInUserImageChanged*(self: Module) =

method onNewKeycardSet*(self: Module, keyPair: KeyPairDto) =
let walletAccounts = self.controller.getWalletAccounts()
let mainViewItem = self.buildKeycardItem(walletAccounts, keyPair, BuildItemReason.MainView)
if not mainViewItem.isNil:
self.view.keycardModel().addItem(mainViewItem)
var mainViewItem = self.view.keycardModel().getItemForKeyUid(keyPair.keyUid)
if mainViewItem.isNil:
mainViewItem = self.buildKeycardItem(walletAccounts, keyPair, BuildItemReason.MainView)
if not mainViewItem.isNil:
self.view.keycardModel().addItem(mainViewItem)
else:
for accAddr in keyPair.accountsAddresses:
if mainViewItem.containsAccountAddress(accAddr):
continue
let account = findAccountByAccountAddress(walletAccounts, accAddr)
if account.isNil:
## we should never be here cause all keypairs are firstly added to wallet
continue
mainViewItem.addAccount(newKeyPairAccountItem(account.name, account.path, account.address, account.publicKey,
account.emoji, account.color, icon = "", balance = 0.0))
if self.view.keycardDetailsModel().isNil:
return
let detailsViewItem = self.buildKeycardItem(walletAccounts, keyPair, BuildItemReason.DetailsView)
if not detailsViewItem.isNil:
self.view.keycardDetailsModel().addItem(detailsViewItem)
var detailsViewItem = self.view.keycardDetailsModel().getItemForKeycardUid(keyPair.keycardUid)
if detailsViewItem.isNil:
detailsViewItem = self.buildKeycardItem(walletAccounts, keyPair, BuildItemReason.DetailsView)
if not detailsViewItem.isNil:
self.view.keycardDetailsModel().addItem(detailsViewItem)
else:
for accAddr in keyPair.accountsAddresses:
if detailsViewItem.containsAccountAddress(accAddr):
continue
let account = findAccountByAccountAddress(walletAccounts, accAddr)
if account.isNil:
## we should never be here cause all keypairs are firstly added to wallet
continue
detailsViewItem.addAccount(newKeyPairAccountItem(account.name, account.path, account.address, account.publicKey,
account.emoji, account.color, icon = "", balance = 0.0))

method onKeycardLocked*(self: Module, keycardUid: string) =
self.view.keycardModel().setLocked(keycardUid, true)
method onKeycardLocked*(self: Module, keyUid: string, keycardUid: string) =
self.view.keycardModel().setLockedForKeycardsWithKeyUid(keyUid, true)
if self.view.keycardDetailsModel().isNil:
return
self.view.keycardDetailsModel().setLocked(keycardUid, true)
self.view.keycardDetailsModel().setLockedForKeycardWithKeycardUid(keycardUid, true)

method onKeycardUnlocked*(self: Module, keycardUid: string) =
self.view.keycardModel().setLocked(keycardUid, false)
method onKeycardUnlocked*(self: Module, keyUid: string, keycardUid: string) =
self.view.keycardModel().setLockedForKeycardsWithKeyUid(keyUid, false)
if self.view.keycardDetailsModel().isNil:
return
self.view.keycardDetailsModel().setLocked(keycardUid, false)
self.view.keycardDetailsModel().setLockedForKeycardWithKeycardUid(keycardUid, false)

method onKeycardNameChanged*(self: Module, keycardUid: string, keycardNewName: string) =
self.view.keycardModel().setName(keycardUid, keycardNewName)
self.view.keycardModel().setNameForKeycardWithKeycardUid(keycardUid, keycardNewName)
if self.view.keycardDetailsModel().isNil:
return
self.view.keycardDetailsModel().setName(keycardUid, keycardNewName)
self.view.keycardDetailsModel().setNameForKeycardWithKeycardUid(keycardUid, keycardNewName)

method onKeycardUidUpdated*(self: Module, keycardUid: string, keycardNewUid: string) =
self.view.keycardModel().setKeycardUid(keycardUid, keycardNewUid)
if self.view.keycardDetailsModel().isNil:
return
self.view.keycardDetailsModel().setKeycardUid(keycardUid, keycardNewUid)

method onKeycardAccountsRemoved*(self: Module, keyUid: string, keycardUid: string, accountsToRemove: seq[string]) =
self.view.keycardModel().removeAccountsFromKeycardsWithKeyUid(keyUid, accountsToRemove, removeKeycardItemIfHasNoAccounts = true)
if self.view.keycardDetailsModel().isNil:
return
self.view.keycardDetailsModel().removeAccountsFromKeycardWithKeycardUid(keycardUid, accountsToRemove, removeKeycardItemIfHasNoAccounts = true)

method onWalletAccountUpdated*(self: Module, account: WalletAccountDto) =
self.view.keycardModel().updateDetailsForAddressForKeyPairsWithKeyUid(account.keyUid, account.address, account.name,
account.color, account.emoji)
if self.view.keycardDetailsModel().isNil:
return
self.view.keycardDetailsModel().updateDetailsForAddressForKeyPairsWithKeyUid(account.keyUid, account.address, account.name,
account.color, account.emoji)

method prepareKeycardDetailsModel*(self: Module, keyUid: string) =
let walletAccounts = self.controller.getWalletAccounts()
var items: seq[KeycardItem]
Expand Down
5 changes: 4 additions & 1 deletion src/app/modules/main/profile_section/keycard/view.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ QtObject:
self.delegate.viewDidLoad()

proc getKeycardSharedModule(self: View): QVariant {.slot.} =
return self.delegate.getKeycardSharedModule()
let module = self.delegate.getKeycardSharedModule()
if not module.isNil:
return module
return newQVariant()
QtProperty[QVariant] keycardSharedModule:
read = getKeycardSharedModule

Expand Down
5 changes: 4 additions & 1 deletion src/app/modules/main/view.nim
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,10 @@ QtObject:
self.displayUserProfile(publicKey)

proc getKeycardSharedModule(self: View): QVariant {.slot.} =
return self.delegate.getKeycardSharedModule()
let module = self.delegate.getKeycardSharedModule()
if not module.isNil:
return module
return newQVariant()
QtProperty[QVariant] keycardSharedModule:
read = getKeycardSharedModule

Expand Down
16 changes: 13 additions & 3 deletions src/app/modules/main/wallet_section/accounts/controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ proc init*(self: Controller) =
let args = SharedKeycarModuleArgs(e)
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER:
return
self.delegate.onUserAuthenticated(args.password)
self.delegate.onUserAuthenticated(args.pin, args.password, args.keyUid)

self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED_AND_WALLET_ADDRESS_GENERATED) do(e: Args):
let args = SharedKeycarModuleUserAuthenticatedAndWalletAddressGeneratedArgs(e)
Expand All @@ -66,6 +66,12 @@ proc init*(self: Controller) =
derivedAddress = args.derivedAddresses[0]
self.delegate.addressDetailsFetched(derivedAddress, args.error)

self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED) do(e: Args):
let args = SharedKeycarModuleFlowTerminatedArgs(e)
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER:
return
self.delegate.onSharedKeycarModuleFlowTerminated(args.lastStepInTheCurrentFlow)

proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
return self.walletAccountService.getWalletAccounts()

Expand All @@ -84,8 +90,8 @@ proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string
proc addWatchOnlyAccount*(self: Controller, address: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.addWatchOnlyAccount(address, accountName, color, emoji)

proc deleteAccount*(self: Controller, address: string) =
self.walletAccountService.deleteAccount(address)
proc deleteAccount*(self: Controller, address: string, keyPairMigratedToKeycard: bool) =
self.walletAccountService.deleteAccount(address, keyPairMigratedToKeycard)

proc fetchDerivedAddressDetails*(self: Controller, address: string) =
self.walletAccountService.fetchDerivedAddressDetails(address)
Expand Down Expand Up @@ -127,3 +133,7 @@ proc getCurrentCurrency*(self: Controller): string =

proc getCurrencyFormat*(self: Controller, symbol: string): CurrencyFormatDto =
return self.currencyService.getCurrencyFormat(symbol)

proc getMigratedKeyPairByKeyUid*(self: Controller, keyUid: string): seq[KeyPairDto] =
return self.walletAccountService.getMigratedKeyPairByKeyUid(keyUid)

Loading

0 comments on commit cae2a5b

Please sign in to comment.