Skip to content

Commit

Permalink
feat(onboarding): integrate all keycard flows for the new onboarding
Browse files Browse the repository at this point in the history
Fixes #17079
  • Loading branch information
jrainville authored and igor-sirotin committed Feb 2, 2025
1 parent 3b56fc0 commit 0061e2b
Show file tree
Hide file tree
Showing 40 changed files with 1,295 additions and 267 deletions.
6 changes: 4 additions & 2 deletions src/app/boot/app_controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
# Services
result.generalService = general_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.keycardServiceV2 = keycard_serviceV2.newService(statusFoundation.events, statusFoundation.threadpool)
result.keycardServiceV2 = keycard_serviceV2.newService(statusFoundation.events, statusFoundation.threadpool, result.keycardService)
result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration,
result.settingsService, statusFoundation.events)
result.keychainService = keychain_service.newService(statusFoundation.events)
Expand Down Expand Up @@ -441,8 +441,10 @@ proc mainDidLoad*(self: AppController) =
self.checkForStoringPasswordToKeychain()

proc start*(self: AppController) =
if self.shouldUseTheNewOnboardingModule():
self.keycardServiceV2.init()

self.keycardService.init()
self.keycardServiceV2.init()
self.keychainService.init()
self.generalService.init()
self.accountsService.init()
Expand Down
77 changes: 64 additions & 13 deletions src/app/modules/onboarding/controller.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import chronicles, strutils
import chronicles
import io_interface
import uuids

Expand All @@ -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
from app_service/service/keycardV2/dto import KeycardExportedKeysDto

logScope:
topics = "onboarding-controller"
Expand Down Expand Up @@ -60,9 +61,47 @@ proc init*(self: Controller) =
self.delegate.onLocalPairingStatusUpdate(args)
self.connectionIds.add(handlerId)

proc setPin*(self: Controller, pin: string): bool =
self.keycardServiceV2.setPin(pin)
discard
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_STATE_UPDATED) do(e: Args):
let args = KeycardEventArg(e)
self.delegate.onKeycardStateUpdated(args.keycardEvent)
self.connectionIds.add(handlerId)

handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_SET_PIN_FAILURE) do(e: Args):
let args = KeycardErrorArg(e)
self.delegate.onKeycardSetPinFailure(args.error)
self.connectionIds.add(handlerId)

handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_AUTHORIZE_FAILURE) do(e: Args):
let args = KeycardErrorArg(e)
self.delegate.onKeycardAuthorizeFailure(args.error)
self.connectionIds.add(handlerId)

handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_LOAD_MNEMONIC_FAILURE) do(e: Args):
let args = KeycardErrorArg(e)
self.delegate.onKeycardLoadMnemonicFailure(args.error)
self.connectionIds.add(handlerId)

handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_LOAD_MNEMONIC_SUCCESS) do(e: Args):
let args = KeycardKeyUIDArg(e)
self.delegate.onKeycardLoadMnemonicSuccess(args.keyUID)
self.connectionIds.add(handlerId)

handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE) do(e: Args):
let args = KeycardErrorArg(e)
self.delegate.onKeycardExportKeysFailure(args.error)
self.connectionIds.add(handlerId)

handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS) do(e: Args):
let args = KeycardExportedKeysArg(e)
self.delegate.onKeycardExportKeysSuccess(args.exportedKeys)
self.connectionIds.add(handlerId)

proc initialize*(self: Controller, pin: string) =
let puk = self.keycardServiceV2.generateRandomPUK()
self.keycardServiceV2.initialize(pin, puk)

proc authorize*(self: Controller, pin: string) =
self.keycardServiceV2.asyncAuthorize(pin)

proc getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)
Expand All @@ -73,16 +112,11 @@ proc validMnemonic*(self: Controller, mnemonic: string): bool =
return true
return false

proc buildSeedPhrasesFromIndexes(self: Controller, seedPhraseIndexes: seq[int]): string =
if seedPhraseIndexes.len == 0:
error "keycard error: cannot generate mnemonic"
return
let sp = self.keycardServiceV2.buildSeedPhrasesFromIndexes(seedPhraseIndexes)
return sp.join(" ")
proc loadMnemonic*(self: Controller, mnemonic: string) =
self.keycardServiceV2.loadMnemonic(mnemonic)

proc getMnemonic*(self: Controller): string =
let indexes = self.keycardServiceV2.getMnemonicIndexes()
return self.buildSeedPhrasesFromIndexes(indexes)
proc generateRandomPUK*(self: Controller): string =
return self.keycardServiceV2.generateRandomPUK()

