Skip to content

Commit

Permalink
feat(@desktop/onboarding): Lost Keycard - `Create replacement Keyca…
Browse files Browse the repository at this point in the history
…rd with seed phrase` flow

This commit introduces:
- `Create replacement Keycard with seed phrase` flow
- `Order new keycard` option

Closes: #7641
  • Loading branch information
saledjenic committed Jan 23, 2023
1 parent d3fffac commit 3ca03bd
Show file tree
Hide file tree
Showing 47 changed files with 451 additions and 92 deletions.
72 changes: 52 additions & 20 deletions src/app/boot/app_controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ logScope:
type
AppController* = ref object of RootObj
storeKeyPair: bool
syncWalletForReplacedKeycard: bool
statusFoundation: StatusFoundation
notificationsManager*: NotificationsManager

Expand Down Expand Up @@ -108,6 +109,7 @@ proc userLoggedIn*(self: AppController): string
proc logout*(self: AppController)
proc finishAppLoading*(self: AppController)
proc storeKeyPairForNewKeycardUser*(self: AppController)
proc syncWalletAccountsOnLoginForReplacedKeycard*(self: AppController)

# Main Module Delegate Interface
proc mainDidLoad*(self: AppController)
Expand All @@ -130,6 +132,7 @@ proc connect(self: AppController) =
proc newAppController*(statusFoundation: StatusFoundation): AppController =
result = AppController()
result.storeKeyPair = false
result.syncWalletForReplacedKeycard = false
result.statusFoundation = statusFoundation

# Preparing settings service to be exposed later as global QObject
Expand Down Expand Up @@ -434,25 +437,54 @@ proc buildAndRegisterUserProfile(self: AppController) =

singletonInstance.engine.setRootContextProperty("userProfile", self.userProfileVariant)

if self.storeKeyPair and singletonInstance.userProfile.getIsKeycardUser():
let allAccounts = self.walletAccountService.fetchAccounts()
let defaultWalletAccounts = allAccounts.filter(a =>
a.walletType == WalletTypeDefaultStatusAccount and
a.path == account_constants.PATH_DEFAULT_WALLET and
not a.isChat and
a.isWallet
)
if defaultWalletAccounts.len == 0:
error "default wallet account was not generated"
return
let defaultWalletAddress = defaultWalletAccounts[0].address
let keyPair = KeyPairDto(keycardUid: self.keycardService.getLastReceivedKeycardData().flowEvent.instanceUID,
keycardName: displayName,
keycardLocked: false,
accountsAddresses: @[defaultWalletAddress],
keyUid: loggedInAccount.keyUid)
let keystoreDir = self.accountsService.getKeyStoreDir()
discard self.walletAccountService.addMigratedKeyPair(keyPair, keystoreDir)
if singletonInstance.userProfile.getIsKeycardUser():
if self.storeKeyPair:
let allAccounts = self.walletAccountService.fetchAccounts()
let defaultWalletAccounts = allAccounts.filter(a =>
a.walletType == WalletTypeDefaultStatusAccount and
a.path == account_constants.PATH_DEFAULT_WALLET and
not a.isChat and
a.isWallet
)
if defaultWalletAccounts.len == 0:
error "default wallet account was not generated"
return
let defaultWalletAddress = defaultWalletAccounts[0].address
let keyPair = KeyPairDto(keycardUid: self.keycardService.getLastReceivedKeycardData().flowEvent.instanceUID,
keycardName: displayName,
keycardLocked: false,
accountsAddresses: @[defaultWalletAddress],
keyUid: loggedInAccount.keyUid)
let keystoreDir = self.accountsService.getKeyStoreDir()
discard self.walletAccountService.addMigratedKeyPair(keyPair, keystoreDir)
if self.syncWalletForReplacedKeycard:
let allAccounts = self.walletAccountService.fetchAccounts()
let accountsForLoggedInUser = allAccounts.filter(a => a.keyUid == loggedInAccount.keyUid)
var kpDto = KeyPairDto(keycardUid: "",
keycardName: displayName,
keycardLocked: false,
accountsAddresses: @[],
keyUid: loggedInAccount.keyUid)
var activeValidPathsToStoreToAKeycard: seq[string]
for acc in accountsForLoggedInUser:
activeValidPathsToStoreToAKeycard.add(acc.path)
kpDto.accountsAddresses.add(acc.address)
self.keycardService.startStoreMetadataFlow(displayName, self.startupModule.getPin(), activeValidPathsToStoreToAKeycard)
## sleep for 3 seconds, since that is more than enough to store metadata to a keycard, if the reader is still plugged in
## and the card is still inserted, otherwise we just skip that.
## At the moment we're not able to sync later keycard without metadata, cause such card doesn't return instance uid for
## loaded seed phrase, that's in the notes I am taking for discussion with keycard team. If they are able to provide
## instance uid for GetMetadata flow we will be able to use SIGNAL_SHARED_KEYCARD_MODULE_TRY_KEYCARD_SYNC signal for syncing
## otherwise we need to handle that way separatelly in `handleKeycardSyncing` of shared module
sleep(3000)
let (_, kcEvent) = self.keycardService.getLastReceivedKeycardData()
if kcEvent.instanceUID.len > 0:
kpDto.keycardUid = kcEvent.instanceUID
let keystoreDir = self.accountsService.getKeyStoreDir()
discard self.walletAccountService.addMigratedKeyPair(kpDto, keystoreDir)

