Skip to content

Commit

Permalink
PM-10524: Persist user's autofill and vault unlock setup progress in …
Browse files Browse the repository at this point in the history
…create account flow (#944)
  • Loading branch information
matt-livefront committed Sep 19, 2024
1 parent 1893bd1 commit 67ee5d9
Show file tree
Hide file tree
Showing 22 changed files with 520 additions and 166 deletions.
20 changes: 18 additions & 2 deletions BitwardenShared/Core/Auth/Services/AuthService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,14 @@ protocol AuthService {
/// - password: The master password.
/// - username: The username.
/// - captchaToken: An optional captcha token value to add to the token request.
/// - isNewAccount: Whether the user is logging into a newly created account.
///
func loginWithMasterPassword(_ password: String, username: String, captchaToken: String?) async throws
func loginWithMasterPassword(
_ password: String,
username: String,
captchaToken: String?,
isNewAccount: Bool
) async throws

/// Login with the single sign on code.
///
Expand Down Expand Up @@ -538,7 +544,12 @@ class DefaultAuthService: AuthService { // swiftlint:disable:this type_body_leng
return (loginWithDeviceData.privateKey, key)
}

func loginWithMasterPassword(_ masterPassword: String, username: String, captchaToken: String?) async throws {
func loginWithMasterPassword(
_ masterPassword: String,
username: String,
captchaToken: String?,
isNewAccount: Bool
) async throws {
// Complete the pre-login steps.
let response = try await accountAPIService.preLogin(email: username)

Expand Down Expand Up @@ -573,6 +584,11 @@ class DefaultAuthService: AuthService { // swiftlint:disable:this type_body_leng
if try await requirePasswordChange(email: username, masterPassword: masterPassword, policy: policy) {
try await stateService.setForcePasswordResetReason(.weakMasterPasswordOnLogin)
}

if isNewAccount, await configService.getFeatureFlag(.nativeCreateAccountFlow) {
try await stateService.setAccountSetupAutofill(.incomplete)
try await stateService.setAccountSetupVaultUnlock(.incomplete)
}
}

/// Check TDE user decryption options to see if can unlock with trusted deviceKey or needs
Expand Down
92 changes: 85 additions & 7 deletions BitwardenShared/Core/Auth/Services/AuthServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,77 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)

// Verify the results.
let preLoginRequest = PreLoginRequestModel(
email: "email@example.com"
)
let tokenRequest = IdentityTokenRequestModel(
authenticationMethod: .password(username: "email@example.com", password: "hashed password"),
captchaToken: nil,
deviceInfo: DeviceInfo(
identifier: "App id",
name: "Model id"
),
loginRequestId: nil
)
XCTAssertEqual(client.requests.count, 2)
XCTAssertEqual(client.requests[0].body, try preLoginRequest.encode())
XCTAssertEqual(client.requests[1].body, try tokenRequest.encode())

XCTAssertEqual(clientService.mockAuth.hashPasswordEmail, "user@bitwarden.com")
XCTAssertEqual(clientService.mockAuth.hashPasswordPassword, "Password1234!")
XCTAssertEqual(clientService.mockAuth.hashPasswordKdfParams, .pbkdf2(iterations: 600_000))

XCTAssertEqual(stateService.accountsAdded, [Account.fixtureAccountLogin()])
XCTAssertEqual(
stateService.accountEncryptionKeys,
[
"13512467-9cfe-43b0-969f-07534084764b": AccountEncryptionKeys(
encryptedPrivateKey: "PRIVATE_KEY",
encryptedUserKey: "KEY"
),
]
)
XCTAssertNil(stateService.accountSetupAutofill["13512467-9cfe-43b0-969f-07534084764b"])
XCTAssertNil(stateService.accountSetupVaultUnlock["13512467-9cfe-43b0-969f-07534084764b"])
XCTAssertEqual(
stateService.masterPasswordHashes,
["13512467-9cfe-43b0-969f-07534084764b": "hashed password"]
)
try XCTAssertEqual(
keychainRepository.getValue(for: .accessToken(userId: "13512467-9cfe-43b0-969f-07534084764b")),
IdentityTokenResponseModel.fixture().accessToken
)
try XCTAssertEqual(
keychainRepository.getValue(for: .refreshToken(userId: "13512467-9cfe-43b0-969f-07534084764b")),
IdentityTokenResponseModel.fixture().refreshToken
)
assertGetConfig()
}

/// `loginWithMasterPassword(_:username:captchaToken:)` logs the user in with the password for
/// a newly created account.
func test_loginWithMasterPassword_isNewAccount() async throws { // swiftlint:disable:this function_body_length
client.results = [
.httpSuccess(testData: .preLoginSuccess),
.httpSuccess(testData: .identityTokenSuccess),
]
appSettingsStore.appId = "App id"
clientService.mockAuth.hashPasswordResult = .success("hashed password")
configService.featureFlagsBool[.nativeCreateAccountFlow] = true
stateService.preAuthEnvironmentUrls = EnvironmentUrlData(base: URL(string: "https://vault.bitwarden.com"))
systemDevice.modelIdentifier = "Model id"

// Attempt to login.
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil,
isNewAccount: true
)

// Verify the results.
Expand Down Expand Up @@ -365,6 +435,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
),
]
)
XCTAssertEqual(stateService.accountSetupAutofill["13512467-9cfe-43b0-969f-07534084764b"], .incomplete)
XCTAssertEqual(stateService.accountSetupVaultUnlock["13512467-9cfe-43b0-969f-07534084764b"], .incomplete)
XCTAssertEqual(
stateService.masterPasswordHashes,
["13512467-9cfe-43b0-969f-07534084764b": "hashed password"]
Expand Down Expand Up @@ -398,7 +470,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)

// Verify the results.
Expand Down Expand Up @@ -461,7 +534,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)
}

Expand Down Expand Up @@ -607,7 +681,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)
}

Expand Down Expand Up @@ -675,7 +750,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)
}

Expand Down Expand Up @@ -713,7 +789,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)
}

Expand Down Expand Up @@ -823,7 +900,8 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
try await subject.loginWithMasterPassword(
"Password1234!",
username: "email@example.com",
captchaToken: nil
captchaToken: nil,
isNewAccount: false
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class MockAuthService: AuthService {
var loginWithMasterPasswordPassword: String?
var loginWithMasterPasswordUsername: String?
var loginWithMasterPasswordCaptchaToken: String?
var loginWithMasterPasswordIsNewAccount = false
var loginWithMasterPasswordResult: Result<Void, Error> = .success(())

var loginWithSingleSignOnCode: String?
Expand Down Expand Up @@ -125,10 +126,16 @@ class MockAuthService: AuthService {
return try loginWithDeviceResult.get()
}

func loginWithMasterPassword(_ password: String, username: String, captchaToken: String?) async throws {
func loginWithMasterPassword(
_ password: String,
username: String,
captchaToken: String?,
isNewAccount: Bool
) async throws {
loginWithMasterPasswordPassword = password
loginWithMasterPasswordUsername = username
loginWithMasterPasswordCaptchaToken = captchaToken
loginWithMasterPasswordIsNewAccount = isNewAccount
try loginWithMasterPasswordResult.get()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// MARK: - AccountSetupProgress

/// An enum to represent a user's progress towards setting up new account functionality.
///
enum AccountSetupProgress: Int, Codable {
/// The user hasn't yet made any progress.
case incomplete = 0

/// The user choose to set up the functionality later.
case setUpLater = 1

/// The user has completed the set up.
case complete = 2
}
Loading

0 comments on commit 67ee5d9

Please sign in to comment.