// LoopFollow // AuthService.swift import Foundation import LocalAuthentication public enum AuthResult { case success case canceled case unavailable(String) case failed } public enum AuthService { /// Unified authentication that prefers biometrics and falls back to passcode automatically. /// - Parameters: /// - reason: Shown in the system auth prompt. /// - reuseDuration: Optional Touch ID/Face ID reuse window (seconds). 0 disables reuse. /// - completion: Returns an `AuthResult` representing the outcome. public static func authenticate(reason: String, reuseDuration: TimeInterval = 0, completion: @escaping (AuthResult) -> Void) { let context = LAContext() context.localizedFallbackTitle = String(localized: "Enter Passcode") if reuseDuration > 0 { context.touchIDAuthenticationAllowableReuseDuration = reuseDuration } var error: NSError? guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else { var message = String(localized: "Device authentication is not available. ") let biometryType = context.biometryType if biometryType == .none { message += String(localized: "Please enable Face ID, Touch ID, or set up a device passcode in Settings.") } else if biometryType == .faceID { message += String(localized: "Face ID is not available. Please set up a device passcode in Settings.") } else if biometryType == .touchID { message += String(localized: "Touch ID is not available. Please set up a device passcode in Settings.") } DispatchQueue.main.async { completion(.unavailable(message)) } return } context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, err in DispatchQueue.main.async { if success { completion(.success) return } if let e = err as? LAError { switch e.code { case .userCancel, .systemCancel, .appCancel: completion(.canceled) default: completion(.failed) } } else { completion(.failed) } } } } }