Skip to content

Commit 49940d9

Browse files
jrainvilleigor-sirotin
authored andcommitted
feat(login): integrate basic login flows happy paths
Fixes #17137
1 parent be1059b commit 49940d9

File tree

17 files changed

+370
-94
lines changed

17 files changed

+370
-94
lines changed

src/app/boot/app_controller.nim

+2-5
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,9 @@ proc connect(self: AppController) =
148148
elif defined(production):
149149
setLogLevel(chronicles.LogLevel.INFO)
150150

151-
# TODO remove these functions once we have only the new onboarding module
152-
proc shouldStartWithOnboardingScreen(self: AppController): bool =
153-
return self.accountsService.openedAccounts().len == 0
151+
# TODO remove this function once we have only the new onboarding module
154152
proc shouldUseTheNewOnboardingModule(self: AppController): bool =
155-
# Only the onboarding for new users is implemented in the new module for now
156-
return singletonInstance.featureFlags().getOnboardingV2Enabled() and self.shouldStartWithOnboardingScreen()
153+
return singletonInstance.featureFlags().getOnboardingV2Enabled()
157154

158155
proc newAppController*(statusFoundation: StatusFoundation): AppController =
159156
result = AppController()

src/app/modules/onboarding/controller.nim

+57-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import app_service/service/accounts/service as accounts_service
99
import app_service/service/accounts/dto/image_crop_rectangle
1010
import app_service/service/devices/service as devices_service
1111
import app_service/service/keycardV2/service as keycard_serviceV2
12+
import app_service/common/utils
1213
from app_service/service/keycardV2/dto import KeycardExportedKeysDto
1314

1415
logScope:
@@ -86,14 +87,29 @@ proc init*(self: Controller) =
8687
self.delegate.onKeycardLoadMnemonicSuccess(args.keyUID)
8788
self.connectionIds.add(handlerId)
8889

89-
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_FAILURE) do(e: Args):
90+
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_FAILURE) do(e: Args):
9091
let args = KeycardErrorArg(e)
91-
self.delegate.onKeycardExportKeysFailure(args.error)
92+
self.delegate.onKeycardExportRestoreKeysFailure(args.error)
9293
self.connectionIds.add(handlerId)
9394

94-
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_KEYS_SUCCESS) do(e: Args):
95+
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_RESTORE_KEYS_SUCCESS) do(e: Args):
9596
let args = KeycardExportedKeysArg(e)
96-
self.delegate.onKeycardExportKeysSuccess(args.exportedKeys)
97+
self.delegate.onKeycardExportRestoreKeysSuccess(args.exportedKeys)
98+
self.connectionIds.add(handlerId)
99+
100+
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_FAILURE) do(e: Args):
101+
let args = KeycardErrorArg(e)
102+
self.delegate.onKeycardExportLoginKeysFailure(args.error)
103+
self.connectionIds.add(handlerId)
104+
105+
handlerId = self.events.onWithUUID(SIGNAL_KEYCARD_EXPORT_LOGIN_KEYS_SUCCESS) do(e: Args):
106+
let args = KeycardExportedKeysArg(e)
107+
self.delegate.onKeycardExportLoginKeysSuccess(args.exportedKeys)
108+
self.connectionIds.add(handlerId)
109+
110+
handlerId = self.events.onWithUUID(SIGNAL_LOGIN_ERROR) do(e: Args):
111+
let args = LoginErrorArgs(e)
112+
self.delegate.onAccountLoginError(args.error)
97113
self.connectionIds.add(handlerId)
98114

99115
proc initialize*(self: Controller, pin: string) =
@@ -174,3 +190,40 @@ proc generateMnemonic*(self: Controller, length: int): string =
174190

175191
proc exportRecoverKeysFromKeycard*(self: Controller) =
176192
self.keycardServiceV2.asyncExportRecoverKeys()
193+
194+
proc exportLoginKeysFromKeycard*(self: Controller) =
195+
self.keycardServiceV2.asyncExportLoginKeys()
196+
197+
proc getOpenedAccounts*(self: Controller): seq[AccountDto] =
198+
return self.accountsService.openedAccounts()
199+
200+
proc getAccountByKeyUid*(self: Controller, keyUid: string): AccountDto =
201+
return self.accountsService.getAccountByKeyUid(keyUid)
202+
203+
proc login*(
204+
self: Controller,
205+
account: AccountDto,
206+
password: string,
207+
keycard: bool = false,
208+
publicEncryptionKey: string = "",
209+
privateWhisperKey: string = "",
210+
mnemonic: string = "",
211+
keycardReplacement: bool = false,
212+
) =
213+
var passwordHash, chatPrivateKey = ""
214+
215+
if not keycard:
216+
passwordHash = hashPassword(password)
217+
else:
218+
passwordHash = publicEncryptionKey
219+
chatPrivateKey = privateWhisperKey
220+
221+
# if keycard and keycardReplacement:
222+
# self.delegate.applyKeycardReplacementAfterLogin()
223+
224+
self.accountsService.login(
225+
account,
226+
passwordHash,
227+
chatPrivateKey,
228+
mnemonic,
229+
)

src/app/modules/onboarding/io_interface.nim

+14-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ method loadMnemonic*(self: AccessInterface, dataJson: string) {.base.} =
4545
method finishOnboardingFlow*(self: AccessInterface, flowInt: int, dataJson: string): string {.base.} =
4646
raise newException(ValueError, "No implementation available")
4747

48+
method loginRequested*(self: AccessInterface, keyUid: string, loginFlow: int, dataJson: string) {.base.} =
49+
raise newException(ValueError, "No implementation available")
50+
4851
method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =
4952
raise newException(ValueError, "No implementation available")
5053

@@ -63,10 +66,19 @@ method onKeycardLoadMnemonicFailure*(self: AccessInterface, error: string) {.bas
6366
method onKeycardLoadMnemonicSuccess*(self: AccessInterface, keyUID: string) {.base.} =
6467
raise newException(ValueError, "No implementation available")
6568

66-
method onKeycardExportKeysFailure*(self: AccessInterface, error: string) {.base.} =
69+
method onKeycardExportRestoreKeysFailure*(self: AccessInterface, error: string) {.base.} =
70+
raise newException(ValueError, "No implementation available")
71+
72+
method onKeycardExportRestoreKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
73+
raise newException(ValueError, "No implementation available")
74+
75+
method onKeycardExportLoginKeysFailure*(self: AccessInterface, error: string) {.base.} =
76+
raise newException(ValueError, "No implementation available")
77+
78+
method onKeycardExportLoginKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
6779
raise newException(ValueError, "No implementation available")
6880

69-
method onKeycardExportKeysSuccess*(self: AccessInterface, exportedKeys: KeycardExportedKeysDto) {.base.} =
81+
method onAccountLoginError*(self: AccessInterface, error: string) {.base.} =
7082
raise newException(ValueError, "No implementation available")
7183

7284
method exportRecoverKeys*(self: AccessInterface) {.base.} =

src/app/modules/onboarding/module.nim

+96-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import NimQml, chronicles, json
1+
import NimQml, chronicles, json, strutils
22
import logging
33

44
import io_interface
@@ -14,12 +14,14 @@ from app_service/service/settings/dto/settings import SettingsDto
1414
from app_service/service/accounts/dto/accounts import AccountDto
1515
from app_service/service/keycardV2/dto import KeycardEventDto, KeycardExportedKeysDto, KeycardState
1616

17+
import ../startup/models/login_account_item as login_acc_item
18+
1719
export io_interface
1820

1921
logScope:
2022
topics = "onboarding-module"
2123

22-
type SecondaryFlow* {.pure} = enum
24+
type OnboardingFlow* {.pure} = enum
2325
Unknown = 0,
2426
CreateProfileWithPassword,
2527
CreateProfileWithSeedphrase,
@@ -28,13 +30,17 @@ type SecondaryFlow* {.pure} = enum
2830
LoginWithSeedphrase,
2931
LoginWithSyncing,
3032
LoginWithKeycard,
31-
ActualLogin, # TODO get the real name and value for this when it's implemented on the front-end
33+
34+
type LoginMethod* {.pure} = enum
35+
Unknown = 0,
36+
Password,
37+
Keycard,
3238

3339
type ProgressState* {.pure.} = enum
3440
Idle,
3541
InProgress,
3642
Success,
37-
Failed
43+
Failed,
3844

3945
type
4046
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
@@ -43,7 +49,8 @@ type
4349
viewVariant: QVariant
4450
controller: Controller
4551
localPairingStatus: LocalPairingStatus
46-
currentFlow: SecondaryFlow
52+
loginFlow: LoginMethod
53+
onboardingFlow: OnboardingFlow
4754
exportedKeys: KeycardExportedKeysDto
4855

4956
proc newModule*[T](
@@ -58,6 +65,8 @@ proc newModule*[T](
5865
result.delegate = delegate
5966
result.view = view.newView(result)
6067
result.viewVariant = newQVariant(result.view)
68+
result.onboardingFlow = OnboardingFlow.Unknown
69+
result.loginFlow = LoginMethod.Unknown
6170
result.controller = controller.newController(
6271
result,
6372
events,
@@ -87,6 +96,20 @@ method onAppLoaded*[T](self: Module[T]) =
8796
method load*[T](self: Module[T]) =
8897
singletonInstance.engine.setRootContextProperty("onboardingModule", self.viewVariant)
8998
self.controller.init()
99+
100+
let openedAccounts = self.controller.getOpenedAccounts()
101+
if openedAccounts.len > 0:
102+
var items: seq[login_acc_item.Item]
103+
for i in 0..<openedAccounts.len:
104+
let acc = openedAccounts[i]
105+
var thumbnailImage: string
106+
var largeImage: string
107+
acc.extractImages(thumbnailImage, largeImage)
108+
items.add(login_acc_item.initItem(order = i, acc.name, icon = "", thumbnailImage, largeImage, acc.keyUid, acc.colorHash,
109+
acc.colorId, acc.keycardPairing))
110+
111+
self.view.setLoginAccountsModelItems(items)
112+
90113
self.delegate.onboardingDidLoad()
91114

92115
method initialize*[T](self: Module[T], pin: string) =
@@ -118,26 +141,26 @@ method loadMnemonic*[T](self: Module[T], mnemonic: string) =
118141

119142
method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string): string =
120143
try:
121-
self.currentFlow = SecondaryFlow(flowInt)
144+
self.onboardingFlow = OnboardingFlow(flowInt)
122145

123146
let data = parseJson(dataJson)
124147
let password = data["password"].str
125148
let seedPhrase = data["seedphrase"].str
126149

127150
var err = ""
128151

129-
case self.currentFlow:
152+
case self.onboardingFlow:
130153
# CREATE PROFILE FLOWS
131-
of SecondaryFlow.CreateProfileWithPassword:
154+
of OnboardingFlow.CreateProfileWithPassword:
132155
err = self.controller.createAccountAndLogin(password)
133-
of SecondaryFlow.CreateProfileWithSeedphrase:
156+
of OnboardingFlow.CreateProfileWithSeedphrase:
134157
err = self.controller.restoreAccountAndLogin(
135158
password,
136159
seedPhrase,
137160
recoverAccount = false,
138161
keycardInstanceUID = "",
139162
)
140-
of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase:
163+
of OnboardingFlow.CreateProfileWithKeycardNewSeedphrase:
141164
# New user with a seedphrase we showed them
142165
let keycardEvent = self.view.getKeycardEvent()
143166
err = self.controller.restoreAccountAndLogin(
@@ -146,7 +169,7 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
146169
recoverAccount = false,
147170
keycardInstanceUID = keycardEvent.keycardInfo.instanceUID,
148171
)
149-
of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase:
172+
of OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase:
150173
# New user who entered their own seed phrase
151174
let keycardEvent = self.view.getKeycardEvent()
152175
err = self.controller.restoreAccountAndLogin(
@@ -157,53 +180,78 @@ method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string)
157180
)
158181

159182
# LOGIN FLOWS
160-
of SecondaryFlow.LoginWithSeedphrase:
183+
of OnboardingFlow.LoginWithSeedphrase:
161184
err = self.controller.restoreAccountAndLogin(
162185
password,
163186
seedPhrase,
164187
recoverAccount = true,
165188
keycardInstanceUID = "",
166189
)
167-
of SecondaryFlow.LoginWithSyncing:
190+
of OnboardingFlow.LoginWithSyncing:
168191
# The pairing was already done directly through inputConnectionStringForBootstrapping, we can login
169192
self.controller.loginLocalPairingAccount(
170193
self.localPairingStatus.account,
171194
self.localPairingStatus.password,
172195
self.localPairingStatus.chatKey,
173196
)
174-
of SecondaryFlow.LoginWithKeycard:
197+
of OnboardingFlow.LoginWithKeycard:
175198
err = self.controller.restoreKeycardAccountAndLogin(
176199
self.view.getKeycardEvent().keycardInfo.keyUID,
177200
self.view.getKeycardEvent().keycardInfo.instanceUID,
178201
self.exportedKeys,
179202
recoverAccount = true
180203
)
181204
else:
182-
raise newException(ValueError, "Unknown flow: " & $self.currentFlow)
205+
raise newException(ValueError, "Unknown flow: " & $self.onboardingFlow)
183206

184207
return err
185208
except Exception as e:
186209
error "Error finishing Onboarding Flow", msg = e.msg
187210
return e.msg
188211

212+
method loginRequested*[T](self: Module[T], keyUid: string, loginFlow: int, dataJson: string) =
213+
try:
214+
self.loginFlow = LoginMethod(loginFlow)
215+
216+
let data = parseJson(dataJson)
217+
let account = self.controller.getAccountByKeyUid(keyUid)
218+
219+
case self.loginFlow:
220+
of LoginMethod.Password:
221+
self.controller.login(account, data["password"].str)
222+
of LoginMethod.Keycard:
223+
self.authorize(data["pin"].str)
224+
# We will continue the flow when the card is authorized in onKeycardStateUpdated
225+
else:
226+
raise newException(ValueError, "Unknown flow: " & $self.onboardingFlow)
227+
228+
except Exception as e:
229+
error "Error finishing Login Flow", msg = e.msg
230+
self.view.accountLoginError(e.msg, wrongPassword = false)
231+
189232
proc finishAppLoading2[T](self: Module[T]) =
190233
self.delegate.appReady()
191234

192-
# TODO get the flow to send the right metric
193235
var eventType = "user-logged-in"
194-
if self.currentFlow != SecondaryFlow.ActualLogin:
236+
if self.loginFlow == LoginMethod.Unknown:
195237
eventType = "onboarding-completed"
196238
singletonInstance.globalEvents.addCentralizedMetricIfEnabled(eventType,
197-
$(%*{"flowType": repr(self.currentFlow)}))
239+
$(%*{"flowType": repr(self.onboardingFlow)}))
198240

199241
self.controller.stopKeycardService()
200242

201243
self.delegate.finishAppLoading()
202-
244+
245+
method onAccountLoginError*[T](self: Module[T], error: string) =
246+
# SQLITE_NOTADB: "file is not a database"
247+
var wrongPassword = false
248+
if error.contains("file is not a database"):
249+
wrongPassword = true
250+
self.view.accountLoginError(error, wrongPassword)
251+
203252
method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) =
204253
if error.len != 0:
205-
# TODO: Handle error
206-
echo "ERROR from onNodeLogin: ", error
254+
self.onAccountLoginError(error)
207255
return
208256

