Skip to content

Commit

Permalink
PM-11469: Hide master password text field if account does not have MP. (
Browse files Browse the repository at this point in the history
  • Loading branch information
ezimet-livefront authored Nov 27, 2024
1 parent 88da015 commit 092d092
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 14 deletions.
13 changes: 13 additions & 0 deletions BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class VaultUnlockProcessor: StateProcessor<
case .appeared:
await refreshProfileState()
await checkIfPinUnlockIsAvailable()
await checkIfShouldShowPasswordOrPinFields()
await loadData()
case let .profileSwitcher(profileEffect):
await handleProfileSwitcherEffect(profileEffect)
Expand Down Expand Up @@ -105,6 +106,18 @@ class VaultUnlockProcessor: StateProcessor<

// MARK: Private

/// Checks whether or not the user has a master password or PIN set and updates the state accordingly.
///
private func checkIfShouldShowPasswordOrPinFields() async {
do {
let hasMasterPassword = try await services.authRepository.hasMasterPassword()
state.shouldShowPasswordOrPinFields = hasMasterPassword || state.unlockMethod == .pin
} catch {
services.errorReporter.log(error: error)
state.shouldShowPasswordOrPinFields = true
}
}

/// Checks whether or not pin unlock is available.
///
private func checkIfPinUnlockIsAvailable() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,66 @@ class VaultUnlockProcessorTests: BitwardenTestCase { // swiftlint:disable:this t
)
}

/// `perform(.appeared)` with no master password but with a biometrics status enabled,
/// should yields the expected `shouldShowPasswordOrPinFields` status.
@MainActor
func test_perform_appeared_shouldShowPasswordOrPinFields_false() async {
stateService.activeAccount = .fixture()
let expectedStatus = BiometricsUnlockStatus.available(.touchID, enabled: true)
biometricsRepository.biometricUnlockStatus = .success(expectedStatus)
authRepository.isPinUnlockAvailableResult = .success(false)
authRepository.hasMasterPasswordResult = .success(false)
await subject.perform(.appeared)

XCTAssertEqual(subject.state.biometricUnlockStatus, expectedStatus)
XCTAssertFalse(subject.state.shouldShowPasswordOrPinFields)
}

/// `perform(.appeared)` with no master password but with PIN enabled,
/// should yields the expected `shouldShowPasswordOrPinFields` status.
@MainActor
func test_perform_appeared_shouldShowPasswordOrPinFields_true_pin() async {
stateService.activeAccount = .fixture()
let expectedStatus = BiometricsUnlockStatus.notAvailable
biometricsRepository.biometricUnlockStatus = .success(expectedStatus)
authRepository.isPinUnlockAvailableResult = .success(true)
authRepository.hasMasterPasswordResult = .success(false)
await subject.perform(.appeared)

XCTAssertEqual(subject.state.biometricUnlockStatus, expectedStatus)
XCTAssertTrue(subject.state.shouldShowPasswordOrPinFields)
}

/// `perform(.appeared)` with no PIN or biometric status enabled, but with a master password,
/// should yields the expected `shouldShowPasswordOrPinFields` status.
@MainActor
func test_perform_appeared_shouldShowPasswordOrPinFields_true_masterPassword() async {
stateService.activeAccount = .fixture()
let expectedStatus = BiometricsUnlockStatus.notAvailable
biometricsRepository.biometricUnlockStatus = .success(expectedStatus)
authRepository.isPinUnlockAvailableResult = .success(false)
authRepository.hasMasterPasswordResult = .success(true)
await subject.perform(.appeared)

XCTAssertEqual(subject.state.biometricUnlockStatus, expectedStatus)
XCTAssertTrue(subject.state.shouldShowPasswordOrPinFields)
}

/// `perform(.appeared)` with no PIN or biometric status enabled, but with a master password error,
/// should yields the expected `shouldShowPasswordOrPinFields` status.
@MainActor
func test_perform_appeared_shouldShowPasswordOrPinFields_true_masterPasswordError() async {
stateService.activeAccount = .fixture()
let expectedStatus = BiometricsUnlockStatus.notAvailable
biometricsRepository.biometricUnlockStatus = .success(expectedStatus)
authRepository.isPinUnlockAvailableResult = .success(false)
authRepository.hasMasterPasswordResult = .failure(BitwardenTestError.example)
await subject.perform(.appeared)

XCTAssertEqual(subject.state.biometricUnlockStatus, expectedStatus)
XCTAssertTrue(subject.state.shouldShowPasswordOrPinFields)
}

/// `perform(.appeared)` with an active account and accounts should yield a profile switcher state.
@MainActor
func test_perform_appeared_profiles_single_active() async {
Expand Down
3 changes: 3 additions & 0 deletions BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ struct VaultUnlockState: Equatable {
/// Whether the pin is revealed.
var isPinRevealed = false

/// Whether the master password or PIN field and the unlock button should be displayed.
var shouldShowPasswordOrPinFields: Bool = true

/// The master password provided by the user.
var masterPassword: String = ""

Expand Down
31 changes: 17 additions & 14 deletions BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,27 @@ struct VaultUnlockView: View {
@ViewBuilder var scrollView: some View {
ScrollView {
VStack(spacing: 24) {
VStack(alignment: .leading, spacing: 8) {
textField

Text(footerText)
.styleGuide(.footnote)
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
.accessibilityIdentifier("UserAndEnvironmentDataLabel")
if store.state.shouldShowPasswordOrPinFields {
VStack(alignment: .leading, spacing: 8) {
textField

Text(footerText)
.styleGuide(.footnote)
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
.accessibilityIdentifier("UserAndEnvironmentDataLabel")
}
}

biometricAuthButton

AsyncButton {
await store.perform(.unlockVault)
} label: {
Text(Localizations.unlock)
if store.state.shouldShowPasswordOrPinFields {
AsyncButton {
await store.perform(.unlockVault)
} label: {
Text(Localizations.unlock)
}
.buttonStyle(.primary(shouldFillWidth: true))
.accessibilityIdentifier("UnlockVaultButton")
}
.buttonStyle(.primary(shouldFillWidth: true))
.accessibilityIdentifier("UnlockVaultButton")
}
.padding(16)
}
Expand Down
30 changes: 30 additions & 0 deletions BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,36 @@ class VaultUnlockViewTests: BitwardenTestCase {
assertSnapshot(of: subject, as: .defaultPortrait)
}

/// Test a snapshot of the view with no master password or pin but with touch id.
@MainActor
func test_snapshot_shouldShowPasswordOrPinFields_false_touchId() {
processor.state.shouldShowPasswordOrPinFields = false
processor.state.biometricUnlockStatus = .available(
.touchID,
enabled: true
)
assertSnapshot(of: subject, as: .defaultPortrait)
}

/// Test a snapshot of the view with no master password or pin but with face id.
@MainActor
func test_snapshot_shouldShowPasswordOrPinFields_false_faceId() {
processor.state.shouldShowPasswordOrPinFields = false
processor.state.biometricUnlockStatus = .available(
.faceID,
enabled: true
)
assertSnapshot(of: subject, as: .defaultPortrait)
}

/// Test a snapshot of the view with no master password but with pin.
@MainActor
func test_snapshot_shouldShowPasswordOrPinFields_true_pin() {
processor.state.shouldShowPasswordOrPinFields = true
processor.state.unlockMethod = .pin
assertSnapshot(of: subject, as: .defaultPortrait)
}

/// Test a snapshot of the view when the password is hidden.
@MainActor
func test_snapshot_vaultUnlock_passwordHidden() {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 092d092

Please sign in to comment.