AuthService.swift 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. // LoopFollow
  2. // AuthService.swift
  3. import Foundation
  4. import LocalAuthentication
  5. public enum AuthResult {
  6. case success
  7. case canceled
  8. case unavailable(String)
  9. case failed
  10. }
  11. public enum AuthService {
  12. /// Unified authentication that prefers biometrics and falls back to passcode automatically.
  13. /// - Parameters:
  14. /// - reason: Shown in the system auth prompt.
  15. /// - reuseDuration: Optional Touch ID/Face ID reuse window (seconds). 0 disables reuse.
  16. /// - completion: Returns an `AuthResult` representing the outcome.
  17. public static func authenticate(reason: String,
  18. reuseDuration: TimeInterval = 0,
  19. completion: @escaping (AuthResult) -> Void)
  20. {
  21. let context = LAContext()
  22. context.localizedFallbackTitle = String(localized: "Enter Passcode")
  23. if reuseDuration > 0 {
  24. context.touchIDAuthenticationAllowableReuseDuration = reuseDuration
  25. }
  26. var error: NSError?
  27. guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
  28. var message = String(localized: "Device authentication is not available. ")
  29. let biometryType = context.biometryType
  30. if biometryType == .none {
  31. message += String(localized: "Please enable Face ID, Touch ID, or set up a device passcode in Settings.")
  32. } else if biometryType == .faceID {
  33. message += String(localized: "Face ID is not available. Please set up a device passcode in Settings.")
  34. } else if biometryType == .touchID {
  35. message += String(localized: "Touch ID is not available. Please set up a device passcode in Settings.")
  36. }
  37. DispatchQueue.main.async {
  38. completion(.unavailable(message))
  39. }
  40. return
  41. }
  42. context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, err in
  43. DispatchQueue.main.async {
  44. if success {
  45. completion(.success)
  46. return
  47. }
  48. if let e = err as? LAError {
  49. switch e.code {
  50. case .userCancel, .systemCancel, .appCancel:
  51. completion(.canceled)
  52. default:
  53. completion(.failed)
  54. }
  55. } else {
  56. completion(.failed)
  57. }
  58. }
  59. }
  60. }
  61. }