From c14e27b5896f25536146b1525aebe8bb497a43e6 Mon Sep 17 00:00:00 2001 From: Matt Czech Date: Thu, 29 Aug 2024 14:55:58 -0500 Subject: [PATCH] PM-11396: Log the user in and unlock the vault after creating an account (#880) --- .../CompleteRegistrationProcessor.swift | 75 +++++++++++-------- .../CompleteRegistrationProcessorTests.swift | 43 ++++++----- 2 files changed, 69 insertions(+), 49 deletions(-) diff --git a/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessor.swift b/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessor.swift index f3e541af0..4ec792311 100644 --- a/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessor.swift +++ b/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessor.swift @@ -32,6 +32,7 @@ class CompleteRegistrationProcessor: StateProcessor< typealias Services = HasAccountAPIService & HasAuthRepository + & HasAuthService & HasClientService & HasConfigService & HasEnvironmentService @@ -154,6 +155,43 @@ class CompleteRegistrationProcessor: StateProcessor< } } + /// Performs an API request to create the user's account. + /// + /// - Parameter captchaToken: The token returned when the captcha flow has completed. + /// + private func createAccount(captchaToken: String?) async throws { + let kdfConfig = KdfConfig() + + let keys = try await services.clientService.auth().makeRegisterKeys( + email: state.userEmail, + password: state.passwordText, + kdf: kdfConfig.sdkKdf + ) + + let hashedPassword = try await services.clientService.auth().hashPassword( + email: state.userEmail, + password: state.passwordText, + kdfParams: kdfConfig.sdkKdf, + purpose: .serverAuthorization + ) + + _ = try await services.accountAPIService.registerFinish( + body: RegisterFinishRequestModel( + captchaResponse: captchaToken, + email: state.userEmail, + emailVerificationToken: state.emailVerificationToken, + kdfConfig: kdfConfig, + masterPasswordHash: hashedPassword, + masterPasswordHint: state.passwordHintText, + userSymmetricKey: keys.encryptedUserKey, + userAsymmetricKeys: KeysRequestModel( + encryptedPrivateKey: keys.keys.private, + publicKey: keys.keys.public + ) + ) + ) + } + /// Creates the user's account with their provided credentials. /// /// - Parameter captchaToken: The token returned when the captcha flow has completed. @@ -174,40 +212,17 @@ class CompleteRegistrationProcessor: StateProcessor< coordinator.showLoadingOverlay(title: Localizations.creatingAccount) - let kdf: Kdf = .pbkdf2(iterations: NonZeroU32(KdfConfig().kdfIterations)) + try await createAccount(captchaToken: captchaToken) - let keys = try await services.clientService.auth().makeRegisterKeys( - email: state.userEmail, - password: state.passwordText, - kdf: kdf - ) - - let hashedPassword = try await services.clientService.auth().hashPassword( - email: state.userEmail, - password: state.passwordText, - kdfParams: kdf, - purpose: .serverAuthorization + try await services.authService.loginWithMasterPassword( + state.passwordText, + username: state.userEmail, + captchaToken: captchaToken ) - _ = try await services.accountAPIService.registerFinish( - body: RegisterFinishRequestModel( - captchaResponse: captchaToken, - email: state.userEmail, - emailVerificationToken: state.emailVerificationToken, - kdfConfig: KdfConfig(), - masterPasswordHash: hashedPassword, - masterPasswordHint: state.passwordHintText, - userSymmetricKey: keys.encryptedUserKey, - userAsymmetricKeys: KeysRequestModel( - encryptedPrivateKey: keys.keys.private, - publicKey: keys.keys.public - ) - ) - ) + try await services.authRepository.unlockVaultWithPassword(password: state.passwordText) - coordinator.navigate(to: .dismissWithAction(DismissAction { - self.coordinator.showToast(Localizations.accountSuccessfullyCreated) - })) + await coordinator.handleEvent(.didCompleteAuth) } catch let error as CompleteRegistrationError { showCompleteRegistrationErrorAlert(error) } catch { diff --git a/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessorTests.swift b/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessorTests.swift index 720b1dfb2..fc21567f5 100644 --- a/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessorTests.swift +++ b/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationProcessorTests.swift @@ -11,6 +11,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { // MARK: Properties var authRepository: MockAuthRepository! + var authService: MockAuthService! var client: MockHTTPClient! var clientAuth: MockClientAuth! var configService: MockConfigService! @@ -24,6 +25,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { override func setUp() { super.setUp() authRepository = MockAuthRepository() + authService = MockAuthService() client = MockHTTPClient() clientAuth = MockClientAuth() configService = MockConfigService() @@ -34,6 +36,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { coordinator: coordinator.asAnyCoordinator(), services: ServiceContainer.withMocks( authRepository: authRepository, + authService: authService, clientService: MockClientService(auth: clientAuth), configService: configService, environmentService: environmentService, @@ -50,6 +53,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { override func tearDown() { super.tearDown() authRepository = nil + authService = nil clientAuth = nil client = nil coordinator = nil @@ -193,22 +197,25 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { /// network request fails. @MainActor func test_perform_checkPasswordAndCompleteRegistration_failure() async throws { + authService.loginWithMasterPasswordResult = .success(()) subject.state = .fixture(isCheckDataBreachesToggleOn: true) - client.results = [.httpFailure(URLError(.timedOut) as Error), .httpSuccess(testData: .createAccountRequest)] + client.results = [ + .httpFailure(URLError(.timedOut) as Error), + .httpSuccess(testData: .createAccountRequest), + ] await subject.perform(.completeRegistration) - var dismissAction: DismissAction? - if case let .dismissWithAction(onDismiss) = coordinator.routes.last { - dismissAction = onDismiss - } - XCTAssertNotNil(dismissAction) - dismissAction?.action() + + XCTAssertEqual(authService.loginWithMasterPasswordPassword, "password1234") + XCTAssertNil(authService.loginWithMasterPasswordCaptchaToken) + XCTAssertEqual(authService.loginWithMasterPasswordUsername, "email@example.com") XCTAssertEqual(client.requests.count, 2) XCTAssertEqual(client.requests[0].url, URL(string: "https://api.pwnedpasswords.com/range/e6b6a")) XCTAssertEqual(client.requests[1].url, URL(string: "https://example.com/identity/accounts/register/finish")) + XCTAssertEqual(coordinator.events, [.didCompleteAuth]) XCTAssertFalse(coordinator.isLoadingOverlayShowing) XCTAssertEqual( coordinator.loadingOverlaysShown, @@ -480,6 +487,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { /// When the user taps `Try again`, the create account request is made again. @MainActor func test_perform_completeRegistration_noInternetConnection() async throws { + authService.loginWithMasterPasswordResult = .success(()) subject.state = .fixture() let urlError = URLError(.notConnectedToInternet) as Error @@ -494,17 +502,15 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { try await alert.tapAction(title: Localizations.tryAgain) - var dismissAction: DismissAction? - if case let .dismissWithAction(onDismiss) = coordinator.routes.last { - dismissAction = onDismiss - } - XCTAssertNotNil(dismissAction) - dismissAction?.action() + XCTAssertEqual(authService.loginWithMasterPasswordPassword, "password1234") + XCTAssertNil(authService.loginWithMasterPasswordCaptchaToken) + XCTAssertEqual(authService.loginWithMasterPasswordUsername, "email@example.com") XCTAssertEqual(client.requests.count, 2) XCTAssertEqual(client.requests[0].url, URL(string: "https://example.com/identity/accounts/register/finish")) XCTAssertEqual(client.requests[1].url, URL(string: "https://example.com/identity/accounts/register/finish")) + XCTAssertEqual(coordinator.events, [.didCompleteAuth]) XCTAssertFalse(coordinator.isLoadingOverlayShowing) XCTAssertEqual( coordinator.loadingOverlaysShown, @@ -547,6 +553,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { /// When the user taps `Try again`, the create account request is made again. @MainActor func test_perform_completeRegistration_timeout() async throws { + authService.loginWithMasterPasswordResult = .success(()) subject.state = .fixture() let urlError = URLError(.timedOut) as Error @@ -559,17 +566,15 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase { try await alert.tapAction(title: Localizations.tryAgain) - var dismissAction: DismissAction? - if case let .dismissWithAction(onDismiss) = coordinator.routes.last { - dismissAction = onDismiss - } - XCTAssertNotNil(dismissAction) - dismissAction?.action() + XCTAssertEqual(authService.loginWithMasterPasswordPassword, "password1234") + XCTAssertNil(authService.loginWithMasterPasswordCaptchaToken) + XCTAssertEqual(authService.loginWithMasterPasswordUsername, "email@example.com") XCTAssertEqual(client.requests.count, 2) XCTAssertEqual(client.requests[0].url, URL(string: "https://example.com/identity/accounts/register/finish")) XCTAssertEqual(client.requests[1].url, URL(string: "https://example.com/identity/accounts/register/finish")) + XCTAssertEqual(coordinator.events, [.didCompleteAuth]) XCTAssertFalse(coordinator.isLoadingOverlayShowing) XCTAssertEqual( coordinator.loadingOverlaysShown,