taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

BiometricService.swift (3670B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-25 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 authenticateUser() {
     23         let reason = resetContext()
     24 
     25         if useAuthentication {
     26             guard let context = context else {
     27                 authenticationError = String(localized: "Failed to initialize authentication context.", comment: "FaceID")
     28                 canAuthenticate = false
     29                 return
     30             }
     31             guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) else {
     32                 authenticationError = String(localized: "Authentication is not available on this device.", comment: "FaceID")
     33                 canAuthenticate = false
     34                 return
     35             }
     36             canAuthenticate = true
     37             context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { [weak self] success, authenticationError in
     38                 DispatchQueue.main.async {
     39                     if success {
     40                         self?.isAuthenticated = true
     41                         self?.authenticationError = nil
     42                     } else if let error = authenticationError as? LAError {
     43                         self?.authenticationError = self?.errorMessage(for: error)
     44                     } else {
     45                         self?.authenticationError = String(localized: "Unknown authentication error occurred.", comment: "FaceID")
     46                     }
     47                 }
     48             }
     49         }
     50     }
     51 
     52     private func resetContext() -> String {
     53         let reason = String(localized: "Authenticate to access your money", comment: "FaceID")
     54         context = LAContext()
     55         context?.localizedReason = reason
     56         context?.localizedCancelTitle = String(localized: "Cancel", comment: "FaceID")
     57         context?.localizedFallbackTitle = String(localized: "Use Passcode", comment: "FaceID")
     58         return reason
     59     }
     60 
     61     private func errorMessage(for error: LAError) -> String {
     62         switch error.code {
     63             case .authenticationFailed:
     64                 return String(localized: "Authentication failed. Please try again.", comment: "FaceID")
     65             case .userCancel:
     66                 return String(localized: "You canceled the authentication.", comment: "FaceID")
     67             case .userFallback:
     68                 return String(localized: "You chose to use the fallback option.", comment: "FaceID")
     69             case .systemCancel:
     70                 return String(localized: "The system canceled the authentication.", comment: "FaceID")
     71             case .passcodeNotSet:
     72                 return String(localized: "Passcode is not set on this device.", comment: "FaceID")
     73             case .biometryNotAvailable:
     74                 return String(localized: "Biometric authentication is not available on this device.", comment: "FaceID")
     75             case .biometryNotEnrolled:
     76                 return String(localized: "No biometrics are enrolled on this device.", comment: "FaceID")
     77             case .biometryLockout:
     78                 return String(localized: "Biometric authentication is locked. Enter your passcode to unlock.", comment: "FaceID")
     79             default:
     80                 return String(localized: "An unknown error occurred.", comment: "FaceID")
     81         }
     82     }
     83 }