proc storeKeyPairForNewKeycardUser*(self: AppController) =
self.storeKeyPair = true
self.storeKeyPair = true

proc syncWalletAccountsOnLoginForReplacedKeycard*(self: AppController) =
self.syncWalletForReplacedKeycard = true
5 changes: 4 additions & 1 deletion src/app/global/app_translatable_constants.nim
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
## All app's labels/terms/warnings which need to be used/created on the backed side and displayed on the UI later
## should be stated here and translated via translations map in `Constants.qml` to be displayed well in user's language
const LOGIN_ACCOUNTS_LIST_ADD_NEW_USER* = "LOGIN-ACCOUNTS-LIST-ADD-NEW-USER"
const LOGIN_ACCOUNTS_LIST_ADD_EXISTING_USER* = "LOGIN-ACCOUNTS-LIST-ADD-EXISTING-USER"
const LOGIN_ACCOUNTS_LIST_ADD_EXISTING_USER* = "LOGIN-ACCOUNTS-LIST-ADD-EXISTING-USER"
const LOGIN_ACCOUNTS_LIST_LOST_KEYCARD* = "LOGIN-ACCOUNTS-LIST-LOST-KEYCARD"
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ method executePrePrimaryStateCommand*(self: FactoryResetSuccessState, controller
controller.runLoadAccountFlow(seedPhraseLength = 0, seedPhrase = "", pin = controller.getPinForKeycardCopy())

method executeCancelCommand*(self: FactoryResetSuccessState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard or
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.CreateCopyOfAKeycard:
Expand Down
13 changes: 10 additions & 3 deletions src/app/modules/startup/controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ proc getSelectedLoginAccount*(self: Controller): AccountDto =
if(acc.keyUid == self.tmpSelectedLoginAccountKeyUid):
return acc

proc keyUidMatch*(self: Controller, keyUid: string): bool =
proc keyUidMatchSelectedLoginAccount*(self: Controller, keyUid: string): bool =
return self.tmpSelectedLoginAccountKeyUid == keyUid

proc isSelectedLoginAccountKeycardAccount*(self: Controller): bool =
Expand All @@ -391,7 +391,7 @@ proc setSelectedLoginAccount*(self: Controller, keyUid: string, isKeycardAccount
let selectedAccount = self.getSelectedLoginAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)

proc isKeycardCreatedAccountSelectedOne*(self: Controller): bool =
proc isSelectedAccountAKeycardAccount*(self: Controller): bool =
let selectedAccount = self.getSelectedLoginAccount()
return selectedAccount.keycardPairing.len > 0

Expand All @@ -413,7 +413,14 @@ proc login*(self: Controller) =
if(error.len > 0):
self.delegate.emitAccountLoginError(error)

proc loginAccountKeycard*(self: Controller) =
proc loginAccountKeycard*(self: Controller, storeToKeychain = false, syncWalletAfterLogin = false) =
if syncWalletAfterLogin:
self.delegate.syncWalletAccountsOnLoginForReplacedKeycard()
if storeToKeychain:
## storing not now, user will be asked to store the pin once he is logged in
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW)
else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
self.delegate.moveToLoadingAppState()
let error = self.accountsService.loginAccountKeycard(self.tmpKeycardEvent)
if(error.len > 0):
Expand Down
18 changes: 17 additions & 1 deletion src/app/modules/startup/internal/biometrics_state.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
type
BiometricsState* = ref object of State
storeToKeychain: bool

proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState =
result = BiometricsState()
result.setup(flowType, StateType.Biometrics, backState)
result.storeToKeychain = false

proc delete*(self: BiometricsState) =
self.State.delete
Expand All @@ -28,6 +30,9 @@ method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)
elif self.flowType == FlowType.LostKeycardReplacement:
self.storeToKeychain = storeToKeychain
controller.startLoginFlowAutomatically(controller.getPin())

method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
Expand All @@ -44,4 +49,15 @@ method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)
controller.setupKeycardAccount(storeToKeychain)
elif self.flowType == FlowType.LostKeycardReplacement:
self.storeToKeychain = storeToKeychain
controller.startLoginFlowAutomatically(controller.getPin())

method resolveKeycardNextState*(self: BiometricsState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.LostKeycardReplacement:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard(self.storeToKeychain, syncWalletAfterLogin = true)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ proc delete*(self: KeycardInsertKeycardState) =
method executeBackCommand*(self: KeycardInsertKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method resolveKeycardNextState*(self: KeycardInsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ proc delete*(self: KeycardInsertedKeycardState) =
method executeBackCommand*(self: KeycardInsertedKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method getNextPrimaryState*(self: KeycardInsertedKeycardState, controller: Controller): State =
Expand Down
4 changes: 4 additions & 0 deletions src/app/modules/startup/internal/keycard_locked_state.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ proc newKeycardLockedState*(flowType: FlowType, backState: State): KeycardLocked
proc delete*(self: KeycardLockedState) =
self.State.delete

method executeBackCommand*(self: KeycardLockedState, controller: Controller) =
if self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method executePrimaryCommand*(self: KeycardLockedState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.runFactoryResetPopup()
9 changes: 7 additions & 2 deletions src/app/modules/startup/internal/keycard_not_empty_state.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ proc newKeycardNotEmptyState*(flowType: FlowType, backState: State): KeycardNotE
proc delete*(self: KeycardNotEmptyState) =
self.State.delete

method executeBackCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method executePrimaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.LostKeycardReplacement:
controller.runFactoryResetPopup()

method executeSecondaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
Expand All @@ -32,4 +37,4 @@ method resolveKeycardNextState*(self: KeycardNotEmptyState, keycardFlowType: str
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ proc newKeycardNotKeycardState*(flowType: FlowType, backState: State): KeycardNo
result.setup(flowType, StateType.KeycardNotKeycard, backState)

proc delete*(self: KeycardNotKeycardState) =
self.State.delete
self.State.delete

method executeBackCommand*(self: KeycardNotKeycardState, controller: Controller) =
if self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()
22 changes: 19 additions & 3 deletions src/app/modules/startup/internal/keycard_pin_set_state.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,27 @@ method getNextPrimaryState*(self: KeycardPinSetState, controller: Controller): S
controller.loginAccountKeycard()
return nil
return createState(StateType.KeycardWrongPuk, self.flowType, self.getBackState)
if self.flowType == FlowType.LostKeycardReplacement:
if not defined(macosx):
return nil
return createState(StateType.Biometrics, self.flowType, self.getBackState)

method executePrimaryCommand*(self: KeycardPinSetState, controller: Controller) =
if controller.getValidPuk() and not defined(macosx):
controller.setupKeycardAccount(false)
if defined(macosx):
return
# if controller.getValidPuk():
# controller.setupKeycardAccount(false)
# return
if self.flowType == FlowType.AppLogin:
if controller.getRecoverUsingSeedPhraseWhileLogin():
controller.startLoginFlowAutomatically(controller.getPin())
controller.startLoginFlowAutomatically(controller.getPin())
if self.flowType == FlowType.LostKeycardReplacement:
controller.startLoginFlowAutomatically(controller.getPin())

method resolveKeycardNextState*(self: KeycardPinSetState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.LostKeycardReplacement:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard()
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ proc delete*(self: KeycardPluginReaderState) =
method executeBackCommand*(self: KeycardPluginReaderState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method resolveKeycardNextState*(self: KeycardPluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ proc delete*(self: KeycardReadingKeycardState) =
method executeBackCommand*(self: KeycardReadingKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method getNextPrimaryState*(self: KeycardReadingKeycardState, controller: Controller): State =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ proc delete*(self: KeycardRecognizedKeycardState) =
method executeBackCommand*(self: KeycardRecognizedKeycardState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.LostKeycardReplacement:
controller.cancelCurrentFlow()

method getNextPrimaryState*(self: KeycardRecognizedKeycardState, controller: Controller): State =
Expand All @@ -21,3 +22,5 @@ method getNextPrimaryState*(self: KeycardRecognizedKeycardState, controller: Con
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
return createState(StateType.KeycardEnterPin, self.flowType, self.getBackState)
if self.flowType == FlowType.LostKeycardReplacement:
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)
25 changes: 16 additions & 9 deletions src/app/modules/startup/internal/keycard_repeat_pin_state.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ method executeBackCommand*(self: KeycardRepeatPinState, controller: Controller)
method executePrimaryCommand*(self: KeycardRepeatPinState, controller: Controller) =
if not controller.getPinMatch():
return
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.storePinToKeycard(controller.getPin(), puk = "")
elif self.flowType == FlowType.AppLogin:
controller.storePinToKeycard(controller.getPin(), puk = "")
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
return
if self.flowType == FlowType.FirstRunOldUserKeycardImport or
self.flowType == FlowType.AppLogin or
self.flowType == FlowType.LostKeycardReplacement:
controller.storePinToKeycard(controller.getPin(), puk = "")
return

method resolveKeycardNextState*(self: KeycardRepeatPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
Expand Down Expand Up @@ -65,4 +66,10 @@ method resolveKeycardNextState*(self: KeycardRepeatPinState, keycardFlowType: st
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
if self.flowType == FlowType.LostKeycardReplacement:
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
let backState = findBackStateWithTargetedStateType(self, StateType.LostKeycardOptions)
return createState(StateType.KeycardPinSet, self.flowType, backState)
Loading

0 comments on commit 3ca03bd

Please sign in to comment.