proc validateLocalPairingConnectionString*(self: Controller, connectionString: string): bool =
let err = self.devicesService.validateConnectionString(connectionString)
Expand Down Expand Up @@ -110,6 +144,14 @@ proc restoreAccountAndLogin*(self: Controller, password, mnemonic: string, recov
keycardInstanceUID,
)

proc restoreKeycardAccountAndLogin*(self: Controller, keyUid, instanceUid: string, keycardKeys: KeycardExportedKeysDto, recoverAccount: bool): string =
return self.accountsService.restoreKeycardAccountAndLoginV2(
keyUid,
instanceUid,
keycardKeys,
recoverAccount,
)

proc setLoggedInAccount*(self: Controller, account: AccountDto) =
self.accountsService.setLoggedInAccount(account)
self.accountsService.updateLoggedInAccount(account.name, account.images)
Expand All @@ -123,3 +165,12 @@ proc loginLocalPairingAccount*(self: Controller, account: AccountDto, password,

proc finishPairingThroughSeedPhraseProcess*(self: Controller, installationId: string) =
self.devicesService.finishPairingThroughSeedPhraseProcess(installationId)

proc stopKeycardService*(self: Controller) =
self.keycardServiceV2.stop()

proc generateMnemonic*(self: Controller, length: int): string =
return self.keycardServiceV2.generateMnemonic(length)

proc exportRecoverKeysFromKeycard*(self: Controller) =
self.keycardServiceV2.asyncExportRecoverKeys()
33 changes: 32 additions & 1 deletion src/app/modules/onboarding/io_interface.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ type

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
from app_service/service/devices/dto/local_pairing_status import LocalPairingStatus

method delete*(self: AccessInterface) {.base.} =
Expand All @@ -17,7 +18,10 @@ method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, s
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

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

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

method getPasswordStrengthScore*(self: AccessInterface, password, userName: string): int {.base.} =
Expand All @@ -35,12 +39,39 @@ method validateLocalPairingConnectionString*(self: AccessInterface, connectionSt
method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string) {.base.} =
raise newException(ValueError, "No implementation available")

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

method finishOnboardingFlow*(self: AccessInterface, flowInt: int, dataJson: string): string {.base.} =
raise newException(ValueError, "No implementation available")

method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =
raise newException(ValueError, "No implementation available")

method onKeycardStateUpdated*(self: AccessInterface, keycardEvent: KeycardEventDto) {.base.} =
raise newException(ValueError, "No implementation available")

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

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

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

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

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

method onKeycardExportKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
raise newException(ValueError, "No implementation available")

method exportRecoverKeys*(self: AccessInterface) {.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
91 changes: 78 additions & 13 deletions src/app/modules/onboarding/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import app_service/service/devices/service as devices_service
import app_service/service/keycardV2/service as keycard_serviceV2
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

export io_interface

Expand All @@ -22,14 +23,19 @@ type SecondaryFlow* {.pure} = enum
Unknown = 0,
CreateProfileWithPassword,
CreateProfileWithSeedphrase,
CreateProfileWithKeycard,
CreateProfileWithKeycardNewSeedphrase,
CreateProfileWithKeycardExistingSeedphrase,
LoginWithSeedphrase,
LoginWithSyncing,
LoginWithKeycard,
ActualLogin, # TODO get the real name and value for this when it's implemented on the front-end

type ProgressState* {.pure.} = enum
Idle,
InProgress,
Success,
Failed

type
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
delegate: T
Expand All @@ -38,6 +44,7 @@ type
controller: Controller
localPairingStatus: LocalPairingStatus
currentFlow: SecondaryFlow
exportedKeys: KeycardExportedKeysDto

proc newModule*[T](
delegate: T,
Expand Down Expand Up @@ -82,8 +89,13 @@ method load*[T](self: Module[T]) =
self.controller.init()
self.delegate.onboardingDidLoad()

method setPin*[T](self: Module[T], pin: string): bool =
self.controller.setPin(pin)
method initialize*[T](self: Module[T], pin: string) =
self.view.setPinSettingState(ProgressState.InProgress.int)
self.controller.initialize(pin)

method authorize*[T](self: Module[T], pin: string) =
self.view.setAuthorizationState(ProgressState.InProgress.int)
self.controller.authorize(pin)

method getPasswordStrengthScore*[T](self: Module[T], password, userName: string): int =
self.controller.getPasswordStrengthScore(password, userName)
Expand All @@ -92,14 +104,18 @@ method validMnemonic*[T](self: Module[T], mnemonic: string): bool =
self.controller.validMnemonic(mnemonic)

method getMnemonic*[T](self: Module[T]): string =
self.controller.getMnemonic()
return self.controller.generateMnemonic(SupportedMnemonicLength12)

method validateLocalPairingConnectionString*[T](self: Module[T], connectionString: string): bool =
self.controller.validateLocalPairingConnectionString(connectionString)

method inputConnectionStringForBootstrapping*[T](self: Module[T], connectionString: string) =
self.controller.inputConnectionStringForBootstrapping(connectionString)

method loadMnemonic*[T](self: Module[T], mnemonic: string) =
self.view.setAddKeyPairState(ProgressState.InProgress.int)
self.controller.loadMnemonic(mnemonic)

method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string): string =
try:
self.currentFlow = SecondaryFlow(flowInt)
Expand All @@ -121,15 +137,24 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
recoverAccount = false,
keycardInstanceUID = "",
)
of SecondaryFlow.CreateProfileWithKeycard:
# TODO implement keycard function
discard
of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase:
# TODO implement keycard function
discard
# New user with a seedphrase we showed them
let keycardEvent = self.view.getKeycardEvent()
err = self.controller.restoreAccountAndLogin(
password = "", # For keycard it will be substituted with`encryption.publicKey` in status-go
seedPhrase,
recoverAccount = false,
keycardInstanceUID = keycardEvent.keycardInfo.instanceUID,
)
of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase:
# TODO implement keycard function
discard
# New user who entered their own seed phrase
let keycardEvent = self.view.getKeycardEvent()
err = self.controller.restoreAccountAndLogin(
password = "", # For keycard it will be substituted with`encryption.publicKey` in status-go
seedPhrase,
recoverAccount = false,
keycardInstanceUID = keycardEvent.keycardInfo.instanceUID,
)

# LOGIN FLOWS
of SecondaryFlow.LoginWithSeedphrase:
Expand All @@ -147,8 +172,12 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
self.localPairingStatus.chatKey,
)
of SecondaryFlow.LoginWithKeycard:
# TODO implement keycard function
discard
err = self.controller.restoreKeycardAccountAndLogin(
self.view.getKeycardEvent().keycardInfo.keyUID,
self.view.getKeycardEvent().keycardInfo.instanceUID,
self.exportedKeys,
recoverAccount = true
)
else:
raise newException(ValueError, "Unknown flow: " & $self.currentFlow)