209257
self.controller.setLoggedInAccount(account)
@@ -221,6 +269,11 @@ method onLocalPairingStatusUpdate*[T](self: Module[T], status: LocalPairingStatu
221269
method onKeycardStateUpdated*[T](self: Module[T], keycardEvent: KeycardEventDto) =
222270
self.view.setKeycardEvent(keycardEvent)
223271

272+
if keycardEvent.state == KeycardState.Authorized and self.loginFlow == LoginMethod.Keycard:
273+
# After authorizing, we export the keys
274+
self.controller.exportLoginKeysFromKeycard()
275+
# We will login once we have the keys in onKeycardExportLoginKeysSuccess
276+
224277
if keycardEvent.state == KeycardState.NotEmpty and self.view.getPinSettingState() == ProgressState.InProgress.int:
225278
# We just finished setting the pin
226279
self.view.setPinSettingState(ProgressState.Success.int)
@@ -235,19 +288,39 @@ method onKeycardSetPinFailure*[T](self: Module[T], error: string) =
235288
method onKeycardAuthorizeFailure*[T](self: Module[T], error: string) =
236289
self.view.setAuthorizationState(ProgressState.Failed.int)
237290

291+
if self.loginFlow == LoginMethod.Keycard:
292+
# We were trying to login and the authorization failed
293+
var wrongPassword = false
294+
if error.contains("wrong pin"):
295+
wrongPassword = true
296+
self.view.accountLoginError(error, wrongPassword)
297+
238298
method onKeycardLoadMnemonicFailure*[T](self: Module[T], error: string) =
239299
self.view.setAddKeyPairState(ProgressState.Failed.int)
240300

241301
method onKeycardLoadMnemonicSuccess*[T](self: Module[T], keyUID: string) =
242302
self.view.setAddKeyPairState(ProgressState.Success.int)
243303

244-
method onKeycardExportKeysFailure*[T](self: Module[T], error: string) =
304+
method onKeycardExportRestoreKeysFailure*[T](self: Module[T], error: string) =
245305
self.view.setRestoreKeysExportState(ProgressState.Failed.int)
246306

247-
method onKeycardExportKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
307+
method onKeycardExportRestoreKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
248308
self.exportedKeys = exportedKeys
249309
self.view.setRestoreKeysExportState(ProgressState.Success.int)
250310

311+
method onKeycardExportLoginKeysFailure*[T](self: Module[T], error: string) =
312+
self.view.accountLoginError(error, wrongPassword = false)
313+
314+
method onKeycardExportLoginKeysSuccess*[T](self: Module[T], exportedKeys: KeycardExportedKeysDto) =
315+
# We got the keys, now we can login. If everything goes well, we will finish the app loading
316+
self.controller.login(
317+
self.controller.getAccountByKeyUid(self.view.getKeycardEvent.keycardInfo.keyUID),
318+
password = "",
319+
keycard = true,
320+
publicEncryptionKey = exportedKeys.encryptionKey.publicKey,
321+
privateWhisperKey = exportedKeys.whisperKey.privateKey,
322+
)
323+
251324
method exportRecoverKeys*[T](self: Module[T]) =
252325
self.view.setRestoreKeysExportState(ProgressState.InProgress.int)
253326
self.controller.exportRecoverKeysFromKeycard()

0 commit comments

Comments
 (0)