taler-ios

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

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 }