Skip to content

Commit

Permalink
PM-11396: Log the user in and unlock the vault after creating an acco…
Browse files Browse the repository at this point in the history
…unt (#880)
  • Loading branch information
matt-livefront authored Aug 29, 2024
1 parent b9ae875 commit c14e27b
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class CompleteRegistrationProcessor: StateProcessor<

typealias Services = HasAccountAPIService
& HasAuthRepository
& HasAuthService
& HasClientService
& HasConfigService
& HasEnvironmentService
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase {
// MARK: Properties

var authRepository: MockAuthRepository!
var authService: MockAuthService!
var client: MockHTTPClient!
var clientAuth: MockClientAuth!
var configService: MockConfigService!
Expand All @@ -24,6 +25,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase {
override func setUp() {
super.setUp()
authRepository = MockAuthRepository()
authService = MockAuthService()
client = MockHTTPClient()
clientAuth = MockClientAuth()
configService = MockConfigService()
Expand All @@ -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,
Expand All @@ -50,6 +53,7 @@ class CompleteRegistrationProcessorTests: BitwardenTestCase {
override func tearDown() {
super.tearDown()
authRepository = nil
authService = nil
clientAuth = nil
client = nil
coordinator = nil
Expand Down Expand Up @@ -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 protected]")

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,
Expand Down Expand Up @@ -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
Expand All @@ -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 protected]")

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,
Expand Down Expand Up @@ -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
Expand All @@ -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 protected]")

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,
Expand Down

0 comments on commit c14e27b

Please sign in to comment.