BiometricService.swift (3832B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * 7 * @author Marc Stibane 8 */ 9 import SwiftUI 10 import LocalAuthentication 11 12 class BiometricService: ObservableObject { 13 static let shared = BiometricService() 14 @Published var canAuthenticate: Bool = true 15 @Published var isAuthenticated = false 16 @Published var authenticationError: String? 17 18 @AppStorage("useAuthentication") var useAuthentication: Bool = false 19 20 private var context: LAContext? 21 22 func biometryType() -> LABiometryType { 23 if context == nil { 24 resetContext() 25 } 26 return context?.biometryType ?? .none 27 } 28 29 func authenticateUser() { 30 let reason = resetContext() 31 32 if useAuthentication { 33 guard let context = context else { 34 authenticationError = String(localized: "Failed to initialize authentication context.", comment: "FaceID") 35 canAuthenticate = false 36 return 37 } 38 guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else { 39 authenticationError = String(localized: "Authentication is not available on this device.", comment: "FaceID") 40 canAuthenticate = false 41 return 42 } 43 canAuthenticate = true 44 context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { [weak self] success, authenticationError in 45 DispatchQueue.main.async { 46 if success { 47 self?.isAuthenticated = true 48 self?.authenticationError = nil 49 } else if let error = authenticationError as? LAError { 50 self?.authenticationError = self?.errorMessage(for: error) 51 } else { 52 self?.authenticationError = String(localized: "Unknown authentication error occurred.", comment: "FaceID") 53 } 54 } 55 } 56 } 57 } 58 59 private func resetContext() -> String { 60 let reason = String(localized: "Authenticate to access your money", comment: "FaceID") 61 context = LAContext() 62 context?.localizedReason = reason 63 context?.localizedCancelTitle = String(localized: "Cancel", comment: "FaceID") 64 context?.localizedFallbackTitle = String(localized: "Use Passcode", comment: "FaceID") 65 return reason 66 } 67 68 private func errorMessage(for error: LAError) -> String { 69 switch error.code { 70 case .authenticationFailed: 71 return String(localized: "Authentication failed. Please try again.", comment: "FaceID") 72 case .userCancel: 73 return String(localized: "You canceled the authentication.", comment: "FaceID") 74 case .userFallback: 75 return String(localized: "You chose to use the fallback option.", comment: "FaceID") 76 case .systemCancel: 77 return String(localized: "The system canceled the authentication.", comment: "FaceID") 78 case .passcodeNotSet: 79 return String(localized: "Passcode is not set on this device.", comment: "FaceID") 80 case .biometryNotAvailable: 81 return String(localized: "Biometric authentication is not available on this device.", comment: "FaceID") 82 case .biometryNotEnrolled: 83 return String(localized: "No biometrics are enrolled on this device.", comment: "FaceID") 84 case .biometryLockout: 85 return String(localized: "Biometric authentication is locked. Enter your passcode to unlock.", comment: "FaceID") 86 default: 87 return String(localized: "An unknown error occurred.", comment: "FaceID") 88 } 89 } 90 }