Skip to content

Commit e3144b4

Browse files
authored
feat: allow set desired policy when checking biometric auth availability (#3)
* feat: allow set desired policy when checking biometric auth availability * fix: move mapping LocalAuthenticationPolicy->LAPolicy. fix SwiftLint * fix: disable compilation of code on platforms which do not support it
1 parent f0bec04 commit e3144b4

File tree

6 files changed

+70
-17
lines changed

6 files changed

+70
-17
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Use the provided methods to perform authentication tasks:
5858

5959
```swift
6060
// Check if biometric authentication is available
61-
if try await provider.checkBiometricAvailable() {
61+
if try await provider.checkBiometricAvailable(with: .biometrics) {
6262
// Set up biometric authentication
6363
if try await provider.setBiometricAuthentication(localizedReason: "Authenticate to access your data") {
6464
// Authenticate the user
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// LAContext+Extensions.swift
3+
//
4+
//
5+
// Created by Andriy Vasyk on 02.09.2024.
6+
//
7+
8+
import Foundation
9+
import LocalAuthentication
10+
11+
extension LAContext {
12+
internal func canEvaluate(policy: LocalAuthenticationPolicy, error: NSErrorPointer) -> Bool {
13+
let localPolicy: LAPolicy
14+
switch policy {
15+
case .authentication:
16+
localPolicy = .deviceOwnerAuthentication
17+
case .biometrics:
18+
localPolicy = .deviceOwnerAuthenticationWithBiometrics
19+
#if os(macOS)
20+
case .watch:
21+
localPolicy = .deviceOwnerAuthenticationWithWatch
22+
case .biometricsOrWatch:
23+
localPolicy = .deviceOwnerAuthenticationWithBiometricsOrWatch
24+
#endif
25+
}
26+
27+
return self.canEvaluatePolicy(localPolicy, error: error)
28+
}
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// LocalAuthenticationPolicy.swift
3+
//
4+
//
5+
// Created by Andriy Vasyk on 02.09.2024.
6+
//
7+
8+
import Foundation
9+
10+
public enum LocalAuthenticationPolicy: Int {
11+
/// Device owner will be authenticated using a biometric method (Touch ID).
12+
case biometrics = 1
13+
14+
/// Device owner will be authenticated by biometry or user password.
15+
case authentication = 2
16+
17+
#if os(macOS)
18+
/// Device owner will be authenticated by Apple Watch.
19+
case watch = 3
20+
21+
/// Device owner will be authenticated by biometry or Apple Watch.
22+
case biometricsOrWatch = 4
23+
#endif
24+
}

Sources/LocalAuthenticationProvider/LocalAuthenticationProvider.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,12 @@ public final class LocalAuthenticationProvider: LocalAuthenticationProviderProto
4545
}
4646

4747
/// Checks if biometric authentication is available on the device.
48+
/// - Parameter policy: The policy to evaluate.
4849
/// - Returns: `true` if biometric authentication is available, `false` otherwise.
4950
/// - Throws: An appropriate `LocalAuthenticationError` if an error occurs during the check.
50-
public func checkBiometricAvailable() async throws -> Bool {
51+
public func checkBiometricAvailable(with policy: LocalAuthenticationPolicy) async throws -> Bool {
5152
var error: NSError?
52-
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
53-
return true
54-
} else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
53+
if context.canEvaluate(policy: policy, error: &error) {
5554
return true
5655
} else {
5756
if let error {
@@ -87,7 +86,7 @@ public final class LocalAuthenticationProvider: LocalAuthenticationProviderProto
8786
/// - Returns: `true` if biometric authentication was successfully set up, `false` otherwise.
8887
/// - Throws: An appropriate `LocalAuthenticationError` if an error occurs during setup.
8988
public func setBiometricAuthentication(localizedReason: String) async throws -> Bool {
90-
if try await checkBiometricAvailable() {
89+
if try await checkBiometricAvailable(with: .biometrics) {
9190
return try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: localizedReason)
9291
} else {
9392
return false
@@ -99,7 +98,7 @@ public final class LocalAuthenticationProvider: LocalAuthenticationProviderProto
9998
/// - Returns: `true` if authentication was successful, `false` otherwise.
10099
/// - Throws: An appropriate `LocalAuthenticationError` if an error occurs during authentication.
101100
public func authenticate(localizedReason: String) async throws -> Bool {
102-
if try await checkBiometricAvailable() {
101+
if try await checkBiometricAvailable(with: .biometrics) {
103102
guard context.biometryType != .none else {
104103
logger.error("\(#function) User face or fingerprint were not recognized")
105104
throw LocalAuthenticationError.biometricError

Sources/LocalAuthenticationProvider/LocalAuthenticationProviderProtocol.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ import Foundation
2828
public protocol LocalAuthenticationProviderProtocol {
2929
/// Checks if biometric authentication is available on the device.
3030
///
31+
/// - Parameter policy: The policy to evaluate.
3132
/// - Returns: `true` if biometric authentication is available, `false` otherwise.
3233
/// - Throws: An appropriate `LocalAuthenticationError` if an error occurs during the check.
33-
func checkBiometricAvailable() async throws -> Bool
34+
func checkBiometricAvailable(with policy: LocalAuthenticationPolicy) async throws -> Bool
3435

3536
/// Sets up biometric authentication with the given localized reason, preparing for authentication but not initiating it immediately.
3637
///

Tests/LocalAuthenticationProviderTests/LocalAuthenticationProviderTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@ final class LocalAuthenticationProviderTests: XCTestCase {
2929
func testCanEvaluatePolicyWhenOwnerAuthenticatedWithBiometrics() async throws {
3030
let context = MockLAContext(canEvaluatePolicies: [.deviceOwnerAuthenticationWithBiometrics])
3131
let provider = LocalAuthenticationProvider(context: context)
32-
let checkBiometricsSuccess = try await provider.checkBiometricAvailable()
32+
let checkBiometricsSuccess = try await provider.checkBiometricAvailable(with: .biometrics)
3333
XCTAssert(checkBiometricsSuccess)
3434
}
3535

3636
func testCanEvaluatePolicyWhenOwnerNotAuthenticateWithBiometricsIssue() async throws {
37-
let result = try await provider.checkBiometricAvailable()
37+
let result = try await provider.checkBiometricAvailable(with: .biometrics)
3838
XCTAssertFalse(result)
3939
}
4040

4141
func testCanEvaluatePolicyWhenOwnerAlreadyAuthenticated() async throws {
4242
let context = MockLAContext(canEvaluatePolicies: [.deviceOwnerAuthentication])
4343
let provider = LocalAuthenticationProvider(context: context)
44-
let checkBiometricsSuccess = try await provider.checkBiometricAvailable()
44+
let checkBiometricsSuccess = try await provider.checkBiometricAvailable(with: .authentication)
4545
XCTAssert(checkBiometricsSuccess)
4646
}
4747

@@ -50,7 +50,7 @@ final class LocalAuthenticationProviderTests: XCTestCase {
5050
let context = MockLAContext(canEvaluatePolicyError: NSError(domain: "", code: errorCode))
5151
let provider = LocalAuthenticationProvider(context: context)
5252
do {
53-
_ = try await provider.checkBiometricAvailable()
53+
_ = try await provider.checkBiometricAvailable(with: .biometrics)
5454
XCTFail("Error must be thrown")
5555
} catch LocalAuthenticationError.deniedAccess {
5656
} catch {
@@ -63,7 +63,7 @@ final class LocalAuthenticationProviderTests: XCTestCase {
6363
let context = MockLAContext(canEvaluatePolicyError: NSError(domain: "", code: errorCode))
6464
let provider = LocalAuthenticationProvider(context: context)
6565
do {
66-
_ = try await provider.checkBiometricAvailable()
66+
_ = try await provider.checkBiometricAvailable(with: .biometrics)
6767
XCTFail(".noPasscodeSet error must be thrown")
6868
} catch LocalAuthenticationError.noPasscodeSet {
6969
} catch {
@@ -76,7 +76,7 @@ final class LocalAuthenticationProviderTests: XCTestCase {
7676
let context = MockLAContext(canEvaluatePolicyError: NSError(domain: "", code: errorCode))
7777
let provider = LocalAuthenticationProvider(context: context)
7878
do {
79-
_ = try await provider.checkBiometricAvailable()
79+
_ = try await provider.checkBiometricAvailable(with: .biometrics)
8080
XCTFail("Error must be thrown")
8181
} catch LocalAuthenticationError.biometricError {
8282
} catch {
@@ -89,7 +89,7 @@ final class LocalAuthenticationProviderTests: XCTestCase {
8989
let context = MockLAContext(canEvaluatePolicyError: NSError(domain: "", code: errorCode), biometryType: .faceID)
9090
let provider = LocalAuthenticationProvider(context: context)
9191
do {
92-
_ = try await provider.checkBiometricAvailable()
92+
_ = try await provider.checkBiometricAvailable(with: .biometrics)
9393
XCTFail("Error must be thrown")
9494
} catch LocalAuthenticationError.noFaceIdEnrolled {
9595
} catch {
@@ -102,7 +102,7 @@ final class LocalAuthenticationProviderTests: XCTestCase {
102102
let context = MockLAContext(canEvaluatePolicyError: NSError(domain: "", code: errorCode), biometryType: .touchID)
103103
let provider = LocalAuthenticationProvider(context: context)
104104
do {
105-
_ = try await provider.checkBiometricAvailable()
105+
_ = try await provider.checkBiometricAvailable(with: .biometrics)
106106
XCTFail("Error must be thrown")
107107
} catch LocalAuthenticationError.noFingerprintEnrolled {
108108
} catch {
@@ -116,7 +116,7 @@ final class LocalAuthenticationProviderTests: XCTestCase {
116116
let context = MockLAContext(canEvaluatePolicyError: expectedError)
117117
let provider = LocalAuthenticationProvider(context: context)
118118
do {
119-
_ = try await provider.checkBiometricAvailable()
119+
_ = try await provider.checkBiometricAvailable(with: .biometrics)
120120
XCTFail("Error must be thrown")
121121
} catch LocalAuthenticationError.error(let error) {
122122
XCTAssertEqual(error as NSError, expectedError)

0 commit comments

Comments
 (0)