Expand All @@ -167,6 +196,8 @@ proc finishAppLoading2[T](self: Module[T]) =
singletonInstance.globalEvents.addCentralizedMetricIfEnabled(eventType,
$(%*{"flowType": repr(self.currentFlow)}))

self.controller.stopKeycardService()

self.delegate.finishAppLoading()

method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) =
Expand All @@ -187,4 +218,38 @@ method onLocalPairingStatusUpdate*[T](self: Module[T], status: LocalPairingStatu
self.localPairingStatus = status
self.view.setSyncState(status.state.int)

method onKeycardStateUpdated*[T](self: Module[T], keycardEvent: KeycardEventDto) =
self.view.setKeycardEvent(keycardEvent)

if keycardEvent.state == KeycardState.NotEmpty and self.view.getPinSettingState() == ProgressState.InProgress.int:
# We just finished setting the pin
self.view.setPinSettingState(ProgressState.Success.int)

if keycardEvent.state == KeycardState.Authorized and self.view.getAuthorizationState() == ProgressState.InProgress.int:
# We just finished authorizing
self.view.setAuthorizationState(ProgressState.Success.int)

method onKeycardSetPinFailure*[T](self: Module[T], error: string) =
self.view.setPinSettingState(ProgressState.Failed.int)

method onKeycardAuthorizeFailure*[T](self: Module[T], error: string) =
self.view.setAuthorizationState(ProgressState.Failed.int)

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) =
self.view.setRestoreKeysExportState(ProgressState.Failed.int)

method onKeycardExportKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
self.exportedKeys = exportedKeys
self.view.setRestoreKeysExportState(ProgressState.Success.int)

method exportRecoverKeys*[T](self: Module[T]) =
self.view.setRestoreKeysExportState(ProgressState.InProgress.int)
self.controller.exportRecoverKeysFromKeycard()

{.pop.}
Loading

0 comments on commit 0061e2b

Please sign in to comment.