Passcode lock
This commit is contained in:
parent
fe80ed3dc8
commit
433562584e
6 changed files with 125 additions and 78 deletions
2
Cartfile
2
Cartfile
|
|
@ -1,8 +1,6 @@
|
||||||
github "SVProgressHUD/SVProgressHUD"
|
github "SVProgressHUD/SVProgressHUD"
|
||||||
github "radex/SwiftyUserDefaults"
|
github "radex/SwiftyUserDefaults"
|
||||||
github "libgit2/objective-git"
|
github "libgit2/objective-git"
|
||||||
# github "zahlz/SwiftPasscodeLock" "master"
|
|
||||||
github "yishilin14/SwiftPasscodeLock" "app-extension-support"
|
|
||||||
github "leonbreedt/FavIcon"
|
github "leonbreedt/FavIcon"
|
||||||
github "kishikawakatsumi/KeychainAccess"
|
github "kishikawakatsumi/KeychainAccess"
|
||||||
github "mattrubin/OneTimePassword"
|
github "mattrubin/OneTimePassword"
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
|
||||||
["KeychainAccess",
|
["KeychainAccess",
|
||||||
"https://github.com/kishikawakatsumi/KeychainAccess",
|
"https://github.com/kishikawakatsumi/KeychainAccess",
|
||||||
"https://github.com/kishikawakatsumi/KeychainAccess/blob/master/LICENSE"],
|
"https://github.com/kishikawakatsumi/KeychainAccess/blob/master/LICENSE"],
|
||||||
["PasscodeLock",
|
|
||||||
"https://github.com/zahlz/SwiftPasscodeLock",
|
|
||||||
"https://github.com/zahlz/SwiftPasscodeLock/blob/master/LICENSE.txt"],
|
|
||||||
["ObjectiveGit",
|
["ObjectiveGit",
|
||||||
"https://github.com/libgit2/objective-git",
|
"https://github.com/libgit2/objective-git",
|
||||||
"https://github.com/libgit2/objective-git/blob/master/LICENSE"],
|
"https://github.com/libgit2/objective-git/blob/master/LICENSE"],
|
||||||
|
|
|
||||||
|
|
@ -159,9 +159,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
setPGPKeyTableViewCellDetailText()
|
setPGPKeyTableViewCellDetailText()
|
||||||
setPasswordRepositoryTableViewCellDetailText()
|
setPasswordRepositoryTableViewCellDetailText()
|
||||||
setPasscodeLockCell()
|
setPasscodeLockCell()
|
||||||
|
|
||||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
|
||||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
|
@ -262,8 +259,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
passcodeRemoveViewController.successCallback = {
|
passcodeRemoveViewController.successCallback = {
|
||||||
self?.passcodeLock.delete()
|
self?.passcodeLock.delete()
|
||||||
self?.setPasscodeLockCell()
|
self?.setPasscodeLockCell()
|
||||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
|
||||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window)
|
|
||||||
}
|
}
|
||||||
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
|
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
// PasscodeLockPresenter.swift
|
// PasscodeLockPresenter.swift
|
||||||
// PasscodeLock
|
// PasscodeLock
|
||||||
//
|
//
|
||||||
// Created by Yanko Dimitrov on 8/29/15.
|
// Created by Yishi Lin on 10/04/2018.
|
||||||
// Copyright © 2015 Yanko Dimitrov. All rights reserved.
|
// Copyright © 2018 Yishi Lin. All rights reserved.
|
||||||
|
//
|
||||||
|
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
@ -11,49 +13,42 @@ import UIKit
|
||||||
open class PasscodeLockPresenter {
|
open class PasscodeLockPresenter {
|
||||||
|
|
||||||
fileprivate var mainWindow: UIWindow?
|
fileprivate var mainWindow: UIWindow?
|
||||||
|
fileprivate var passcodeLockWindow: UIWindow?
|
||||||
fileprivate lazy var passcodeLockWindow = UIWindow(frame: UIScreen.main.bounds)
|
|
||||||
|
public init(mainWindow window: UIWindow?) {
|
||||||
open var isPasscodePresented = false
|
self.mainWindow = window
|
||||||
open let passcodeLockVC: PasscodeLockViewController
|
|
||||||
|
|
||||||
public init(mainWindow window: UIWindow?, viewController: PasscodeLockViewController) {
|
|
||||||
mainWindow = window
|
|
||||||
passcodeLockVC = viewController
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public convenience init(mainWindow window: UIWindow?) {
|
|
||||||
let passcodeLockVC = PasscodeLockViewController()
|
|
||||||
self.init(mainWindow: window, viewController: passcodeLockVC)
|
|
||||||
}
|
|
||||||
|
|
||||||
open func present(windowLevel: CGFloat?) {
|
open func present(windowLevel: CGFloat?) {
|
||||||
guard PasscodeLock.shared.hasPasscode else { return }
|
guard PasscodeLock.shared.hasPasscode else { return }
|
||||||
guard !isPasscodePresented else { return }
|
|
||||||
|
|
||||||
isPasscodePresented = true
|
// dismiss the original window
|
||||||
|
dismiss()
|
||||||
|
|
||||||
|
// new window
|
||||||
mainWindow?.endEditing(true)
|
mainWindow?.endEditing(true)
|
||||||
|
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
|
||||||
moveWindowsToFront(windowLevel: windowLevel)
|
moveWindowsToFront(windowLevel: windowLevel)
|
||||||
passcodeLockWindow.isHidden = false
|
passcodeLockWindow?.isHidden = false
|
||||||
|
|
||||||
|
// new vc
|
||||||
|
let passcodeLockVC = PasscodeLockViewController()
|
||||||
let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback
|
let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback
|
||||||
passcodeLockVC.dismissCompletionCallback = { [weak self] in
|
passcodeLockVC.dismissCompletionCallback = { [weak self] in
|
||||||
userDismissCompletionCallback?()
|
userDismissCompletionCallback?()
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
}
|
}
|
||||||
passcodeLockWindow.rootViewController = passcodeLockVC
|
passcodeLockWindow?.rootViewController = passcodeLockVC
|
||||||
}
|
}
|
||||||
|
|
||||||
open func dismiss() {
|
open func dismiss() {
|
||||||
isPasscodePresented = false
|
passcodeLockWindow?.isHidden = true
|
||||||
passcodeLockWindow.isHidden = true
|
passcodeLockWindow?.rootViewController = nil
|
||||||
passcodeLockWindow.rootViewController = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func moveWindowsToFront(windowLevel: CGFloat?) {
|
fileprivate func moveWindowsToFront(windowLevel: CGFloat?) {
|
||||||
let windowLevel = windowLevel ?? UIWindowLevelNormal
|
let windowLevel = windowLevel ?? UIWindowLevelNormal
|
||||||
let maxWinLevel = max(windowLevel, UIWindowLevelNormal)
|
let maxWinLevel = max(windowLevel, UIWindowLevelNormal)
|
||||||
passcodeLockWindow.windowLevel = maxWinLevel + 1
|
passcodeLockWindow?.windowLevel = maxWinLevel + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,69 @@
|
||||||
//
|
//
|
||||||
// PasscodeLockViewController.swift
|
// PasscodeLockPresenter.swift
|
||||||
// PasscodeLock
|
// PasscodeLock
|
||||||
//
|
//
|
||||||
// Created by Yanko Dimitrov on 8/28/15.
|
// Created by Yishi Lin on 10/04/2018.
|
||||||
// Copyright © 2015 Yanko Dimitrov. All rights reserved.
|
// Copyright © 2018 Yishi Lin. All rights reserved.
|
||||||
|
//
|
||||||
|
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
|
||||||
open class PasscodeLockViewController: UIViewController {
|
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
|
|
||||||
open var dismissCompletionCallback: (()->Void)?
|
open var dismissCompletionCallback: (()->Void)?
|
||||||
open var successCallback: (()->Void)?
|
open var successCallback: (()->Void)?
|
||||||
open var cancelCallback: (()->Void)?
|
open var cancelCallback: (()->Void)?
|
||||||
lazy var enterPasscodeAlert: UIAlertController = {
|
|
||||||
let enterPasscodeAlert = UIAlertController(title: "Authenticate Pass", message: "Unlock with passcode for Pass", preferredStyle: .alert)
|
weak var passcodeLabel: UILabel?
|
||||||
|
weak var passcodeWrongAttemptsLabel: UILabel?
|
||||||
enterPasscodeAlert.addTextField(configurationHandler: {(_ textField: UITextField) -> Void in
|
weak var passcodeTextField: UITextField?
|
||||||
textField.placeholder = "passcode"
|
weak var biometryAuthButton: UIButton?
|
||||||
textField.isSecureTextEntry = true
|
|
||||||
textField.addTarget(self, action: #selector(self.passcodeTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
|
var passcodeFailedAttempts = 0
|
||||||
textField.clearButtonMode = UITextFieldViewMode.whileEditing
|
|
||||||
textField.becomeFirstResponder()
|
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()
|
let myContext = LAContext()
|
||||||
var authError: NSError?
|
var authError: NSError?
|
||||||
|
|
@ -35,31 +75,50 @@ open class PasscodeLockViewController: UIViewController {
|
||||||
biometryType = "Face ID"
|
biometryType = "Face ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bioAction = UIAlertAction(title: "Use " + biometryType, style: .default) { (action:UIAlertAction) -> Void in
|
biometryAuthButton.setTitle(biometryType, for: .normal)
|
||||||
self.authenticate()
|
biometryAuthButton.isHidden = false
|
||||||
}
|
|
||||||
enterPasscodeAlert.addAction(bioAction)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
open override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.view.backgroundColor = UIColor.white
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func viewDidAppear(_ animated: Bool) {
|
open override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
authenticate()
|
if let biometryAuthButton = biometryAuthButton {
|
||||||
|
self.bioButtonPressedAction(biometryAuthButton)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func dismissPasscodeLock(completionHandler: (() -> Void)? = nil) {
|
internal func dismissPasscodeLock(completionHandler: (() -> Void)? = nil) {
|
||||||
// clean up the textfield
|
// clean up the textfield
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.enterPasscodeAlert.textFields?[0].text = ""
|
self.passcodeTextField?.text = ""
|
||||||
self.enterPasscodeAlert.dismiss(animated: false, completion: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pop
|
// pop
|
||||||
|
|
@ -80,6 +139,8 @@ open class PasscodeLockViewController: UIViewController {
|
||||||
// MARK: - PasscodeLockDelegate
|
// MARK: - PasscodeLockDelegate
|
||||||
|
|
||||||
open func passcodeLockDidSucceed() {
|
open func passcodeLockDidSucceed() {
|
||||||
|
passcodeFailedAttempts = 0
|
||||||
|
passcodeWrongAttemptsLabel?.text = ""
|
||||||
dismissPasscodeLock(completionHandler: successCallback)
|
dismissPasscodeLock(completionHandler: successCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +148,7 @@ open class PasscodeLockViewController: UIViewController {
|
||||||
dismissPasscodeLock(completionHandler: cancelCallback)
|
dismissPasscodeLock(completionHandler: cancelCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func authenticate() {
|
@objc public func bioButtonPressedAction(_ uiButton: UIButton) {
|
||||||
let myContext = LAContext()
|
let myContext = LAContext()
|
||||||
let myLocalizedReasonString = "Authentication is needed to access Pass."
|
let myLocalizedReasonString = "Authentication is needed to access Pass."
|
||||||
var authError: NSError?
|
var authError: NSError?
|
||||||
|
|
@ -100,29 +161,30 @@ open class PasscodeLockViewController: UIViewController {
|
||||||
// user authenticated successfully, take appropriate action
|
// user authenticated successfully, take appropriate action
|
||||||
self.passcodeLockDidSucceed()
|
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) {
|
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
// check whether the passcode is correct
|
if textField == passcodeTextField {
|
||||||
if PasscodeLock.shared.check(passcode: sender.text ?? "") {
|
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()
|
self.passcodeLockDidSucceed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showPasswordAlert() {
|
|
||||||
self.present(enterPasscodeAlert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ extension UITextField {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIViewController {
|
extension UIViewController {
|
||||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
if textField.nextField != nil {
|
if textField.nextField != nil {
|
||||||
textField.nextField?.becomeFirstResponder()
|
textField.nextField?.becomeFirstResponder()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue