Allow resettig app in the passcodelockview

This commit is contained in:
Yishi Lin 2019-09-28 21:38:32 +08:00
parent 6b3d75c1be
commit 35f599c45b
3 changed files with 65 additions and 26 deletions

View file

@ -156,6 +156,11 @@
"DiscardExplanation." = "Do you want to permanently discard all changes to the local copy of your password data? You cannot undo this action."; "DiscardExplanation." = "Do you want to permanently discard all changes to the local copy of your password data? You cannot undo this action.";
"Resetting..." = "Resetting ..."; "Resetting..." = "Resetting ...";
// Forget passcode
"ForgotYourPasscode?" = "Forgot your passcode?";
"ResetPass" = "Reset Pass";
"ResetPassExplanation." = "The only thing you can do is to reset the app. This will delete all local data and settings. Password store data on your remote server will not be affected.";
// Time related // Time related
"Unknown" = "Unknown"; "Unknown" = "Unknown";
"JustNow" = "Just now"; "JustNow" = "Just now";

View file

@ -18,13 +18,14 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
open var cancelCallback: (()->Void)? open var cancelCallback: (()->Void)?
weak var passcodeLabel: UILabel? weak var passcodeLabel: UILabel?
weak var passcodeWrongAttemptsLabel: UILabel?
weak var passcodeTextField: UITextField? weak var passcodeTextField: UITextField?
weak var biometryAuthButton: UIButton? weak var biometryAuthButton: UIButton?
weak var forgotPasscodeButton: UIButton?
open weak var cancelButton: UIButton? open weak var cancelButton: UIButton?
var passcodeFailedAttempts = 0
var isCancellable: Bool = false var isCancellable: Bool = false
private let passwordStore = PasswordStore.shared
open override func loadView() { open override func loadView() {
super.loadView() super.loadView()
@ -37,17 +38,9 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
self.view.addSubview(passcodeLabel) self.view.addSubview(passcodeLabel)
self.passcodeLabel = passcodeLabel self.passcodeLabel = passcodeLabel
let passcodeWrongAttemptsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeWrongAttemptsLabel.text = ""
passcodeWrongAttemptsLabel.textColor = Globals.red
passcodeWrongAttemptsLabel.textAlignment = .center
passcodeWrongAttemptsLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeWrongAttemptsLabel)
self.passcodeWrongAttemptsLabel = passcodeWrongAttemptsLabel
let passcodeTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 300, height: 40)) let passcodeTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeTextField.borderStyle = UITextField.BorderStyle.roundedRect passcodeTextField.borderStyle = UITextField.BorderStyle.roundedRect
passcodeTextField.placeholder = "Passcode".localize() passcodeTextField.placeholder = "EnterPasscode".localize()
passcodeTextField.isSecureTextEntry = true passcodeTextField.isSecureTextEntry = true
passcodeTextField.clearButtonMode = UITextField.ViewMode.whileEditing passcodeTextField.clearButtonMode = UITextField.ViewMode.whileEditing
passcodeTextField.delegate = self passcodeTextField.delegate = self
@ -87,6 +80,16 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
biometryAuthButton.isHidden = false biometryAuthButton.isHidden = false
} }
} }
let forgotPasscodeButton = UIButton(type: .custom)
forgotPasscodeButton.setTitle("ForgotYourPasscode?".localize(), for: .normal)
forgotPasscodeButton.setTitleColor(Globals.blue, for: .normal)
forgotPasscodeButton.addTarget(self, action: #selector(forgotPasscodeButtonPressedAction(_:)), for: .touchUpInside)
// hide the forgotPasscodeButton if the native app is running
forgotPasscodeButton.isHidden = self.isCancellable
forgotPasscodeButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(forgotPasscodeButton)
self.forgotPasscodeButton = forgotPasscodeButton
let cancelButton = UIButton(type: .custom) let cancelButton = UIButton(type: .custom)
cancelButton.setTitle("Cancel".localize(), for: .normal) cancelButton.setTitle("Cancel".localize(), for: .normal)
@ -109,20 +112,20 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
passcodeLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), passcodeLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
passcodeLabel.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor), passcodeLabel.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor),
// below passcode // below passcode
passcodeWrongAttemptsLabel.widthAnchor.constraint(equalToConstant: 300), biometryAuthButton.widthAnchor.constraint(equalToConstant: 300),
passcodeWrongAttemptsLabel.heightAnchor.constraint(equalToConstant: 40),
passcodeWrongAttemptsLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
passcodeWrongAttemptsLabel.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor),
// bottom of the screen
biometryAuthButton.widthAnchor.constraint(equalToConstant: 150),
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40), biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
biometryAuthButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40), biometryAuthButton.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor),
// cancel (top-left of the screen) // cancel (top-left of the screen)
cancelButton.widthAnchor.constraint(equalToConstant: 150), cancelButton.widthAnchor.constraint(equalToConstant: 150),
cancelButton.heightAnchor.constraint(equalToConstant: 40), cancelButton.heightAnchor.constraint(equalToConstant: 40),
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor), cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20) cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20),
// bottom of the screen
forgotPasscodeButton.widthAnchor.constraint(equalToConstant: 300),
forgotPasscodeButton.heightAnchor.constraint(equalToConstant: 40),
forgotPasscodeButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
forgotPasscodeButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40)
]) ])
} }
@ -162,8 +165,6 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
// MARK: - PasscodeLockDelegate // MARK: - PasscodeLockDelegate
open func passcodeLockDidSucceed() { open func passcodeLockDidSucceed() {
passcodeFailedAttempts = 0
passcodeWrongAttemptsLabel?.text = ""
dismissPasscodeLock(completionHandler: successCallback) dismissPasscodeLock(completionHandler: successCallback)
} }
@ -190,11 +191,42 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
} }
} }
@objc func forgotPasscodeButtonPressedAction(_ uiButton: UIButton) {
let alert = UIAlertController(title: "ResetPass".localize(), message: "ResetPassExplanation.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in
let myContext = LAContext()
var error: NSError?
// If the device passcode is not set, reset the app.
guard myContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
self.passwordStore.erase()
self.passcodeLockDidSucceed()
return
}
// If the device passcode is set, authentication is required.
myContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "ErasePasswordStoreData".localize()) { (success, error) in
if success {
DispatchQueue.main.async {
// User authenticated successfully, take appropriate action
self.passwordStore.erase()
self.passcodeLockDidSucceed()
}
} else {
DispatchQueue.main.async {
Utils.alert(title: "Error".localize(), message: error?.localizedDescription ?? "", controller: self, completion: nil)
}
}
}
}))
alert.addAction(UIAlertAction(title: "Dismiss".localize(), style: UIAlertAction.Style.cancel, handler:nil))
self.present(alert, animated: true, completion: nil)
}
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool { public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == passcodeTextField { if textField == passcodeTextField {
if !PasscodeLock.shared.check(passcode: textField.text ?? "") { if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
passcodeFailedAttempts = passcodeFailedAttempts + 1 self.passcodeTextField?.placeholder =
passcodeWrongAttemptsLabel?.text = "WrongAttempts(%d)".localize(passcodeFailedAttempts) "TryAgain".localize()
self.passcodeTextField?.text = ""
} }
} }
textField.resignFirstResponder() textField.resignFirstResponder()

View file

@ -12,7 +12,11 @@ public class PasscodeLock {
private static let identifier = Globals.bundleIdentifier + "passcode" private static let identifier = Globals.bundleIdentifier + "passcode"
/// Cached passcode to avoid frequent access to Keychain /// Cached passcode to avoid frequent access to Keychain
private var passcode: String? = AppKeychain.shared.get(for: PasscodeLock.identifier) private var passcode: String? {
get {
AppKeychain.shared.get(for: PasscodeLock.identifier)
}
}
/// Constructor used to migrate passcode from SharedDefaults to Keychain /// Constructor used to migrate passcode from SharedDefaults to Keychain
private init() { private init() {
@ -28,7 +32,6 @@ public class PasscodeLock {
public func save(passcode: String) { public func save(passcode: String) {
AppKeychain.shared.add(string: passcode, for: PasscodeLock.identifier) AppKeychain.shared.add(string: passcode, for: PasscodeLock.identifier)
self.passcode = passcode
} }
public func check(passcode: String) -> Bool { public func check(passcode: String) -> Bool {
@ -37,6 +40,5 @@ public class PasscodeLock {
public func delete() { public func delete() {
AppKeychain.shared.removeContent(for: PasscodeLock.identifier) AppKeychain.shared.removeContent(for: PasscodeLock.identifier)
passcode = nil
} }
} }