generated from bitwarden/template
-
Notifications
You must be signed in to change notification settings - Fork 53
PM-10276: Set up unlock: enable pin unlock #867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a2119a4
PM-10276: Set up unlock: enable pin unlock
matt-livefront f0d3019
PM-10276: Update docs and formatting
matt-livefront 1e09b25
PM-10276: Convert toggle pin action to effect
matt-livefront 73b0c98
Merge branch 'main' into matt/PM-10276-set-up-unlock-pin
matt-livefront 9ebf6b3
PM-10276: Convert action to effect
matt-livefront File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
BitwardenShared/UI/Auth/Utilities/TestHelpers/MockVaultUnlockSetupHelper.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
@testable import BitwardenShared | ||
|
||
class MockVaultUnlockSetupHelper: VaultUnlockSetupHelper { | ||
var setBiometricUnlockCalled = false | ||
var setBiometricUnlockStatus: BiometricsUnlockStatus? | ||
|
||
var setPinUnlockCalled = false | ||
var setPinUnlockResult: Bool? | ||
|
||
func setBiometricUnlock(enabled: Bool, showAlert: @escaping (Alert) -> Void) async -> BiometricsUnlockStatus? { | ||
setBiometricUnlockCalled = true | ||
return setBiometricUnlockStatus | ||
} | ||
|
||
func setPinUnlock(enabled: Bool, showAlert: @escaping (Alert) -> Void) async -> Bool { | ||
setPinUnlockCalled = true | ||
return setPinUnlockResult ?? !enabled | ||
} | ||
} |
153 changes: 153 additions & 0 deletions
153
BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelper.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// MARK: - VaultUnlockSetupHelper | ||
|
||
/// A protocol for a helper used to set up vault unlock methods. | ||
/// | ||
protocol VaultUnlockSetupHelper: AnyObject { | ||
/// Enables or disables biometric vault unlock. | ||
/// | ||
/// - Parameters: | ||
/// - enabled: Whether to enable biometric vault unlock. | ||
/// - showAlert: A closure used to handle showing an alert if needed while toggling biometric | ||
/// vault unlock. | ||
/// - Returns: The biometric unlock status after vault unlock has been updated. | ||
/// | ||
func setBiometricUnlock(enabled: Bool, showAlert: @escaping (Alert) -> Void) async -> BiometricsUnlockStatus? | ||
|
||
/// Enables or disables pin vault unlock. | ||
/// | ||
/// - Parameters: | ||
/// - enabled: Whether to enable pin vault unlock. | ||
/// - showAlert: A closure used to handle showing an alert if needed while toggling pin vault unlock. | ||
/// - Returns: Whether pin unlock is enabled after vault unlock has been updated. | ||
/// | ||
func setPinUnlock(enabled: Bool, showAlert: @escaping (Alert) -> Void) async -> Bool | ||
} | ||
|
||
// MARK: - VaultUnlockSetupHelperError | ||
|
||
/// Errors thrown by a `VaultUnlockSetupHelper`. | ||
/// | ||
enum VaultUnlockSetupHelperError: Error { | ||
/// The user cancelled setting up an unlock method. | ||
case userCancelled | ||
} | ||
|
||
// MARK: - DefaultVaultUnlockSetupHelper | ||
|
||
/// A default implementation of `VaultUnlockSetupHelper` which is used to set up vault unlock methods. | ||
/// | ||
@MainActor | ||
class DefaultVaultUnlockSetupHelper { | ||
// MARK: Types | ||
|
||
typealias Services = HasAuthRepository | ||
& HasBiometricsRepository | ||
& HasErrorReporter | ||
& HasStateService | ||
|
||
// MARK: Properties | ||
|
||
/// The services used by this helper. | ||
private let services: Services | ||
|
||
// MARK: Initialization | ||
|
||
/// Creates a new `DefaultVaultUnlockSetupHelper`. | ||
/// | ||
/// - Parameters: | ||
/// - services: The services used by this helper. | ||
/// | ||
init(services: Services) { | ||
self.services = services | ||
} | ||
|
||
// MARK: Private | ||
|
||
/// Shows an alert for the user to enter their pin for vault unlock. | ||
/// | ||
/// - Parameter showAlert: A closure used to handling showing the alert. | ||
/// - Returns: The pin that the user entered. | ||
/// | ||
private func showEnterPinAlert(showAlert: @escaping (Alert) -> Void) async throws -> String { | ||
try await withCheckedThrowingContinuation { continuation in | ||
showAlert(.enterPINCode( | ||
onCancelled: { | ||
continuation.resume(throwing: VaultUnlockSetupHelperError.userCancelled) | ||
}, | ||
completion: { pin in | ||
continuation.resume(returning: pin) | ||
} | ||
)) | ||
} | ||
} | ||
|
||
/// Shows an alert asking the user whether they want to require their master password to unlock | ||
/// the vault when the app restarts. | ||
/// | ||
/// - Parameters: | ||
/// - biometricType: The biometric type the app supports. | ||
/// - showAlert: A closure used to handle showing the alert. | ||
/// - Returns: Whether the user wants to require their master password on app restart. | ||
/// | ||
private func showUnlockWithPinAlert( | ||
biometricType: BiometricAuthenticationType?, | ||
showAlert: @escaping (Alert) -> Void | ||
) async -> Bool { | ||
await withCheckedContinuation { continuation in | ||
showAlert(.unlockWithPINCodeAlert(biometricType: biometricType) { requirePassword in | ||
continuation.resume(returning: requirePassword) | ||
}) | ||
} | ||
} | ||
} | ||
|
||
// MARK: - DefaultVaultUnlockSetupHelper+VaultUnlockSetupHelper | ||
|
||
extension DefaultVaultUnlockSetupHelper: VaultUnlockSetupHelper { | ||
func setBiometricUnlock(enabled: Bool, showAlert: @escaping (Alert) -> Void) async -> BiometricsUnlockStatus? { | ||
do { | ||
try await services.authRepository.allowBioMetricUnlock(enabled) | ||
return try await services.biometricsRepository.getBiometricUnlockStatus() | ||
} catch { | ||
services.errorReporter.log(error: error) | ||
showAlert(.defaultAlert(title: Localizations.anErrorHasOccurred)) | ||
|
||
// In the case of an error, still attempt to return the unlock status. This status may | ||
// be used by the UI to determine whether biometrics are available on the device. | ||
return try? await services.biometricsRepository.getBiometricUnlockStatus() | ||
} | ||
} | ||
|
||
func setPinUnlock(enabled: Bool, showAlert: @escaping (Alert) -> Void) async -> Bool { | ||
do { | ||
guard enabled else { | ||
try await services.authRepository.clearPins() | ||
return false | ||
} | ||
|
||
let pin = try await showEnterPinAlert(showAlert: showAlert) | ||
guard !pin.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return false } | ||
|
||
let userHasMasterPassword = try await services.stateService.getUserHasMasterPassword() | ||
let biometricType = services.biometricsRepository.getBiometricAuthenticationType() | ||
let requirePasswordAfterRestart = if userHasMasterPassword { | ||
await showUnlockWithPinAlert(biometricType: biometricType, showAlert: showAlert) | ||
} else { | ||
false | ||
} | ||
|
||
try await services.authRepository.setPins( | ||
pin, | ||
requirePasswordAfterRestart: requirePasswordAfterRestart | ||
) | ||
|
||
return true | ||
} catch VaultUnlockSetupHelperError.userCancelled { | ||
return !enabled | ||
} catch { | ||
services.errorReporter.log(error: error) | ||
showAlert(.defaultAlert(title: Localizations.anErrorHasOccurred)) | ||
return false | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just curious, why not make this a service itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've used this concept of a helper elsewhere to extract logic from the processor or share logic between multiple processors. In this case, while not directly showing alerts, it does end up creating alerts that should be shown. So I feel like it belongs in the UI layer rather than the service layer.