diff --git a/CHANGELOG.md b/CHANGELOG.md index 7025f68..01bd1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.1.4 +- **Update**: Refactor PassCode + ## 0.1.3 - **Update**: Typical api response keys naming diff --git a/LeadKitAdditions.podspec b/LeadKitAdditions.podspec index c015803..91d2b7a 100644 --- a/LeadKitAdditions.podspec +++ b/LeadKitAdditions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKitAdditions" - s.version = "0.1.3" + s.version = "0.1.4" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKitAdditions" s.license = "Apache License, Version 2.0" diff --git a/LeadKitAdditions/Sources/Classes/ApiResponse.swift b/LeadKitAdditions/Sources/Classes/ApiResponse.swift index 98773b5..6a98860 100644 --- a/LeadKitAdditions/Sources/Classes/ApiResponse.swift +++ b/LeadKitAdditions/Sources/Classes/ApiResponse.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Classes/BaseDateFormatter.swift b/LeadKitAdditions/Sources/Classes/BaseDateFormatter.swift index 3bdf9ae..cec812c 100644 --- a/LeadKitAdditions/Sources/Classes/BaseDateFormatter.swift +++ b/LeadKitAdditions/Sources/Classes/BaseDateFormatter.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Classes/LoadingBarButton.swift b/LeadKitAdditions/Sources/Classes/LoadingBarButton.swift index 2d7a7a9..5db8b52 100644 --- a/LeadKitAdditions/Sources/Classes/LoadingBarButton.swift +++ b/LeadKitAdditions/Sources/Classes/LoadingBarButton.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeConfiguration.swift b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeConfiguration.swift index d48d8e5..4d9bc18 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeConfiguration.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeConfiguration.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -24,26 +24,22 @@ public struct PassCodeConfiguration { /// Pass code length - public var passCodeCharactersNumber: UInt = 4 + public let passCodeLength: Int + /// Incorrect pass code attempts count - public var maxAttemptsLoginNumber: UInt = 5 + public let maxAttemptsNumber: Int /// Clear input progress when application goes to background - public var shouldResetWhenGoBackground: Bool = true + public let shouldResetWhenGoBackground: Bool - private init() {} - - init?(passCodeCharactersNumber: UInt) { - guard passCodeCharactersNumber > 0 else { - assertionFailure("passCodeCharactersNumber must be greater then 0") - return nil - } - self.passCodeCharactersNumber = passCodeCharactersNumber + public init(passCodeLength: Int = 4, maxAttemptsNumber: Int = 5, shouldResetWhenGoBackground: Bool = true) { + self.passCodeLength = passCodeLength + self.maxAttemptsNumber = maxAttemptsNumber + self.shouldResetWhenGoBackground = shouldResetWhenGoBackground } /// Returns configuration with default values public static var defaultConfiguration: PassCodeConfiguration { return PassCodeConfiguration() } - } diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeError.swift b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeError.swift index 6e766af..ca7ebed 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeError.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeError.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -21,8 +21,22 @@ // /// Describes error, which may occur during pass code entering +/// - codesNotMatch: Different codes +/// - wrongCode: Value is remaining attemps +/// - tooManyAttempts: Attempts limit reached public enum PassCodeError: Error { case codesNotMatch - case wrongCode + case wrongCode(Int) case tooManyAttempts } + +public extension PassCodeError { + var isTooManyAttempts: Bool { + switch self { + case .tooManyAttempts: + return true + default: + return false + } + } +} diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolder.swift b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolder.swift index 4dae8fc..aac5919 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolder.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolder.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -81,7 +81,7 @@ public class PassCodeHolderCreate: PassCodeHolderProtocol { if let passCode = passCode { return .valid(passCode) } else { - return .inValid(.codesNotMatch) + return .invalid(.codesNotMatch) } } @@ -112,7 +112,7 @@ public class PassCodeHolderEnter: PassCodeHolderProtocol { if let passCode = passCode { return .valid(passCode) } else { - return .inValid(nil) + return .invalid(nil) } } @@ -178,7 +178,7 @@ public class PassCodeHolderChange: PassCodeHolderProtocol { if let passCode = passCode { return .valid(passCode) } else { - return .inValid(enterStep == .newEnter ? nil : .codesNotMatch) + return .invalid(enterStep == .newEnter ? nil : .codesNotMatch) } } diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolderProtocol.swift b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolderProtocol.swift index b4a031d..d8bcf6c 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolderProtocol.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeHolderProtocol.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeValidationResult.swift b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeValidationResult.swift index 41bdbd1..f918a6d 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeValidationResult.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/Model/PassCodeValidationResult.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -24,33 +24,35 @@ public enum PassCodeValidationResult { case valid(String) - case inValid(PassCodeError?) + case invalid(PassCodeError?) - public var isValid: Bool { +} + +public extension PassCodeValidationResult { + var isValid: Bool { switch self { case .valid: return true - default: + case .invalid: return false } } - public var passCode: String? { + var passCode: String? { switch self { case let .valid(passCode): return passCode - default: + case .invalid: return nil } } - public var error: PassCodeError? { + var error: PassCodeError? { switch self { - case let .inValid(error): + case let .invalid(error): return error - default: + case .valid: return nil } } - } diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/View/BasePassCodeViewController.swift b/LeadKitAdditions/Sources/Controllers/PassCode/View/BasePassCodeViewController.swift index 5ad2563..e441d32 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/View/BasePassCodeViewController.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/View/BasePassCodeViewController.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -47,7 +47,7 @@ public enum PassCodeControllerState { } /// Base view controller that operates with pass code -open class BasePassCodeViewController: UIViewController { +open class BasePassCodeViewController: UIViewController, ConfigurableController { public var viewModel: BasePassCodeViewModel! @@ -59,7 +59,7 @@ open class BasePassCodeViewController: UIViewController { public let disposeBag = DisposeBag() - fileprivate lazy var fakeTextField: UITextField = { + private lazy var fakeTextField: UITextField = { let fakeTextField = UITextField() fakeTextField.isSecureTextEntry = true fakeTextField.keyboardType = .numberPad @@ -76,9 +76,20 @@ open class BasePassCodeViewController: UIViewController { initialLoadView() initialDotNumberConfiguration() - enebleKeyboard() configureBackgroundNotifications() - showTouchIdIfNeeded(with: touchIdHint) + showBiometricsRequestIfNeeded(with: biometricsAuthorizationHint) + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + fakeTextField.becomeFirstResponder() + } + + override open func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + fakeTextField.resignFirstResponder() } // MARK: - Private functions @@ -95,14 +106,10 @@ open class BasePassCodeViewController: UIViewController { .disposed(by: disposeBag) } - private func enebleKeyboard() { - fakeTextField.becomeFirstResponder() - } - private func initialDotNumberConfiguration() { dotStackView.arrangedSubviews.forEach { dotStackView.removeArrangedSubview($0) } - for _ in 0.. String { + open func errorDescription(for error: PassCodeError) -> NSAttributedString? { assertionFailure("You should override this method: errorDescription(for error: PassCodeError)") - return "" + return nil } /// Override to change action title text @@ -189,7 +192,7 @@ open class BasePassCodeViewController: UIViewController { /// Call to show error open func showError(for error: PassCodeError) { - errorLabel?.text = errorDescription(for: error) + errorLabel?.attributedText = errorDescription(for: error) errorLabel?.isHidden = false } @@ -204,11 +207,7 @@ open class BasePassCodeViewController: UIViewController { titleLabel?.text = actionTitle(for: passCodeControllerState) } -} - -// MARK: - ConfigurableController -// We need to implement all functions of ConfigurableController protocol to give ability to override them. -extension BasePassCodeViewController: ConfigurableController { + // MARK: - ConfigurableController open func bindViews() { fakeTextField.rx.text.asDriver() @@ -227,13 +226,13 @@ extension BasePassCodeViewController: ConfigurableController { if validationResult.isValid { self?.hideError() - } else if let pasCodeError = validationResult.error { - self?.showError(for: pasCodeError) + } else if let passCodeError = validationResult.error { + self?.showError(for: passCodeError) } }) .disposed(by: disposeBag) - viewModel.passCodeControllerState + viewModel.passCodeControllerStateDriver .drive(onNext: { [weak self] controllerState in self?.configureUI(for: controllerState) }) @@ -251,6 +250,7 @@ extension BasePassCodeViewController: ConfigurableController { } // MARK: - UITextFieldDelegate + extension BasePassCodeViewController: UITextFieldDelegate { public func textField(_ textField: UITextField, @@ -262,3 +262,4 @@ extension BasePassCodeViewController: UITextFieldDelegate { } } + diff --git a/LeadKitAdditions/Sources/Controllers/PassCode/ViewModel/BasePassCodeViewModel.swift b/LeadKitAdditions/Sources/Controllers/PassCode/ViewModel/BasePassCodeViewModel.swift index a9d42ec..68952ff 100644 --- a/LeadKitAdditions/Sources/Controllers/PassCode/ViewModel/BasePassCodeViewModel.swift +++ b/LeadKitAdditions/Sources/Controllers/PassCode/ViewModel/BasePassCodeViewModel.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -37,36 +37,31 @@ open class BasePassCodeViewModel: BaseViewModel { public let disposeBag = DisposeBag() - /// TouchId service, which can answer if user is authorized by finger - public let touchIdService: TouchIDService? + /// Service that can answer if user is authorized by biometrics + public let biometricsService = BiometricsService() + /// Contains configuration for pass code operations public let passCodeConfiguration: PassCodeConfiguration - fileprivate let validationResultHolder = Variable(nil) + private let validationResultHolder = Variable(nil) var validationResult: Driver { return validationResultHolder.asDriver() } - fileprivate let passCodeControllerStateHolder = Variable(.enter) - public var passCodeControllerState: Driver { - return passCodeControllerStateHolder.asDriver() + private let passCodeControllerStateVariable = Variable(.enter) + public var passCodeControllerStateDriver: Driver { + return passCodeControllerStateVariable.asDriver() } private let passCodeText = Variable(nil) - fileprivate var attemptsNumber = 0 + private var attemptsNumber = 0 - fileprivate lazy var passCodeHolder: PassCodeHolderProtocol = { - return PassCodeHolderBuilder.build(with: self.controllerType) - }() - - public init(controllerType: PassCodeControllerType, - passCodeConfiguration: PassCodeConfiguration, - touchIdService: TouchIDService? = nil) { + private lazy var passCodeHolder: PassCodeHolderProtocol = PassCodeHolderBuilder.build(with: self.controllerType) + public init(controllerType: PassCodeControllerType, passCodeConfiguration: PassCodeConfiguration) { self.controllerType = controllerType self.passCodeConfiguration = passCodeConfiguration - self.touchIdService = touchIdService bindViewModel() } @@ -76,35 +71,14 @@ open class BasePassCodeViewModel: BaseViewModel { .distinctUntilChanged { $0 == $1 } .drive(onNext: { [weak self] passCode in if let passCode = passCode, - passCode.characters.count == Int(self?.passCodeConfiguration.passCodeCharactersNumber ?? 0) { + passCode.characters.count == Int(self?.passCodeConfiguration.passCodeLength ?? 0) { self?.set(passCode: passCode) } }) .disposed(by: disposeBag) - validationResultHolder.asDriver() - .drive(onNext: { [weak self] validationResult in - guard let sSelf = self else { - return - } - - if sSelf.passCodeHolder.type == .change { - if validationResult?.isValid ?? false, - sSelf.passCodeHolder.enterStep == .repeatEnter, - let passCode = validationResult?.passCode { - - sSelf.authSucceed(.passCode(passCode)) - } else { - sSelf.passCodeControllerStateHolder.value = sSelf.passCodeHolder.enterStep - } - } else { - if validationResult?.isValid ?? false, let passCode = validationResult?.passCode { - sSelf.authSucceed(.passCode(passCode)) - } else { - sSelf.passCodeControllerStateHolder.value = sSelf.passCodeHolder.enterStep - } - } - }) + validationResultHolder.asObservable() + .bind(to: validationResultBinder) .disposed(by: disposeBag) } @@ -121,11 +95,21 @@ open class BasePassCodeViewModel: BaseViewModel { public func reset() { passCodeText.value = nil validationResultHolder.value = nil - passCodeControllerStateHolder.value = controllerType == .change ? .oldEnter : .enter + passCodeControllerStateVariable.value = controllerType == .change ? .oldEnter : .enter attemptsNumber = 0 passCodeHolder.reset() } + public func authenticateUsingBiometrics(with description: String) { + biometricsService.authenticateWithBiometrics(with: description) { [weak self] success, error in + if success { + self?.authSucceed(.touchId) + } else { + self?.authFailed(with: error) + } + } + } + // MARK: - HAVE TO OVERRIDE /// Override to check if entered pass code is equal to stored @@ -134,40 +118,68 @@ open class BasePassCodeViewModel: BaseViewModel { return false } - /// Handler called after successful authentication + /// Method is called after successful authentication open func authSucceed(_ type: PassCodeAuthType) { assertionFailure("You should override this method: authSucceed(_ type: PassCodeAuthType)") } - // MARK: - Functions that can you can override to use TouchId + /// Called when authentication failed + open func authFailed(with: Error?) { + assertionFailure("You should override this method: authFailed(with: Error)") + } - /// Override to be able use touchId during authentication - open var isTouchIdEnabled: Bool { + // MARK: - Biometrics + + /// Posibility to use biometrics for authentication + open var isBiometricsEnabled: Bool { return false } - /// You should save user choice about authenticate by touchId - open func activateTouchIdForUser() { - assertionFailure("You should override this method: activateTouchIdForUser()") + /// Notify about activation for biometrics. Remember to save user choice + open func activateBiometricsForUser() { + assertionFailure("You should override this method: activateBiometricsForUser()") } } +private extension BasePassCodeViewModel { + var validationResultBinder: Binder { + return Binder(self) { model, validationResult in + let isValid = validationResult?.isValid ?? false + let passCode = validationResult?.passCode + + if model.passCodeHolder.type == .change { + if isValid, model.passCodeHolder.enterStep == .repeatEnter, let passCode = passCode { + model.authSucceed(.passCode(passCode)) + } else { + model.passCodeControllerStateVariable.value = model.passCodeHolder.enterStep + } + } else { + if isValid, let passCode = passCode { + model.authSucceed(.passCode(passCode)) + } else { + model.passCodeControllerStateVariable.value = model.passCodeHolder.enterStep + } + } + } + } +} + extension BasePassCodeViewModel { - fileprivate func set(passCode: String) { + private func set(passCode: String) { passCodeHolder.add(passCode: passCode) validateIfNeeded() if shouldUpdateControllerState { - passCodeControllerStateHolder.value = passCodeHolder.enterStep + passCodeControllerStateVariable.value = passCodeHolder.enterStep } } private var shouldUpdateControllerState: Bool { return !passCodeHolder.shouldValidate || !(validationResultHolder.value?.isValid ?? true) || - validationResultHolder.value?.error == .tooManyAttempts + validationResultHolder.value?.error?.isTooManyAttempts ?? false } private func validateIfNeeded() { @@ -177,16 +189,18 @@ extension BasePassCodeViewModel { var validationResult = passCodeHolder.validate() - if passCodeHolder.type == .enter || (passCodeHolder.type == .change && passCodeHolder.enterStep == .newEnter) { + let passCodeValidationForPassCodeChange = passCodeHolder.type == .change && passCodeHolder.enterStep == .newEnter + + if passCodeHolder.type == .enter || passCodeValidationForPassCodeChange { attemptsNumber += 1 if let passCode = validationResult.passCode, !isEnteredPassCodeValid(passCode) { - validationResult = .inValid(.wrongCode) + validationResult = .invalid(.wrongCode(passCodeConfiguration.maxAttemptsNumber - attemptsNumber)) } - if (!validationResult.isValid && attemptsNumber == Int(passCodeConfiguration.maxAttemptsLoginNumber)) || - attemptsNumber > Int(passCodeConfiguration.maxAttemptsLoginNumber) { - validationResult = .inValid(.tooManyAttempts) + if (!validationResult.isValid && attemptsNumber == Int(passCodeConfiguration.maxAttemptsNumber)) || + attemptsNumber > Int(passCodeConfiguration.maxAttemptsNumber) { + validationResult = .invalid(.tooManyAttempts) } } diff --git a/LeadKitAdditions/Sources/Enums/ApiError.swift b/LeadKitAdditions/Sources/Enums/ApiError.swift index bb4533e..aff43d1 100644 --- a/LeadKitAdditions/Sources/Enums/ApiError.swift +++ b/LeadKitAdditions/Sources/Enums/ApiError.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Enums/ApiErrorProtocol.swift b/LeadKitAdditions/Sources/Enums/ApiErrorProtocol.swift index 31b0113..f94a9a8 100644 --- a/LeadKitAdditions/Sources/Enums/ApiErrorProtocol.swift +++ b/LeadKitAdditions/Sources/Enums/ApiErrorProtocol.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Extensions/Observable+Extensions.swift b/LeadKitAdditions/Sources/Extensions/Observable+Extensions.swift index c6dbe09..c854ee9 100644 --- a/LeadKitAdditions/Sources/Extensions/Observable+Extensions.swift +++ b/LeadKitAdditions/Sources/Extensions/Observable+Extensions.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Extensions/UIBarButtonItem+Extensions.swift b/LeadKitAdditions/Sources/Extensions/UIBarButtonItem+Extensions.swift index ef3eaf7..f237bf2 100644 --- a/LeadKitAdditions/Sources/Extensions/UIBarButtonItem+Extensions.swift +++ b/LeadKitAdditions/Sources/Extensions/UIBarButtonItem+Extensions.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Extensions/UserDefaults+UserService.swift b/LeadKitAdditions/Sources/Extensions/UserDefaults+UserService.swift index 6ca694f..39ffd95 100644 --- a/LeadKitAdditions/Sources/Extensions/UserDefaults+UserService.swift +++ b/LeadKitAdditions/Sources/Extensions/UserDefaults+UserService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Services/BasePassCodeService.swift b/LeadKitAdditions/Sources/Services/BasePassCodeService.swift index 5892a7c..8686b1c 100644 --- a/LeadKitAdditions/Sources/Services/BasePassCodeService.swift +++ b/LeadKitAdditions/Sources/Services/BasePassCodeService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -24,11 +24,22 @@ import KeychainAccess import CocoaLumberjack import IDZSwiftCommonCrypto +private enum Keys { + static let passCodeHash = "passCodeHashKey" + static let isBiometricsEnabled = "isBiometricsEnabledKey" + static let isInitialLoad = "isInitialLoadKey" +} + +private enum Values { + static let biometricsEnabled = "biometricsEnabled" + static let initialLoad = "initialLoad" +} + /// Represents base pass code service which encapsulates pass code storing open class BasePassCodeService { /// Override to set specific keychain service name - open class var keychainService: String { + open class var keychainServiceString: String { return Bundle.main.bundleIdentifier ?? "" } @@ -42,47 +53,33 @@ open class BasePassCodeService { // MARK: - Private stuff - fileprivate lazy var keychain: Keychain = { - return Keychain(service: BasePassCodeService.keychainService) - .synchronizable(false) - }() + private lazy var keychain = Keychain(service: BasePassCodeService.keychainServiceString).synchronizable(false) - fileprivate var passCodeHash: String? { + private var passCodeHash: String? { return keychain[Keys.passCodeHash] } - fileprivate enum Keys { - static let passCodeHash = "passCodeHash" - static let isTouchIdEnabled = "isTouchIdEnabled" - static let isInitialLoad = "isInitialLoad" - } - - fileprivate enum Values { - static let touchIdEnabled = "touchIdEnabled" - static let initialLoad = "initialLoad" - } - } -extension BasePassCodeService { +public extension BasePassCodeService { /// Indicates is pass code already saved on this device - public var isPassCodeSaved: Bool { + var isPassCodeSaved: Bool { return keychain[Keys.passCodeHash] != nil } - /// Indicates is it possible to authenticate on this device via touch id - public var isTouchIdEnabled: Bool { + /// Possibility to authenticate via biometrics. TouchID or FaceID + var isBiometricsAuthorizationEnabled: Bool { get { - return keychain[Keys.isTouchIdEnabled] == Values.touchIdEnabled + return keychain[Keys.isBiometricsEnabled] == Values.biometricsEnabled } set { - keychain[Keys.isTouchIdEnabled] = newValue ? Values.touchIdEnabled : nil + keychain[Keys.isBiometricsEnabled] = newValue ? Values.biometricsEnabled : nil } } /// Saves new pass code - public func save(passCode: String?) { + func save(passCode: String?) { if let passCode = passCode { keychain[Keys.passCodeHash] = sha256(passCode) } else { @@ -91,14 +88,14 @@ extension BasePassCodeService { } /// Check if pass code is correct - public func check(passCode: String) -> Bool { + func check(passCode: String) -> Bool { return sha256(passCode) == passCodeHash } /// Reset pass code settings - public func reset() { + func reset() { save(passCode: nil) - isTouchIdEnabled = false + isBiometricsAuthorizationEnabled = false } } diff --git a/LeadKitAdditions/Sources/Services/BaseUserService.swift b/LeadKitAdditions/Sources/Services/BaseUserService.swift index 016b749..2fa1606 100644 --- a/LeadKitAdditions/Sources/Services/BaseUserService.swift +++ b/LeadKitAdditions/Sources/Services/BaseUserService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Services/TouchIDService.swift b/LeadKitAdditions/Sources/Services/BiometricsService.swift similarity index 61% rename from LeadKitAdditions/Sources/Services/TouchIDService.swift rename to LeadKitAdditions/Sources/Services/BiometricsService.swift index 4dfaab2..034ac89 100644 --- a/LeadKitAdditions/Sources/Services/TouchIDService.swift +++ b/LeadKitAdditions/Sources/Services/BiometricsService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -22,34 +22,38 @@ import LocalAuthentication -public typealias TouchIDServiceAuthHandler = (Bool) -> Void +public typealias BiometricsAuthHandler = (Bool, Error?) -> Void -/// Represents service that provides access to authentication via touch id -public class TouchIDService { +/// Service that provide access to authentication via biometric +public final class BiometricsService { - private lazy var laContext: LAContext = { - return LAContext() - }() - - public init() {} + private lazy var laContext = LAContext() /// Indicates is it possible to authenticate on this device via touch id - public var canAuthenticateByTouchId: Bool { + public var canAuthenticateWithBiometrics: Bool { return laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) } /** - Initiates system touch id authentication process + Initiates system biometrics authentication process - parameters: - description: prompt on the system alert that describes what for user should attach finger to device - authHandler: callback, with parameter, indicates if user authenticate successfuly */ - public func authenticateByTouchId(description: String, authHandler: @escaping TouchIDServiceAuthHandler) { - laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, - localizedReason: description) { success, _ in + public func authenticateWithBiometrics(with description: String, + fallback fallbackTitle: String? = nil, + cancel cancelTitle: String? = nil, + authHandler: @escaping BiometricsAuthHandler) { + if #available(iOS 10.0, *), let cancel = cancelTitle { + laContext.localizedCancelTitle = cancelTitle + } + if let fallback = fallbackTitle { + laContext.localizedFallbackTitle = fallbackTitle + } - authHandler(success) + laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: description) { success, error in + authHandler(success, error) } } diff --git a/LeadKitAdditions/Sources/Services/Network/ApiNetworkService.swift b/LeadKitAdditions/Sources/Services/Network/ApiNetworkService.swift index 2293906..f30065f 100644 --- a/LeadKitAdditions/Sources/Services/Network/ApiNetworkService.swift +++ b/LeadKitAdditions/Sources/Services/Network/ApiNetworkService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService+ActivityIndicator.swift b/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService+ActivityIndicator.swift index abe2e32..28f5aa1 100644 --- a/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService+ActivityIndicator.swift +++ b/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService+ActivityIndicator.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal diff --git a/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService.swift b/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService.swift index 54d8c2f..b64d9c0 100644 --- a/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService.swift +++ b/LeadKitAdditions/Sources/Services/Network/DefaultNetworkService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal