diff --git a/Cartfile b/Cartfile index 0a50465..97ca48d 100644 --- a/Cartfile +++ b/Cartfile @@ -1,8 +1,6 @@ github "SVProgressHUD/SVProgressHUD" github "radex/SwiftyUserDefaults" github "libgit2/objective-git" -# github "zahlz/SwiftPasscodeLock" "master" -github "yishilin14/SwiftPasscodeLock" "app-extension-support" github "leonbreedt/FavIcon" github "kishikawakatsumi/KeychainAccess" github "mattrubin/OneTimePassword" diff --git a/pass/Controllers/OpenSourceComponentsTableViewController.swift b/pass/Controllers/OpenSourceComponentsTableViewController.swift index b220434..bdd809a 100644 --- a/pass/Controllers/OpenSourceComponentsTableViewController.swift +++ b/pass/Controllers/OpenSourceComponentsTableViewController.swift @@ -17,9 +17,6 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController { ["KeychainAccess", "https://github.com/kishikawakatsumi/KeychainAccess", "https://github.com/kishikawakatsumi/KeychainAccess/blob/master/LICENSE"], - ["PasscodeLock", - "https://github.com/zahlz/SwiftPasscodeLock", - "https://github.com/zahlz/SwiftPasscodeLock/blob/master/LICENSE.txt"], ["ObjectiveGit", "https://github.com/libgit2/objective-git", "https://github.com/libgit2/objective-git/blob/master/LICENSE"], diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift index 536a25c..46491a4 100644 --- a/pass/Controllers/SettingsTableViewController.swift +++ b/pass/Controllers/SettingsTableViewController.swift @@ -159,9 +159,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele setPGPKeyTableViewCellDetailText() setPasswordRepositoryTableViewCellDetailText() setPasscodeLockCell() - - let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window) } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { @@ -262,8 +259,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele passcodeRemoveViewController.successCallback = { self?.passcodeLock.delete() self?.setPasscodeLockCell() - let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window) } self?.present(passcodeRemoveViewController, animated: true, completion: nil) } diff --git a/passKit/Controllers/PasscodeLockPresenter.swift b/passKit/Controllers/PasscodeLockPresenter.swift index ce1b88b..d9df482 100644 --- a/passKit/Controllers/PasscodeLockPresenter.swift +++ b/passKit/Controllers/PasscodeLockPresenter.swift @@ -2,8 +2,10 @@ // PasscodeLockPresenter.swift // PasscodeLock // -// Created by Yanko Dimitrov on 8/29/15. -// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// Created by Yishi Lin on 10/04/2018. +// Copyright © 2018 Yishi Lin. All rights reserved. +// +// Inspired by SwiftPasscodeLock created by Yanko Dimitrov. // import UIKit @@ -11,49 +13,42 @@ import UIKit open class PasscodeLockPresenter { fileprivate var mainWindow: UIWindow? - - fileprivate lazy var passcodeLockWindow = UIWindow(frame: UIScreen.main.bounds) - - open var isPasscodePresented = false - open let passcodeLockVC: PasscodeLockViewController - - public init(mainWindow window: UIWindow?, viewController: PasscodeLockViewController) { - mainWindow = window - passcodeLockVC = viewController + fileprivate var passcodeLockWindow: UIWindow? + + public init(mainWindow window: UIWindow?) { + self.mainWindow = window } - public convenience init(mainWindow window: UIWindow?) { - let passcodeLockVC = PasscodeLockViewController() - self.init(mainWindow: window, viewController: passcodeLockVC) - } - open func present(windowLevel: CGFloat?) { guard PasscodeLock.shared.hasPasscode else { return } - guard !isPasscodePresented else { return } - isPasscodePresented = true - + // dismiss the original window + dismiss() + + // new window mainWindow?.endEditing(true) + passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame) moveWindowsToFront(windowLevel: windowLevel) - passcodeLockWindow.isHidden = false - + passcodeLockWindow?.isHidden = false + + // new vc + let passcodeLockVC = PasscodeLockViewController() let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback passcodeLockVC.dismissCompletionCallback = { [weak self] in userDismissCompletionCallback?() self?.dismiss() } - passcodeLockWindow.rootViewController = passcodeLockVC + passcodeLockWindow?.rootViewController = passcodeLockVC } open func dismiss() { - isPasscodePresented = false - passcodeLockWindow.isHidden = true - passcodeLockWindow.rootViewController = nil + passcodeLockWindow?.isHidden = true + passcodeLockWindow?.rootViewController = nil } fileprivate func moveWindowsToFront(windowLevel: CGFloat?) { let windowLevel = windowLevel ?? UIWindowLevelNormal let maxWinLevel = max(windowLevel, UIWindowLevelNormal) - passcodeLockWindow.windowLevel = maxWinLevel + 1 + passcodeLockWindow?.windowLevel = maxWinLevel + 1 } } diff --git a/passKit/Controllers/PasscodeLockViewController.swift b/passKit/Controllers/PasscodeLockViewController.swift index 9663d64..b433c99 100644 --- a/passKit/Controllers/PasscodeLockViewController.swift +++ b/passKit/Controllers/PasscodeLockViewController.swift @@ -1,29 +1,69 @@ // -// PasscodeLockViewController.swift +// PasscodeLockPresenter.swift // PasscodeLock // -// Created by Yanko Dimitrov on 8/28/15. -// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// Created by Yishi Lin on 10/04/2018. +// Copyright © 2018 Yishi Lin. All rights reserved. +// +// Inspired by SwiftPasscodeLock created by Yanko Dimitrov. // import UIKit import LocalAuthentication -open class PasscodeLockViewController: UIViewController { +open class PasscodeLockViewController: UIViewController, UITextFieldDelegate { open var dismissCompletionCallback: (()->Void)? open var successCallback: (()->Void)? open var cancelCallback: (()->Void)? - lazy var enterPasscodeAlert: UIAlertController = { - let enterPasscodeAlert = UIAlertController(title: "Authenticate Pass", message: "Unlock with passcode for Pass", preferredStyle: .alert) - - enterPasscodeAlert.addTextField(configurationHandler: {(_ textField: UITextField) -> Void in - textField.placeholder = "passcode" - textField.isSecureTextEntry = true - textField.addTarget(self, action: #selector(self.passcodeTextFieldDidChange(_:)), for: UIControlEvents.editingChanged) - textField.clearButtonMode = UITextFieldViewMode.whileEditing - textField.becomeFirstResponder() - }) + + weak var passcodeLabel: UILabel? + weak var passcodeWrongAttemptsLabel: UILabel? + weak var passcodeTextField: UITextField? + weak var biometryAuthButton: UIButton? + + var passcodeFailedAttempts = 0 + + open override func loadView() { + super.loadView() + + let passcodeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40)) + passcodeLabel.text = "Enter passcode for Pass" + passcodeLabel.font = UIFont.boldSystemFont(ofSize: 18) + passcodeLabel.textColor = UIColor.black + passcodeLabel.textAlignment = .center + passcodeLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(passcodeLabel) + self.passcodeLabel = passcodeLabel + + let passcodeWrongAttemptsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40)) + passcodeWrongAttemptsLabel.text = "" + passcodeWrongAttemptsLabel.textColor = UIColor.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)) + passcodeTextField.borderStyle = UITextBorderStyle.roundedRect + passcodeTextField.placeholder = "passcode" + passcodeTextField.isSecureTextEntry = true + passcodeTextField.clearButtonMode = UITextFieldViewMode.whileEditing + passcodeTextField.delegate = self + passcodeTextField.addTarget(self, action: #selector(self.passcodeTextFieldDidChange(_:)), for: UIControlEvents.editingChanged) + self.view.backgroundColor = UIColor.white + passcodeTextField.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(passcodeTextField) + self.passcodeTextField = passcodeTextField + + let biometryAuthButton = UIButton(type: .custom) + biometryAuthButton.setTitle("", for: .normal) + biometryAuthButton.setTitleColor(Globals.blue, for: .normal) + biometryAuthButton.addTarget(self, action: #selector(bioButtonPressedAction(_:)), for: .touchUpInside) + biometryAuthButton.isHidden = true + biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(biometryAuthButton) + self.biometryAuthButton = biometryAuthButton let myContext = LAContext() var authError: NSError? @@ -35,31 +75,50 @@ open class PasscodeLockViewController: UIViewController { biometryType = "Face ID" } } - let bioAction = UIAlertAction(title: "Use " + biometryType, style: .default) { (action:UIAlertAction) -> Void in - self.authenticate() - } - enterPasscodeAlert.addAction(bioAction) + biometryAuthButton.setTitle(biometryType, for: .normal) + biometryAuthButton.isHidden = false } } - return enterPasscodeAlert - }() + NSLayoutConstraint.activate([ + passcodeTextField.widthAnchor.constraint(equalToConstant: 300), + passcodeTextField.heightAnchor.constraint(equalToConstant: 40), + passcodeTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + passcodeTextField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20), + // above passocde + passcodeLabel.widthAnchor.constraint(equalToConstant: 300), + passcodeLabel.heightAnchor.constraint(equalToConstant: 40), + passcodeLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + passcodeLabel.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor), + // below passcode + passcodeWrongAttemptsLabel.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.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + biometryAuthButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -40), + ]) + + } open override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = UIColor.white } open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - authenticate() + if let biometryAuthButton = biometryAuthButton { + self.bioButtonPressedAction(biometryAuthButton) + } } internal func dismissPasscodeLock(completionHandler: (() -> Void)? = nil) { // clean up the textfield DispatchQueue.main.async { - self.enterPasscodeAlert.textFields?[0].text = "" - self.enterPasscodeAlert.dismiss(animated: false, completion: nil) + self.passcodeTextField?.text = "" } // pop @@ -80,6 +139,8 @@ open class PasscodeLockViewController: UIViewController { // MARK: - PasscodeLockDelegate open func passcodeLockDidSucceed() { + passcodeFailedAttempts = 0 + passcodeWrongAttemptsLabel?.text = "" dismissPasscodeLock(completionHandler: successCallback) } @@ -87,7 +148,7 @@ open class PasscodeLockViewController: UIViewController { dismissPasscodeLock(completionHandler: cancelCallback) } - public func authenticate() { + @objc public func bioButtonPressedAction(_ uiButton: UIButton) { let myContext = LAContext() let myLocalizedReasonString = "Authentication is needed to access Pass." var authError: NSError? @@ -100,29 +161,30 @@ open class PasscodeLockViewController: UIViewController { // user authenticated successfully, take appropriate action self.passcodeLockDidSucceed() } - } else { - // User did not authenticate successfully - self.showPasswordAlert() } } - } else { - // could not evaluate policy; look at authError and present an appropriate message to user - self.showPasswordAlert() } - } else { - // fallback on earlier versions - self.showPasswordAlert() } } - @objc func passcodeTextFieldDidChange(_ sender: UITextField) { - // check whether the passcode is correct - if PasscodeLock.shared.check(passcode: sender.text ?? "") { + public override func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField == passcodeTextField { + if !PasscodeLock.shared.check(passcode: textField.text ?? "") { + passcodeFailedAttempts = passcodeFailedAttempts + 1 + if passcodeFailedAttempts == 1 { + passcodeWrongAttemptsLabel?.text = "1 wrong attempt" + } else { + passcodeWrongAttemptsLabel?.text = "\(passcodeFailedAttempts) wrong attempts" + } + } + } + textField.resignFirstResponder() + return true + } + + @objc public func passcodeTextFieldDidChange(_ textField: UITextField) { + if PasscodeLock.shared.check(passcode: textField.text ?? "") { self.passcodeLockDidSucceed() } } - - func showPasswordAlert() { - self.present(enterPasscodeAlert, animated: true, completion: nil) - } } diff --git a/passKit/Helpers/UITextFieldExtension.swift b/passKit/Helpers/UITextFieldExtension.swift index 2500baf..0ea3ab2 100644 --- a/passKit/Helpers/UITextFieldExtension.swift +++ b/passKit/Helpers/UITextFieldExtension.swift @@ -23,7 +23,7 @@ extension UITextField { } extension UIViewController { - func textFieldShouldReturn(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { if textField.nextField != nil { textField.nextField?.becomeFirstResponder() } else {