Instantly share code, notes, and snippets.
Created
August 24, 2020 03:55
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save lalkrishna/a37f8bf061b23c5a7e54b983e9417046 to your computer and use it in GitHub Desktop.
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
// | |
// LocalAuthorization.swift | |
// LocalAuthorization | |
// | |
// Created by lalkrishna. | |
// | |
// Copyright © 2020 Lal Krishna. All rights reserved. | |
// | |
import Foundation | |
import LocalAuthentication | |
// | |
// Add `NSFaceIDUsageDescription` to info.plist. | |
// | |
class LKLocalAuthentication: NSObject { | |
// private override init() { fatalError() } | |
@objc static let shared: LKLocalAuthentication = LKLocalAuthentication() | |
/// LAPolicy default value is `.deviceOwnerAuthentication` | |
public var policy = LAPolicy.deviceOwnerAuthentication | |
/// Timeout interval in minutes. | |
@objc public let timeoutInterval: TimeInterval = 60 | |
/// Reason Message. | |
@objc public var reason = NSLocalizedString("To enabling user authentication using biometrics", comment: "Reason for using device authentication feature") | |
typealias Completion = ((Bool, String?) -> Void) | |
public var lastUnlockedTimeInterval: TimeInterval? | |
@objc var completionHandler: Completion? | |
@objc public var isUnlocked: Bool { | |
guard let lastUnlockedTimeInterval = lastUnlockedTimeInterval else { return false } | |
let currentInterval = Date().timeIntervalSince1970 | |
let timeElapsed = currentInterval - lastUnlockedTimeInterval | |
return timeElapsed < timeoutInterval * 60 | |
} | |
/// Returns bool that indicates whether the device passcode is Set. | |
@objc public var isAuthenticationAvailable: Bool = { | |
var error: NSError? | |
if LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) { | |
return true | |
} else { | |
guard let laError = error as? LAError else { return false } | |
if laError.code == LAError.passcodeNotSet { | |
return false | |
} else { | |
return true | |
} | |
} | |
}() | |
@objc public var availableMethodText: String? = { | |
let context = LAContext() | |
var error: NSError? | |
let evaluated = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) | |
if let laError = error { | |
LKLocalAuthentication.log("laError - \(laError)") | |
} | |
if #available(iOS 11.0, *) { | |
let type = context.biometryType | |
switch type { | |
case .touchID: | |
return NSLocalizedString("Touch ID", comment: "") | |
case .faceID: | |
return NSLocalizedString("Face ID", comment: "") | |
default: | |
return NSLocalizedString("Passcode", comment: "") | |
} | |
} else { | |
if evaluated || (error?.code != LAError.touchIDNotAvailable.rawValue) { | |
return NSLocalizedString("Touch ID", comment: "") | |
} | |
} | |
return nil | |
}() | |
// MARK: - | |
@objc func requestAuth() { | |
requestAuth(policy: policy) | |
} | |
@objc func requestAuth(completionHandler: Completion?) { | |
self.completionHandler = completionHandler | |
requestAuth(policy: policy) | |
} | |
@objc func requestAuth(reason: String, completionHandler: Completion?) { | |
self.reason = reason | |
requestAuth(completionHandler: completionHandler) | |
} | |
private func requestAuth(policy: LAPolicy) { | |
let authContext = LAContext() | |
authContext.localizedFallbackTitle = NSLocalizedString("Use Passcode", comment: "") | |
authContext.localizedCancelTitle = NSLocalizedString("Cancel", comment: "") | |
var authError: NSError? | |
if authContext.canEvaluatePolicy(policy, error: &authError) { | |
evaluatePolicy(policy, context: authContext) | |
} else { | |
guard let error = authError else { return } | |
LKLocalAuthentication.log("LKAuthorization Error: \(error.code)") | |
checkError(error) | |
} | |
} | |
@discardableResult | |
private func checkError(_ error: NSError) -> String? { | |
guard let error = error as? LAError else { return "Authorization Failed" } | |
switch error.code { | |
case .authenticationFailed: | |
// print("authenticationFailed") | |
requestAuth(policy: .deviceOwnerAuthentication) | |
// NSLocalizedString(@"User failed after a few attempts", "") | |
case .userFallback: | |
// print("userFallback") | |
requestAuth(policy: .deviceOwnerAuthentication) | |
case .userCancel: | |
LKLocalAuthentication.log("userCancel") | |
return nil | |
case .systemCancel: | |
LKLocalAuthentication.log("systemCancel") | |
return NSLocalizedString("System canceled auth request due to app coming to foreground or background.", comment: ""); | |
case .passcodeNotSet: | |
return NSLocalizedString("Please set device passcode to continue", comment: "") | |
case .appCancel: | |
LKLocalAuthentication.log("appCancel") | |
return nil | |
case .invalidContext: | |
LKLocalAuthentication.log("invalidContext") | |
return nil | |
case .notInteractive: | |
LKLocalAuthentication.log("notInteractive") | |
return nil | |
default: | |
checkBioMetricError(error) | |
} | |
return nil | |
} | |
private func checkBioMetricError(_ error: LAError) { | |
if #available(iOS 11.0, *) { | |
switch error.code { | |
case .biometryNotAvailable, | |
.biometryNotEnrolled, | |
.biometryLockout: | |
requestAuth(policy: .deviceOwnerAuthentication) | |
default: break | |
} | |
} else { | |
switch error.code { | |
case .touchIDNotAvailable, | |
.touchIDNotEnrolled, | |
.touchIDLockout: | |
requestAuth(policy: .deviceOwnerAuthentication) | |
default: break | |
} | |
} | |
} | |
private func evaluatePolicy(_ policy: LAPolicy, context: LAContext) { | |
context.evaluatePolicy(policy, localizedReason: reason) { (success, error) in | |
if success { | |
self.lastUnlockedTimeInterval = Date().timeIntervalSince1970 | |
if let completion = self.completionHandler { | |
DispatchQueue.main.async { | |
completion(true, nil) | |
} | |
} | |
} else { | |
guard let error = error else { return } | |
let errorMessage = self.checkError(error as NSError) | |
if let completion = self.completionHandler { | |
DispatchQueue.main.async { | |
completion(false, errorMessage) | |
} | |
} | |
} | |
} | |
} | |
private static func log(_ message: String) { | |
#if DEBUG | |
print(message) | |
#endif | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment