diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c5a31f9..6e303738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 1.30.0 + +- **Added**: Base classes for encryption and decryption user token with pin code or biometry +- **Added**: Pin code validators + ### 1.29.1 - **Updated**: `BaseTextAttributes` correct detection of the necessity of using attributed string diff --git a/LeadKit.podspec b/LeadKit.podspec index 44131050..77369e54 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "1.29.1" + s.version = "1.30.0" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/Package.swift b/Package.swift index 03a9262e..87950b8c 100644 --- a/Package.swift +++ b/Package.swift @@ -79,7 +79,7 @@ let package = Package( .target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"), .target(name: "TITransitions", path: "TITransitions/Sources"), .target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"), - .target(name: "TIAuth", dependencies: ["TIFoundationUtils"], path: "TIAuth/Sources"), + .target(name: "TIAuth", dependencies: ["TIFoundationUtils", "TIUIKitCore", "KeychainAccess"], path: "TIAuth/Sources"), .target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"), // MARK: - Tests diff --git a/TIAppleMapUtils/TIAppleMapUtils.podspec b/TIAppleMapUtils/TIAppleMapUtils.podspec index d9575168..80a3f947 100644 --- a/TIAppleMapUtils/TIAppleMapUtils.podspec +++ b/TIAppleMapUtils/TIAppleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAppleMapUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIAuth/Sources/Biometry/BiometryService.swift b/TIAuth/Sources/Biometry/BiometryService.swift new file mode 100644 index 00000000..112db509 --- /dev/null +++ b/TIAuth/Sources/Biometry/BiometryService.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import LocalAuthentication + +public protocol BiometryService { + var isBiometryAuthAvailable: Bool { get } + var biometryType: LABiometryType { get } +} + +extension LAContext: BiometryService { + public var isBiometryAuthAvailable: Bool { + canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) + } +} diff --git a/TIAuth/Sources/Biometry/BiometrySettingsStorage.swift b/TIAuth/Sources/Biometry/BiometrySettingsStorage.swift new file mode 100644 index 00000000..321bf886 --- /dev/null +++ b/TIAuth/Sources/Biometry/BiometrySettingsStorage.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol BiometrySettingsStorage { + var isBiometryAuthEnabled: Bool { get set } +} diff --git a/TIAuth/Sources/Biometry/DefaultBiometrySettingsStorage.swift b/TIAuth/Sources/Biometry/DefaultBiometrySettingsStorage.swift new file mode 100644 index 00000000..195fe064 --- /dev/null +++ b/TIAuth/Sources/Biometry/DefaultBiometrySettingsStorage.swift @@ -0,0 +1,48 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class DefaultBiometrySettingsStorage: BiometrySettingsStorage { + public enum StorageKeys { + static var isBiometryAuthEnabledStorageKey: String { + "isBiometryAuthEnabled" + } + } + + public var defaultsStorage: UserDefaults + + // MARK: - BiometrySettingsService + + public var isBiometryAuthEnabled: Bool { + get { + defaultsStorage.bool(forKey: StorageKeys.isBiometryAuthEnabledStorageKey) + } + set { + defaultsStorage.set(newValue, forKey: StorageKeys.isBiometryAuthEnabledStorageKey) + } + } + + public init(defaultsStorage: UserDefaults = .standard) { + self.defaultsStorage = defaultsStorage + } +} diff --git a/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmPresenter.swift b/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmPresenter.swift index 156b0e51..8ad2223d 100644 --- a/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmPresenter.swift +++ b/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmPresenter.swift @@ -21,18 +21,15 @@ // import Foundation +import TIUIKitCore @MainActor -protocol CodeConfirmPresenter { +protocol CodeConfirmPresenter: LifecyclePresenter { // MARK: - User actions handling func inputChanged(newInput: String?) func refreshCode() - // MARK: - View lifecycle handling - - func viewDidPresented() - // MARK: - Autofill func autofill(code: String, with codeId: String?) diff --git a/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmStateStorage.swift b/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmStateStorage.swift index c13a9dc7..b04e949d 100644 --- a/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmStateStorage.swift +++ b/TIAuth/Sources/CodeConfirmPresenter/CodeConfirmStateStorage.swift @@ -20,8 +20,6 @@ // THE SOFTWARE. // -import Foundation - @MainActor public protocol CodeConfirmStateStorage: AnyObject { var currentUserInput: String? { get set } diff --git a/TIAuth/Sources/CodeConfirmPresenter/DefaultCodeConfirmPresenter.swift b/TIAuth/Sources/CodeConfirmPresenter/DefaultCodeConfirmPresenter.swift index bad77a05..11472480 100644 --- a/TIAuth/Sources/CodeConfirmPresenter/DefaultCodeConfirmPresenter.swift +++ b/TIAuth/Sources/CodeConfirmPresenter/DefaultCodeConfirmPresenter.swift @@ -77,6 +77,8 @@ open class DefaultCodeConfirmPresenter= config.codeLength { stateStorage?.isExecutingRequest = true - Task { + executingTask = Task { await confirm(code: code) } } @@ -200,7 +202,7 @@ open class DefaultCodeConfirmPresenter Result { + crypt(data: data, operation: CCOperation(kCCEncrypt)) + } + + public func decrypt(data: Data) -> Result { + crypt(data: data, operation: CCOperation(kCCDecrypt)) + } + + private func crypt(data: Data, operation: CCOperation) -> Result { + let cryptDataLength = data.count + kCCBlockSizeAES128 + var cryptData = Data(count: cryptDataLength) + + var bytesLength = Int.zero + + let status = cryptData.withUnsafeMutableBytes { cryptBytes in + data.withUnsafeBytes { dataBytes in + iv.withUnsafeBytes { ivBytes in + key.withUnsafeBytes { keyBytes in + CCCrypt(operation, + CCAlgorithm(kCCAlgorithmAES), + CCOptions(kCCOptionPKCS7Padding), + keyBytes.baseAddress, + key.count, + ivBytes.baseAddress, + dataBytes.baseAddress, + data.count, + cryptBytes.baseAddress, + cryptDataLength, + &bytesLength) + } + } + } + } + + guard status == kCCSuccess else { + let error = CryptError(ccCryptorStatus: status, + key: key, + iv: iv) + + if operation == kCCEncrypt { + return .failure(.failedToEncrypt(data: data, error: error)) + } else { + return .failure(.failedToDecrypt(encryptedData: data, error: error)) + } + } + + cryptData.removeSubrange(bytesLength.. Result + func decrypt(data: Data) -> Result +} diff --git a/TIAuth/Sources/Cryptography/CipherError.swift b/TIAuth/Sources/Cryptography/CipherError.swift new file mode 100644 index 00000000..f34e750d --- /dev/null +++ b/TIAuth/Sources/Cryptography/CipherError.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public enum CipherError: Error { + case failedToEncrypt(data: Data, error: Error) + case failedToDecrypt(encryptedData: Data, error: Error) +} diff --git a/TIAuth/Sources/Cryptography/DefaultPBKDF2PasswordDerivator.swift b/TIAuth/Sources/Cryptography/DefaultPBKDF2PasswordDerivator.swift new file mode 100644 index 00000000..7cc4bc1c --- /dev/null +++ b/TIAuth/Sources/Cryptography/DefaultPBKDF2PasswordDerivator.swift @@ -0,0 +1,78 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +open class DefaultPBKDF2PasswordDerivator: PasswordDerivator { + public struct CryptError: Error { + public let derivationStatus: Int32 + public let password: String + public let salt: Data + + func asCipherError() -> CipherError { + var mutablePassword = password + return .failedToEncrypt(data: mutablePassword.withUTF8 { Data($0) }, + error: self) + } + } + + public func derive(password: String, salt: Data) -> Result { + var failureResult: Result? + + let derivedKeyBytes = Array(unsafeUninitializedCapacity: CryptoConstants.keyLength) { derivedKeyBuffer, initializedCount in + guard let derivedKeyStartAddress = derivedKeyBuffer.baseAddress else { + failureResult = .failure(CryptError(derivationStatus: CCStatus(kCCMemoryFailure), + password: password, + salt: salt) + .asCipherError()) + + initializedCount = .zero + return + } + + let deriviationStatus = salt.withContiguousStorageIfAvailable { saltBytes in + CCKeyDerivationPBKDF( + CCPBKDFAlgorithm(kCCPBKDF2), + password, + password.count, + saltBytes.baseAddress, + salt.count, + CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), + UInt32(CryptoConstants.pbkdf2NumberOfIterations), + derivedKeyStartAddress, + CryptoConstants.keyLength) + } ?? CCStatus(kCCParamError) + + guard deriviationStatus == kCCSuccess else { + initializedCount = .zero + failureResult = .failure(CryptError(derivationStatus: deriviationStatus, + password: password, + salt: salt) + .asCipherError()) + return + } + } + + return failureResult ?? .success(Data(derivedKeyBytes)) + } +} diff --git a/TIAuth/Sources/Cryptography/DefaultSaltPreprocessor.swift b/TIAuth/Sources/Cryptography/DefaultSaltPreprocessor.swift new file mode 100644 index 00000000..392ec516 --- /dev/null +++ b/TIAuth/Sources/Cryptography/DefaultSaltPreprocessor.swift @@ -0,0 +1,41 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class DefaultSaltPreprocessor: SaltPreprocessor { + public typealias DeviceIdProviderClosure = () -> String? + + private let deviceIdProvider: DeviceIdProviderClosure + + public init(deviceIdProvider: @escaping DeviceIdProviderClosure) { + self.deviceIdProvider = deviceIdProvider + } + + // MARK: - SaltPreprocessor + + public func preprocess(salt: Data) -> Data { + deviceIdProvider().map { + salt + Data($0.utf8) + } ?? salt + } +} diff --git a/TIAuth/Sources/Cryptography/DefaultTokenCipher.swift b/TIAuth/Sources/Cryptography/DefaultTokenCipher.swift new file mode 100644 index 00000000..88188617 --- /dev/null +++ b/TIAuth/Sources/Cryptography/DefaultTokenCipher.swift @@ -0,0 +1,98 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import Security + +open class DefaultTokenCipher: TokenCipher { + public var saltPreprocessor: SaltPreprocessor + public var passwordDerivator: PasswordDerivator + + public init(saltPreprocessor: SaltPreprocessor, passwordDerivator: PasswordDerivator) { + self.saltPreprocessor = saltPreprocessor + self.passwordDerivator = passwordDerivator + } + + open func createCipher(iv: Data, key: Data) -> Cipher { + AESCipher(iv: iv, key: key) + } + + open func generateIV() -> Data { + generateRandomData(count: CryptoConstants.ivLength) + } + + open func generateSalt() -> Data { + generateRandomData(count: CryptoConstants.saltLength) + } + + open func generateRandomData(count: Int) -> Data { + let randomBytes = Array(unsafeUninitializedCapacity: count) { buffer, initializedCount in + guard let startAddress = buffer.baseAddress, + SecRandomCopyBytes(kSecRandomDefault, + count, + startAddress) == errSecSuccess else { + + initializedCount = .zero + return + } + initializedCount = count + } + + return Data(randomBytes) + } + + // MARK: - TokenCipher + + open func derive(password: String, using salt: Data) -> Result { + passwordDerivator.derive(password: password, + salt: saltPreprocessor.preprocess(salt: salt)) + } + + open func encrypt(token: Data, using password: String) -> Result { + let iv = generateIV() + let salt = generateSalt() + + return derive(password: password, using: salt) + .flatMap { + createCipher(iv: iv, key: $0) + .encrypt(data: token) + .map { + StringEncryptionResult(salt: salt, iv: iv, value: $0) + } + } + } + + open func decrypt(token: StringEncryptionResult, using key: Data) -> Result { + createCipher(iv: token.iv, key: key) + .decrypt(data: token.value) + } + + open func decrypt(token: StringEncryptionResult, using password: String) -> Result { + passwordDerivator.derive(password: password, + salt: saltPreprocessor.preprocess(salt: token.salt)) + .flatMap { + createCipher(iv: token.iv, + key: $0) + .decrypt(data: token.value) + } + } +} diff --git a/TIAuth/Sources/Cryptography/PasswordDerivator.swift b/TIAuth/Sources/Cryptography/PasswordDerivator.swift new file mode 100644 index 00000000..17b97a02 --- /dev/null +++ b/TIAuth/Sources/Cryptography/PasswordDerivator.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol PasswordDerivator { + func derive(password: String, salt: Data) -> Result +} diff --git a/TIAuth/Sources/Cryptography/SaltPreprocessor.swift b/TIAuth/Sources/Cryptography/SaltPreprocessor.swift new file mode 100644 index 00000000..2985c586 --- /dev/null +++ b/TIAuth/Sources/Cryptography/SaltPreprocessor.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol SaltPreprocessor { + func preprocess(salt: Data) -> Data +} diff --git a/TIAuth/Sources/Cryptography/TokenCipher.swift b/TIAuth/Sources/Cryptography/TokenCipher.swift new file mode 100644 index 00000000..955165d9 --- /dev/null +++ b/TIAuth/Sources/Cryptography/TokenCipher.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol TokenCipher { + func derive(password: String, using salt: Data) -> Result + + func encrypt(token: Data, using password: String) -> Result + func decrypt(token: StringEncryptionResult, using key: Data) -> Result + func decrypt(token: StringEncryptionResult, using password: String) -> Result +} diff --git a/TIAuth/Sources/PinCodeValidation/ValidationRules/EqualDigitsValidationRule.swift b/TIAuth/Sources/PinCodeValidation/ValidationRules/EqualDigitsValidationRule.swift new file mode 100644 index 00000000..87ae1129 --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/ValidationRules/EqualDigitsValidationRule.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct EqualDigitsValidationRule: ValidationRule { + private let minEqualDigits: UInt + + public init(minEqualDigits: UInt) { + self.minEqualDigits = minEqualDigits + } + + // MARK: - ValidationRule + + public func validate(input: String) -> Bool { + !input.containsSequenceOfEqualCharacters(minEqualCharacters: minEqualDigits) + } +} diff --git a/TIAuth/Sources/PinCodeValidation/ValidationRules/OrderedDigitsValidationRule.swift b/TIAuth/Sources/PinCodeValidation/ValidationRules/OrderedDigitsValidationRule.swift new file mode 100644 index 00000000..8180890d --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/ValidationRules/OrderedDigitsValidationRule.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct OrderedDigitsValidationRule: ValidationRule { + private let ascendingSequence: Bool + private let minLength: UInt + + public init(ascendingSequence: Bool, minLength: UInt) { + self.ascendingSequence = ascendingSequence + self.minLength = minLength + } + + // MARK: - ValidationRule + + public func validate(input: String) -> Bool { + ascendingSequence + ? !input.containsAscendingSequence(minLength: minLength) + : !input.containsDescendingSequence(minLength: minLength) + } +} diff --git a/TIAuth/Sources/PinCodeValidation/ValidationRules/String+PinCodeValidation.swift b/TIAuth/Sources/PinCodeValidation/ValidationRules/String+PinCodeValidation.swift new file mode 100644 index 00000000..08e30e1b --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/ValidationRules/String+PinCodeValidation.swift @@ -0,0 +1,91 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +private extension Substring.SubSequence { + func recursivePairCheck(requiredMatches: UInt, + sequenceRequiredMatches: UInt, + checkClosure: (Character, Character) -> Bool) -> Bool { + + guard sequenceRequiredMatches > 0 else { + return true + } + + guard !isEmpty else { + return false + } + + let tail = dropFirst() + + guard let current = first, let next = tail.first else { + return false + } + + let matched = checkClosure(current, next) + + let reducedMatches = sequenceRequiredMatches - (matched ? 1 : 0) + + let currentSequenceMatch = matched + && tail.recursivePairCheck(requiredMatches: requiredMatches, + sequenceRequiredMatches: reducedMatches, + checkClosure: checkClosure) + + return currentSequenceMatch || tail.recursivePairCheck(requiredMatches: requiredMatches, + sequenceRequiredMatches: requiredMatches, + checkClosure: checkClosure) + } + + func recursivePairCheck(requiredMatches: UInt, checkClosure: (Character, Character) -> Bool) -> Bool { + recursivePairCheck(requiredMatches: requiredMatches, + sequenceRequiredMatches: requiredMatches, + checkClosure: checkClosure) + } + + func containsOrderedSequence(minLength: UInt, orderingClosure: ((Int, Int) -> Bool)) -> Bool { + recursivePairCheck(requiredMatches: minLength - 1) { + guard let current = $0.intValue, let next = $1.intValue else { + return false + } + + return orderingClosure(current, next) + } + } +} + +private extension Character { + var intValue: Int? { + return Int(String(self)) + } +} + +extension String { + func containsSequenceOfEqualCharacters(minEqualCharacters: UInt) -> Bool { + Substring(self).recursivePairCheck(requiredMatches: minEqualCharacters - 1) { $0 == $1 } + } + + func containsAscendingSequence(minLength: UInt) -> Bool { + Substring(self).containsOrderedSequence(minLength: minLength) { $0 + 1 == $1 } + } + + func containsDescendingSequence(minLength: UInt) -> Bool { + Substring(self).containsOrderedSequence(minLength: minLength) { $0 - 1 == $1 } + } +} diff --git a/TIAuth/Sources/PinCodeValidation/ValidationRules/ValidationRule.swift b/TIAuth/Sources/PinCodeValidation/ValidationRules/ValidationRule.swift new file mode 100644 index 00000000..16b78e5d --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/ValidationRules/ValidationRule.swift @@ -0,0 +1,25 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol ValidationRule { + func validate(input: String) -> Bool +} diff --git a/TIAuth/Sources/PinCodeValidation/Validator/DefaultInputValidator.swift b/TIAuth/Sources/PinCodeValidation/Validator/DefaultInputValidator.swift new file mode 100644 index 00000000..b138b296 --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/Validator/DefaultInputValidator.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +open class DefaultInputValidator: InputValidator { + public var rules: [Violation: ValidationRule] + + public init(rules: [Violation: ValidationRule]) { + self.rules = rules + } + + convenience init(violations: [Violation], rulesCreator: (Violation) -> ValidationRule) { + self.init(rules: .init(uniqueKeysWithValues: violations.map { ($0, rulesCreator($0)) }) ) + } + + // MARK: - InputValidator + + open func validate(input: String) -> Set { + Set(rules.filter { !$0.value.validate(input: input) }.keys) + } +} diff --git a/TIAuth/Sources/PinCodeValidation/Validator/DefaultViolation.swift b/TIAuth/Sources/PinCodeValidation/Validator/DefaultViolation.swift new file mode 100644 index 00000000..4be586ac --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/Validator/DefaultViolation.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public enum DefaultViolation: Hashable { + case orderedDigits(ascending: Bool, minLength: UInt) + case equalDigits(minEqualDigits: UInt) + + public var defaultValidationRule: ValidationRule { + switch self { + case let .orderedDigits(ascending, minLength): + return OrderedDigitsValidationRule(ascendingSequence: ascending, minLength: minLength) + case let .equalDigits(minEqualDigits): + return EqualDigitsValidationRule(minEqualDigits: minEqualDigits) + } + } +} diff --git a/TIAuth/Sources/PinCodeValidation/Validator/InputValidator.swift b/TIAuth/Sources/PinCodeValidation/Validator/InputValidator.swift new file mode 100644 index 00000000..baac926d --- /dev/null +++ b/TIAuth/Sources/PinCodeValidation/Validator/InputValidator.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol InputValidator { + associatedtype Violation: Hashable + + var rules: [Violation: ValidationRule] { get set } + + func validate(input: String) -> Set +} diff --git a/TIAuth/Sources/TokenStorage/AuthSettingsStorage.swift b/TIAuth/Sources/TokenStorage/AuthSettingsStorage.swift new file mode 100644 index 00000000..f5f626e6 --- /dev/null +++ b/TIAuth/Sources/TokenStorage/AuthSettingsStorage.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol AuthSettingsStorage: AnyObject { + /// Should be true by default (on app first run) + var shouldResetStoredAuthData: Bool { get set } +} diff --git a/TIAuth/Sources/TokenStorage/CryptoConstants.swift b/TIAuth/Sources/TokenStorage/CryptoConstants.swift new file mode 100644 index 00000000..28c5f045 --- /dev/null +++ b/TIAuth/Sources/TokenStorage/CryptoConstants.swift @@ -0,0 +1,41 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +enum CryptoConstants { + static var saltLength: Int { + 32 + } + + static var ivLength: Int { + 16 + } + + static var keyLength: Int { + 32 + } + + static var pbkdf2NumberOfIterations: Int { + 8192 + } +} diff --git a/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift b/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift new file mode 100644 index 00000000..ce72a5ea --- /dev/null +++ b/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift @@ -0,0 +1,52 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TIFoundationUtils +import Foundation + +open class DefaultAuthSettingsStorage: AuthSettingsStorage { + public enum Defaults { + public static var shouldResetAuthDataKey: String { + "shouldResetAuthData" + } + } + + private let reinstallChecker: AppReinstallChecker + + // MARK: - PinCodeSettingsStorage + + open var shouldResetStoredAuthData: Bool { + get { + reinstallChecker.isAppFirstRun + } + set { + reinstallChecker.isAppFirstRun = newValue + } + } + + public init(defaultsStorage: UserDefaults = .standard, + storageKey: String = Defaults.shouldResetAuthDataKey) { + + self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage, + storageKey: storageKey) + } +} diff --git a/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift new file mode 100644 index 00000000..70fd0800 --- /dev/null +++ b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift @@ -0,0 +1,71 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import KeychainAccess +import Foundation +import LocalAuthentication + +open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage { + open class Defaults: SingleValueAuthKeychainStorage.Defaults { + public static var encryptedTokenKeyStorageKey: String { + keychainServiceIdentifier + ".encryptedTokenKey" + } + + public static var reusableLAContext: LAContext { + let context = LAContext() + context.touchIDAuthenticationAllowableReuseDuration = LATouchIDAuthenticationMaximumAllowableReuseDuration + return context + } + } + + public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), + localAuthContext: LAContext = Defaults.reusableLAContext, + settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), + encryptedTokenKeyStorageKey: String = Defaults.encryptedTokenKeyStorageKey) { + + let getValueClosure: GetValueClosure = { keychain, storageKey in + do { + guard let value = try keychain.getData(storageKey) else { + return .failure(.valueNotFound) + } + + return .success(value) + } catch { + return .failure(.unableToExtractData(underlyingError: error)) + } + } + + let setValueClosure: SetValueClosure = { keychain, value, storageKey in + do { + return .success(try keychain.set(value, key: storageKey)) + } catch { + return .failure(.unableToWriteData(underlyingError: error)) + } + } + + super.init(keychain: keychain.authenticationContext(localAuthContext), + settingsStorage: settingsStorage, + storageKey: encryptedTokenKeyStorageKey, + getValueClosure: getValueClosure, + setValueClosure: setValueClosure) + } +} diff --git a/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift new file mode 100644 index 00000000..fc928fc3 --- /dev/null +++ b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift @@ -0,0 +1,67 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import KeychainAccess + +open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage { + open class Defaults: SingleValueAuthKeychainStorage.Defaults { + public static var encryptedTokenStorageKey: String { + keychainServiceIdentifier + ".encryptedToken" + } + } + + public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), + settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), + encryptedTokenStorageKey: String = Defaults.encryptedTokenStorageKey) { + + let getValueClosure: GetValueClosure = { keychain, storageKey in + do { + guard let value = try keychain.getData(storageKey) else { + return .failure(.valueNotFound) + } + + do { + return .success(try StringEncryptionResult(storableData: value)) + } catch { + return .failure(.unableToDecode(underlyingError: error)) + } + } catch { + return .failure(.unableToExtractData(underlyingError: error)) + } + } + + let setValueClosure: SetValueClosure = { keychain, value, storageKey in + do { + return .success(try keychain.set(value.asStorableData(), key: storageKey)) + } catch { + return .failure(.unableToWriteData(underlyingError: error)) + } + } + + super.init(keychain: keychain, + settingsStorage: settingsStorage, + storageKey: encryptedTokenStorageKey, + getValueClosure: getValueClosure, + setValueClosure: setValueClosure) + } +} diff --git a/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift b/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift new file mode 100644 index 00000000..0cb473d6 --- /dev/null +++ b/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift @@ -0,0 +1,84 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TIFoundationUtils +import KeychainAccess +import Foundation + +open class SingleValueAuthKeychainStorage: SingleValueStorage { + open class Defaults { + public static var keychainServiceIdentifier: String { + Bundle.main.bundleIdentifier ?? "ru.touchin.TIAuth" + } + } + + public typealias GetValueClosure = (Keychain, String) -> Result + public typealias SetValueClosure = (Keychain, ValueType, String) -> Result + + public let keychain: Keychain + public let settingsStorage: AuthSettingsStorage + public let storageKey: String + public let getValueClosure: GetValueClosure + public let setValueClosure: SetValueClosure + + public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), + settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), + storageKey: String, + getValueClosure: @escaping GetValueClosure, + setValueClosure: @escaping SetValueClosure) { + + self.keychain = keychain + self.settingsStorage = settingsStorage + self.storageKey = storageKey + self.getValueClosure = getValueClosure + self.setValueClosure = setValueClosure + } + + // MARK: - SingleValueStorage + + open func hasStoredValue() -> Bool { + !settingsStorage.shouldResetStoredAuthData && ((try? keychain.contains(storageKey)) ?? false) + } + + open func store(value: ValueType) -> Result { + return setValueClosure(keychain, value, storageKey) + } + + open func getValue() -> Result { + guard !settingsStorage.shouldResetStoredAuthData else { + let result: Result + + do { + try keychain.remove(storageKey) + settingsStorage.shouldResetStoredAuthData = false + + result = .failure(.valueNotFound) + } catch { + result = .failure(.unableToWriteData(underlyingError: error)) + } + + return result + } + + return getValueClosure(keychain, storageKey) + } +} diff --git a/TIAuth/Sources/TokenStorage/StringEncryptionResult.swift b/TIAuth/Sources/TokenStorage/StringEncryptionResult.swift new file mode 100644 index 00000000..f0acb447 --- /dev/null +++ b/TIAuth/Sources/TokenStorage/StringEncryptionResult.swift @@ -0,0 +1,67 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public struct StringEncryptionResult { + public struct DataRangeMismatch: Error { + public let dataLength: Int + public let valueRangeLowerBound: Int + } + + public let salt: Data + public let iv: Data + public let value: Data + + private static var saltRange: Range { + .zero.. { + saltRange.endIndex.. { + ivRange.endIndex... + } + + public init(salt: Data, iv: Data, value: Data) { + self.salt = salt + self.iv = iv + self.value = value + } + + public init(storableData: Data) throws { + guard Self.valueRange.contains(storableData.endIndex) else { + throw DataRangeMismatch(dataLength: storableData.count, + valueRangeLowerBound: Self.valueRange.lowerBound) + } + + self.init(salt: storableData[Self.saltRange], + iv: storableData[Self.ivRange], + value: storableData[Self.valueRange]) + } + + public func asStorableData() -> Data { + salt + iv + value + } +} diff --git a/TIAuth/TIAuth.podspec b/TIAuth/TIAuth.podspec index a91ae56c..75915749 100644 --- a/TIAuth/TIAuth.podspec +++ b/TIAuth/TIAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAuth' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Login, registration, confirmation and other related actions' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } @@ -13,4 +13,6 @@ Pod::Spec.new do |s| s.source_files = s.name + '/Sources/**/*' s.dependency 'TIFoundationUtils', s.version.to_s + s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'KeychainAccess', "~> 4.2" end diff --git a/TIEcommerce/TIEcommerce.podspec b/TIEcommerce/TIEcommerce.podspec index 074d2e68..e65a3a19 100644 --- a/TIEcommerce/TIEcommerce.podspec +++ b/TIEcommerce/TIEcommerce.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIEcommerce' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Cart, products, promocodes, bonuses and other related actions' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIFoundationUtils/DataStorage/AppReinstallChecker.swift b/TIFoundationUtils/DataStorage/AppReinstallChecker.swift new file mode 100644 index 00000000..bb2dcee7 --- /dev/null +++ b/TIFoundationUtils/DataStorage/AppReinstallChecker.swift @@ -0,0 +1,48 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class AppReinstallChecker { + private let defaultsStorage: UserDefaults + private let storageKey: String + + open var isAppFirstRun: Bool { + get { + guard defaultsStorage.object(forKey: storageKey) != nil else { + return true + } + + return defaultsStorage.bool(forKey: storageKey) + } + set { + defaultsStorage.set(newValue, forKey: storageKey) + } + } + + public init(defaultsStorage: UserDefaults = .standard, + storageKey: String) { + + self.defaultsStorage = defaultsStorage + self.storageKey = storageKey + } +} diff --git a/TIFoundationUtils/DataStorage/SingleValueStorage.swift b/TIFoundationUtils/DataStorage/SingleValueStorage.swift new file mode 100644 index 00000000..4c832e6a --- /dev/null +++ b/TIFoundationUtils/DataStorage/SingleValueStorage.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol SingleValueStorage { + associatedtype ValueType + associatedtype ErrorType: Error + + func hasStoredValue() -> Bool + func store(value: ValueType) -> Result + func getValue() -> Result +} diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index c86b8fe9..63f45a58 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for Foundation framework classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIGoogleMapUtils/TIGoogleMapUtils.podspec b/TIGoogleMapUtils/TIGoogleMapUtils.podspec index 1d369eb8..99e30625 100644 --- a/TIGoogleMapUtils/TIGoogleMapUtils.podspec +++ b/TIGoogleMapUtils/TIGoogleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIGoogleMapUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec index ba918808..b0a6525e 100644 --- a/TIKeychainUtils/TIKeychainUtils.podspec +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIKeychainUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for Keychain classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TILogging/TILogging.podspec b/TILogging/TILogging.podspec index 1af21667..20b9d4b6 100644 --- a/TILogging/TILogging.podspec +++ b/TILogging/TILogging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TILogging' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Logging API' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIMapUtils/TIMapUtils.podspec b/TIMapUtils/TIMapUtils.podspec index 4d4c6cf8..578a4eb3 100644 --- a/TIMapUtils/TIMapUtils.podspec +++ b/TIMapUtils/TIMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMapUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for map objects clustering and interacting.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIMoyaNetworking/TIMoyaNetworking.podspec b/TIMoyaNetworking/TIMoyaNetworking.podspec index a2772a87..867865d6 100644 --- a/TIMoyaNetworking/TIMoyaNetworking.podspec +++ b/TIMoyaNetworking/TIMoyaNetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMoyaNetworking' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Moya + Swagger network service.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift b/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift new file mode 100644 index 00000000..6926f1eb --- /dev/null +++ b/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift @@ -0,0 +1,51 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TIFoundationUtils +import Foundation + +open class DefaultFingerprintsSettingsStorage: FingerprintsSettingsStorage { + public enum Defaults { + public static var shouldResetFingerprintsKey: String { + "shouldResetFingerprints" + } + } + + private let reinstallChecker: AppReinstallChecker + + // MARK: - PinCodeSettingsStorage + + open var shouldResetFingerprints: Bool { + get { + reinstallChecker.isAppFirstRun + } + set { + reinstallChecker.isAppFirstRun = newValue + } + } + + public init(defaultsStorage: UserDefaults = .standard, + storageKey: String = Defaults.shouldResetFingerprintsKey) { + + self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage, storageKey: storageKey) + } +} diff --git a/TINetworking/TINetworking.podspec b/TINetworking/TINetworking.podspec index 0ce7cbba..8f92f73a 100644 --- a/TINetworking/TINetworking.podspec +++ b/TINetworking/TINetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworking' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Swagger-frendly networking layer helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworkingCache/TINetworkingCache.podspec b/TINetworkingCache/TINetworkingCache.podspec index b9ccdf8e..3131de4f 100644 --- a/TINetworkingCache/TINetworkingCache.podspec +++ b/TINetworkingCache/TINetworkingCache.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworkingCache' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Caching results of EndpointRequests.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIPagination/TIPagination.podspec b/TIPagination/TIPagination.podspec index b22af67b..1df31c1f 100644 --- a/TIPagination/TIPagination.podspec +++ b/TIPagination/TIPagination.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIPagination' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Generic pagination component.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TISwiftUICore/TISwiftUICore.podspec b/TISwiftUICore/TISwiftUICore.podspec index 25f57178..f9a4a99a 100644 --- a/TISwiftUICore/TISwiftUICore.podspec +++ b/TISwiftUICore/TISwiftUICore.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'TISwiftUICore' - s.version = '1.29.1' - s.summary = 'Core UI elements: protocols, views and helpers..' + s.version = '1.30.0' + s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index bc1f8df0..fc9189e6 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Bunch of useful helpers for Swift development.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index 09c9ea6e..fe06f081 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for TableKit classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITransitions/TITransitions.podspec b/TITransitions/TITransitions.podspec index 914d65c2..e27da747 100644 --- a/TITransitions/TITransitions.podspec +++ b/TITransitions/TITransitions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITransitions' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of custom transitions to present controller. ' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 90bea082..bb4eadba 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Bunch of useful protocols and views.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIKitCore/Sources/Presenter/DefaultUIViewPresenter.swift b/TIUIKitCore/Sources/Presenter/DefaultUIViewPresenter.swift index e05d46d3..36ba18b3 100644 --- a/TIUIKitCore/Sources/Presenter/DefaultUIViewPresenter.swift +++ b/TIUIKitCore/Sources/Presenter/DefaultUIViewPresenter.swift @@ -20,7 +20,7 @@ // THE SOFTWARE. // -open class DefaultUIViewPresenter: ReusableUIViewPresenter{ +open class DefaultUIViewPresenter: ReusableUIViewPresenter { public private(set) weak var view: View? public init() {} diff --git a/TIUIKitCore/Sources/Presenter/LifecyclePresenter.swift b/TIUIKitCore/Sources/Presenter/LifecyclePresenter.swift new file mode 100644 index 00000000..35fc2f0d --- /dev/null +++ b/TIUIKitCore/Sources/Presenter/LifecyclePresenter.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2022 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@MainActor +public protocol LifecyclePresenter { + func viewDidPresented() + func viewWillDestroy() +} diff --git a/TIUIKitCore/Sources/Presenter/ReusableUIViewPresenter.swift b/TIUIKitCore/Sources/Presenter/ReusableUIViewPresenter.swift index 37048990..657f96f8 100644 --- a/TIUIKitCore/Sources/Presenter/ReusableUIViewPresenter.swift +++ b/TIUIKitCore/Sources/Presenter/ReusableUIViewPresenter.swift @@ -20,6 +20,7 @@ // THE SOFTWARE. // +@MainActor public protocol ReusableUIViewPresenter: UIViewPresenter { func willReuse(view: View) } diff --git a/TIUIKitCore/Sources/Presenter/UIViewPresenters.swift b/TIUIKitCore/Sources/Presenter/UIViewPresenter.swift similarity index 99% rename from TIUIKitCore/Sources/Presenter/UIViewPresenters.swift rename to TIUIKitCore/Sources/Presenter/UIViewPresenter.swift index 5f911d44..14a96e1a 100644 --- a/TIUIKitCore/Sources/Presenter/UIViewPresenters.swift +++ b/TIUIKitCore/Sources/Presenter/UIViewPresenter.swift @@ -20,6 +20,7 @@ // THE SOFTWARE. // +@MainActor public protocol UIViewPresenter { associatedtype View: AnyObject // should be stored weakly diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index e53a8483..533371cd 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIYandexMapUtils/TIYandexMapUtils.podspec b/TIYandexMapUtils/TIYandexMapUtils.podspec index 2d6e0f71..ff7ea34a 100644 --- a/TIYandexMapUtils/TIYandexMapUtils.podspec +++ b/TIYandexMapUtils/TIYandexMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIYandexMapUtils' - s.version = '1.29.1' + s.version = '1.30.0' s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' }