Skip to content
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

Add Objective-C support #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,51 @@ public enum AuthenticationError: Error {
}
}
}

@objc public enum AuthenticationErrorOBJC: Int {
case failed, canceledByUser, fallback, canceledBySystem, passcodeNotSet, biometryNotAvailable, biometryNotEnrolled, biometryLockedout, other

public static func initWithError(_ error: LAError) -> AuthenticationErrorOBJC {
switch Int32(error.errorCode) {

case kLAErrorAuthenticationFailed:
return failed
case kLAErrorUserCancel:
return canceledByUser
case kLAErrorUserFallback:
return fallback
case kLAErrorSystemCancel:
return canceledBySystem
case kLAErrorPasscodeNotSet:
return passcodeNotSet
case kLAErrorBiometryNotAvailable:
return biometryNotAvailable
case kLAErrorBiometryNotEnrolled:
return biometryNotEnrolled
case kLAErrorBiometryLockout:
return biometryLockedout
default:
return other
}
}

// get error message based on type
public func message() -> String {
let authentication = BioMetricAuthenticator.shared

switch self {
case .canceledByUser, .fallback, .canceledBySystem:
return ""
case .passcodeNotSet:
return authentication.faceIDAvailable() ? kSetPasscodeToUseFaceID : kSetPasscodeToUseTouchID
case .biometryNotAvailable:
return kBiometryNotAvailableReason
case .biometryNotEnrolled:
return authentication.faceIDAvailable() ? kNoFaceIdentityEnrolled : kNoFingerprintEnrolled
case .biometryLockedout:
return authentication.faceIDAvailable() ? kFaceIdPasscodeAuthenticationReason : kTouchIdPasscodeAuthenticationReason
default:
return authentication.faceIDAvailable() ? kDefaultFaceIDAuthenticationFailedReason : kDefaultTouchIDAuthenticationFailedReason
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
import UIKit
import LocalAuthentication

open class BioMetricAuthenticator: NSObject {
@objc open class BioMetricAuthenticator: NSObject {

// MARK: - Singleton
public static let shared = BioMetricAuthenticator()
@objc public static let shared = BioMetricAuthenticator()

// MARK: - Private
private override init() {}
Expand All @@ -52,10 +52,15 @@ open class BioMetricAuthenticator: NSObject {

// MARK:- Public

public extension BioMetricAuthenticator {
@objc public protocol BioMetricAuthenticatorController {
func biometricResultOK() -> Void;
func biometricError(error: AuthenticationErrorOBJC) -> Void;
}

@objc public extension BioMetricAuthenticator {

/// checks if biometric authentication can be performed currently on the device.
class func canAuthenticate() -> Bool {
@objc class func canAuthenticate() -> Bool {

var isBiometricAuthenticationAvailable = false
var error: NSError? = nil
Expand All @@ -67,11 +72,25 @@ public extension BioMetricAuthenticator {
}

/// Check for biometric authentication
class func authenticateWithBioMetrics(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", completion: @escaping (Result<Bool, AuthenticationError>) -> Void) {
@nonobjc class func authenticateWithBioMetrics(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", completion: @escaping (Result<Bool, AuthenticationError>) -> Void) {

let reasonString = reason.isEmpty ? BioMetricAuthenticator.shared.defaultBiometricAuthenticationReason() : reason
let context = getContext(reason: reason, fallbackTitle: fallbackTitle, cancelTitle: cancelTitle);

// authenticate
BioMetricAuthenticator.shared.evaluate(policy: .deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, completion: completion)
}

@objc class func authenticateWithBioMetrics(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", completion: BioMetricAuthenticatorController) {

// reason
let reasonString = reason.isEmpty ? BioMetricAuthenticator.shared.defaultBiometricAuthenticationReason() : reason
let context = getContext(reason: reason, fallbackTitle: fallbackTitle, cancelTitle: cancelTitle);

// authenticate
BioMetricAuthenticator.shared.evaluate(policy: .deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, completion: completion)
}

class func getContext(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "") -> LAContext{
// context
var context: LAContext!
if BioMetricAuthenticator.shared.isReuseDurationSet() {
Expand All @@ -86,12 +105,33 @@ public extension BioMetricAuthenticator {
context.localizedCancelTitle = cancelTitle
}

return context;
}

/// Check for device passcode authentication
@nonobjc class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", completion: @escaping (Result<Bool, AuthenticationError>) -> ()) {

// reason
let reasonString = reason.isEmpty ? BioMetricAuthenticator.shared.defaultPasscodeAuthenticationReason() : reason

let context = LAContext()

// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}

// authenticate
BioMetricAuthenticator.shared.evaluate(policy: .deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, completion: completion)
if #available(iOS 9.0, *) {
BioMetricAuthenticator.shared.evaluate(policy: .deviceOwnerAuthentication, with: context, reason: reasonString, completion: completion)
} else {
// Fallback on earlier versions
BioMetricAuthenticator.shared.evaluate(policy: .deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, completion: completion)
}
}

/// Check for device passcode authentication
class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", completion: @escaping (Result<Bool, AuthenticationError>) -> ()) {
@objc class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", completion: BioMetricAuthenticatorController) {

// reason
let reasonString = reason.isEmpty ? BioMetricAuthenticator.shared.defaultPasscodeAuthenticationReason() : reason
Expand All @@ -113,7 +153,7 @@ public extension BioMetricAuthenticator {
}

/// checks if device supports face id authentication
func faceIDAvailable() -> Bool {
@objc func faceIDAvailable() -> Bool {
if #available(iOS 11.0, *) {
let context = LAContext()
return (context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: nil) && context.biometryType == .faceID)
Expand All @@ -122,7 +162,7 @@ public extension BioMetricAuthenticator {
}

/// checks if device supports touch id authentication
func touchIDAvailable() -> Bool {
@objc func touchIDAvailable() -> Bool {
let context = LAContext()
var error: NSError?

Expand All @@ -134,6 +174,7 @@ public extension BioMetricAuthenticator {
}
}


// MARK:- Private
extension BioMetricAuthenticator {

Expand All @@ -155,6 +196,19 @@ extension BioMetricAuthenticator {
return true
}

private func evaluate(policy: LAPolicy, with context: LAContext, reason: String, completion: BioMetricAuthenticatorController) {

context.evaluatePolicy(policy, localizedReason: reason) { (success, err) in
DispatchQueue.main.async {
if success {
completion.biometricResultOK();
} else {
completion.biometricError(error: AuthenticationErrorOBJC.initWithError(err as! LAError));
}
}
}
}

/// evaluate policy
private func evaluate(policy: LAPolicy, with context: LAContext, reason: String, completion: @escaping (Result<Bool, AuthenticationError>) -> ()) {

Expand Down