TF-250 Main Repository

This commit is contained in:
Zbigniew Cisiński 2024-04-07 20:54:09 +02:00
parent d96e63b385
commit 4d46d53e90
6 changed files with 272 additions and 33 deletions

View File

@ -19,7 +19,7 @@
import Foundation
public enum PINType: CaseIterable {
public enum PINType: CaseIterable, Codable {
case digits4
case digits6
}

View File

@ -30,6 +30,8 @@ public final class LocalKeyEncryption: CommonLocalKeyEncryption {
alphabet: Keys.LocalKeyEncryption.alphabet
)
public init() {}
public func encrypt(_ str: String) -> String { encryption.encipher(str) }
public func decrypt(_ str: String) -> String { encryption.decipher(str) }
}

View File

@ -719,7 +719,7 @@
C2A4C8372A11141D00AFD67C /* TokensNextTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A4C8362A11141D00AFD67C /* TokensNextTokenView.swift */; };
C2A4C8392A1153A200AFD67C /* TokensTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A4C8382A1153A200AFD67C /* TokensTokenView.swift */; };
C2A4C83B2A11736200AFD67C /* TokensCircleProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A4C83A2A11736200AFD67C /* TokensCircleProgress.swift */; };
C2A4D3152BC097F80001587C /* MainRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A4D3142BC097F80001587C /* MainRepositoryImpl.swift */; };
C2A4D3152BC097F80001587C /* MainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A4D3142BC097F80001587C /* MainRepository.swift */; };
C2A4D32C2BC0B0C30001587C /* ServiceIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AF6A526A237EA00BDAA37 /* ServiceIcon.swift */; };
C2A4D32D2BC0B1AD0001587C /* TokenHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26D14642263A45100599101 /* TokenHandler.swift */; };
C2A4D32E2BC0B1B20001587C /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D672F625DB2BD3004BD697 /* Token.swift */; };
@ -871,6 +871,8 @@
C2B811D5284D1CFE009FD99D /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = C2B811D4284D1CFE009FD99D /* FirebaseCrashlytics */; };
C2B811D9284D1D2C009FD99D /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = C2B811D8284D1D2C009FD99D /* FirebaseRemoteConfig */; };
C2B811DB284D1D62009FD99D /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = C2B811DA284D1D62009FD99D /* FirebaseMessaging */; };
C2B86D222BC3058A00AAAC63 /* UserDefaultsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B86D212BC3058A00AAAC63 /* UserDefaultsRepository.swift */; };
C2B86D242BC3070F00AAAC63 /* AppPIN.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B86D232BC3070F00AAAC63 /* AppPIN.swift */; };
C2B90EEE281EB3CF004B8AEC /* MainRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B90EED281EB3CF004B8AEC /* MainRepositoryImpl.swift */; };
C2B90EF0281EB3F8004B8AEC /* MainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B90EEF281EB3F8004B8AEC /* MainRepository.swift */; };
C2B90EF1281EC622004B8AEC /* NetworkStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C241D445278B9E0E00D7C604 /* NetworkStack.framework */; platformFilter = ios; };
@ -2501,7 +2503,7 @@
C2A4C8362A11141D00AFD67C /* TokensNextTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensNextTokenView.swift; sourceTree = "<group>"; };
C2A4C8382A1153A200AFD67C /* TokensTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensTokenView.swift; sourceTree = "<group>"; };
C2A4C83A2A11736200AFD67C /* TokensCircleProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensCircleProgress.swift; sourceTree = "<group>"; };
C2A4D3142BC097F80001587C /* MainRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepositoryImpl.swift; sourceTree = "<group>"; };
C2A4D3142BC097F80001587C /* MainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepository.swift; sourceTree = "<group>"; };
C2A4D3372BC0B3D60001587C /* CloudState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudState.swift; sourceTree = "<group>"; };
C2A4D3482BC0B6B00001587C /* Base32Watch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Base32Watch.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C2A4D34D2BC0B9620001587C /* Base32Watch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base32Watch.h; sourceTree = "<group>"; };
@ -2621,6 +2623,8 @@
C2B5BF582946616900E5092D /* TokensNavigationFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensNavigationFlowController.swift; sourceTree = "<group>"; };
C2B6D78D260BB024005B599E /* ColorPickerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerButton.swift; sourceTree = "<group>"; };
C2B71D1C23DDEEEE007C5896 /* MainContainerPaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContainerPaging.swift; sourceTree = "<group>"; };
C2B86D212BC3058A00AAAC63 /* UserDefaultsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsRepository.swift; sourceTree = "<group>"; };
C2B86D232BC3070F00AAAC63 /* AppPIN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPIN.swift; sourceTree = "<group>"; };
C2B90EED281EB3CF004B8AEC /* MainRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepositoryImpl.swift; sourceTree = "<group>"; };
C2B90EEF281EB3F8004B8AEC /* MainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepository.swift; sourceTree = "<group>"; };
C2B90EFD281EDE80004B8AEC /* NotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPresenter.swift; sourceTree = "<group>"; };
@ -5625,7 +5629,9 @@
C274C9D52BAB8ABC008E7212 /* Preview Content */,
C2A1B3B32BB6071C00D6B923 /* PINKeyboard.swift */,
C2A1B3B72BB608C900D6B923 /* PINKeyboardLayout.swift */,
C2A4D3142BC097F80001587C /* MainRepositoryImpl.swift */,
C2A4D3142BC097F80001587C /* MainRepository.swift */,
C2B86D212BC3058A00AAAC63 /* UserDefaultsRepository.swift */,
C2B86D232BC3070F00AAAC63 /* AppPIN.swift */,
);
path = "TwoFASWatch Watch App";
sourceTree = "<group>";
@ -10009,7 +10015,7 @@
buildActionMask = 2147483647;
files = (
C2A4D3302BC0B1B80001587C /* Crypto.swift in Sources */,
C2A4D3152BC097F80001587C /* MainRepositoryImpl.swift in Sources */,
C2A4D3152BC097F80001587C /* MainRepository.swift in Sources */,
C2A4D32E2BC0B1B20001587C /* Token.swift in Sources */,
C2D2AFFA2BB8C93C00B91435 /* TokensListPresenter.swift in Sources */,
C2A1B3B82BB608C900D6B923 /* PINKeyboardLayout.swift in Sources */,
@ -10020,8 +10026,10 @@
C2A4D32C2BC0B0C30001587C /* ServiceIcon.swift in Sources */,
C2A4D3312BC0B2540001587C /* TokenGenerator.swift in Sources */,
C2A4D32D2BC0B1AD0001587C /* TokenHandler.swift in Sources */,
C2B86D222BC3058A00AAAC63 /* UserDefaultsRepository.swift in Sources */,
C2A4D3322BC0B2B30001587C /* String+.swift in Sources */,
C2D2AFF82BB8C8EE00B91435 /* TokensList.swift in Sources */,
C2B86D242BC3070F00AAAC63 /* AppPIN.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,26 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2024 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import Foundation
import CommonWatch
struct AppPIN: Codable {
let type: PINType
let value: String
}

View File

@ -28,18 +28,60 @@ import ContentWatch
// register for background push!
//optional func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any]) async -> WKBackgroundFetchResult
// save Storage on going to background!
protocol MainRepository: AnyObject {
func saveStorage()
func service(for secret: String) -> ServiceData?
func listAllServicesWithingCategories(
for phrase: String?,
sorting: SortType,
ids: [ServiceTypeID]
) -> [CategoryData]
func countServices() -> Int
var hasServices: Bool { get }
func token(
secret: Secret,
time: Date?,
digits: Digits,
period: Period,
algorithm: CommonWatch.Algorithm,
counter: Int,
tokenType: TokenType
) -> TokenValue
func markIntroductionAsShown()
func wasIntroductionShown() -> Bool
var sortType: SortType? { get }
func setSortType(_ sortType: SortType)
var pin: AppPIN? { get }
func setPIN(_ pin: AppPIN)
func registerForCloudStateChanges(_ listener: @escaping CloudStateListener, id: CloudStateListenerID)
func unregisterForCloudStageChanges(with id: CloudStateListenerID)
func enableCloudBackup()
func synchronizeBackup()
func syncDidReceiveRemoteNotification(
userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (BackgroundFetchResult) -> Void
)
final class MainRepositoryImpl {
func serviceDefinition(using serviceTypeID: ServiceTypeID) -> ServiceDefinition?
func iconTypeID(for serviceTypeID: ServiceTypeID?) -> UIImage
func listFavoriteServices() -> [ServiceData]
func addFavoriteService(_ secret: Secret)
func removeFavoriteService(_ secret: Secret)
}
final class MainRepositoryImpl: MainRepository {
var storageError: ((String) -> Void)?
let service: ServiceHandler
let protection: Protection
let storage: Storage
let categoryHandler: CategoryHandler
let sectionHandler: SectionHandler
let cloudHandler: CloudHandlerType
// let userDefaultsRepository: UserDefaultsRepository
let userDefaultsRepository: UserDefaultsRepository
let iconDatabase: IconDescriptionDatabase
let serviceDefinitionDatabase: ServiceDefinitionDatabase
let iconDescriptionDatabase: IconDescriptionDatabase
@ -57,8 +99,8 @@ final class MainRepositoryImpl {
return _shared
}
var _isLockScreenActive = false
private var favoriteServicesCache: [Secret]?
//
// let handler = SyncInstanceWatch.getCloudHandler()
// handler.registerForStateChange({ state in
@ -75,23 +117,23 @@ final class MainRepositoryImpl {
// handler.enable()
// handler.synchronize()
//}
static func create() {
let protection = Protection()
EncryptionHolder.initialize(with: protection.localKeyEncryption)
let storage = Storage(readOnly: false) { Log($0, module: .storage) }
let serviceMigration = ServiceMigrationController(storageRepository: storage.storageRepository)
serviceMigration.serviceNameTranslation = "Service"//T.Commons.service
serviceMigration.serviceNameTranslation = "Service"// TODO: Add translation T.Commons.service
SyncInstanceWatch.initialize(commonSectionHandler: storage.section, commonServiceHandler: storage.service) {
Log("Sync: \($0)")
}
SyncInstanceWatch.migrateStoreIfNeeded()
serviceMigration.migrateIfNeeded()
_ = MainRepositoryImpl(
protection: protection,
storage: storage,
@ -100,7 +142,7 @@ final class MainRepositoryImpl {
)
}
init(
private init(
protection: Protection,
storage: Storage,
cloudHandler: CloudHandlerType,
@ -118,9 +160,11 @@ final class MainRepositoryImpl {
serviceDefinitionDatabase = ServiceDefinitionDatabaseImpl()
iconDescriptionDatabase = IconDescriptionDatabaseImpl()
userDefaultsRepository = UserDefaultsRepositoryImpl()
storageRepository = storage.storageRepository
MainRepositoryImpl._shared = self
storage.addUserPresentableError { [weak self] error in
self?.storageError?(error)
}
@ -169,15 +213,35 @@ extension MainRepositoryImpl {
tokenType: tokenType
)
}
}
extension MainRepositoryImpl {
func markIntroductionAsShown() {
userDefaultsRepository.markIntroductionAsShown()
}
// func setIntroductionAsShown() {
// userDefaultsRepository.setIntroductionAsShown()
// }
//
// func introductionWasShown() -> Bool {
// userDefaultsRepository.introductionWasShown()
// }
//
func wasIntroductionShown() -> Bool {
userDefaultsRepository.wasIntroductionShown
}
var sortType: SortType? {
userDefaultsRepository.sortType
}
func setSortType(_ sortType: SortType) {
userDefaultsRepository.setSortType(sortType)
}
var pin: AppPIN? {
userDefaultsRepository.pin
}
func setPIN(_ pin: AppPIN) {
userDefaultsRepository.setPIN(pin)
}
}
extension MainRepositoryImpl {
func registerForCloudStateChanges(_ listener: @escaping CloudStateListener, id: CloudStateListenerID) {
cloudHandler.registerForStateChange({ listener($0.toCloudState) }, with: id)
cloudHandler.checkState()
@ -201,7 +265,9 @@ extension MainRepositoryImpl {
) {
SyncInstanceWatch.didReceiveRemoteNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler)
}
}
extension MainRepositoryImpl {
func serviceDefinition(using serviceTypeID: ServiceTypeID) -> ServiceDefinition? {
serviceDefinitionDatabase.service(using: serviceTypeID)
}
@ -212,12 +278,41 @@ extension MainRepositoryImpl {
}
return ServiceIcon.for(iconTypeID: serviceDef.iconTypeID)
}
// var sortType: SortType? {
// userDefaultsRepository.sortType
// }
//
// func setSortType(_ sortType: SortType) {
// userDefaultsRepository.setSortType(sortType)
// }
}
extension MainRepositoryImpl {
func listFavoriteServices() -> [ServiceData] {
initializeFavoriteServicesCache()
guard let favoriteServicesCache else { return [] }
return favoriteServicesCache.compactMap({ storageRepository.findService(for: $0) })
}
func addFavoriteService(_ secret: Secret) {
initializeFavoriteServicesCache()
guard favoriteServicesCache?.first(where: { $0 == secret }) == nil else { return }
favoriteServicesCache?.append(secret)
saveFavoriteServicesCache()
}
func removeFavoriteService(_ secret: Secret) {
initializeFavoriteServicesCache()
favoriteServicesCache?.removeAll(where: { $0 == secret })
saveFavoriteServicesCache()
}
private func initializeFavoriteServicesCache() {
if favoriteServicesCache != nil {
return
}
let value = userDefaultsRepository.favoriteServices() ?? []
favoriteServicesCache = Array(Set(value))
}
private func saveFavoriteServicesCache() {
guard let favoriteServicesCache else {
Log("Can't get Favorite Services for saving")
return
}
userDefaultsRepository.setFavoriteServices(favoriteServicesCache)
}
}

View File

@ -0,0 +1,108 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2024 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import Foundation
import CommonWatch
import ProtectionWatch
protocol UserDefaultsRepository: AnyObject {
var pin: AppPIN? { get }
func setPIN(_ pin: AppPIN)
var sortType: SortType? { get }
func setSortType(_ sortType: SortType)
var wasIntroductionShown: Bool { get }
func markIntroductionAsShown()
func setFavoriteServices(_ services: [Secret])
func favoriteServices() -> [Secret]?
}
final class UserDefaultsRepositoryImpl: UserDefaultsRepository {
private enum Keys: String, CaseIterable {
case sortType
case introductionShown
case pin
case favoriteServices
}
private let userDefaults = UserDefaults()
private let localEncryption = LocalKeyEncryption()
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
var pin: AppPIN? {
guard
let data = userDefaults.object(forKey: Keys.pin.rawValue) as? Data,
let object = try? decoder.decode(AppPIN.self, from: data)
else { return nil }
let decrypted = AppPIN(type: object.type, value: localEncryption.decrypt(object.value))
return decrypted
}
func setPIN(_ pin: AppPIN) {
do {
let encrypted = AppPIN(type: pin.type, value: localEncryption.encrypt(pin.value))
let encodedNode = try encoder.encode(encrypted)
userDefaults.set(encodedNode, forKey: Keys.pin.rawValue)
userDefaults.synchronize()
} catch {
Log("Can't save App PIN! Error: \(error)", severity: .error)
}
}
var sortType: SortType? {
guard let value = userDefaults.string(forKey: Keys.sortType.rawValue) else { return nil }
return SortType(rawValue: value)
}
func setSortType(_ sortType: SortType) {
userDefaults.set(sortType.rawValue, forKey: Keys.sortType.rawValue)
userDefaults.synchronize()
}
var wasIntroductionShown: Bool {
userDefaults.bool(forKey: Keys.introductionShown.rawValue)
}
func markIntroductionAsShown() {
userDefaults.set(true, forKey: Keys.introductionShown.rawValue)
userDefaults.synchronize()
}
func setFavoriteServices(_ services: [Secret]) {
do {
let encodedNode = try encoder.encode(services)
userDefaults.set(encodedNode, forKey: Keys.favoriteServices.rawValue)
userDefaults.synchronize()
} catch {
Log("Can't save Favorite Services! Error: \(error)", severity: .error)
}
}
func favoriteServices() -> [Secret]? {
guard
let data = userDefaults.object(forKey: Keys.favoriteServices.rawValue) as? Data,
let list = try? decoder.decode([Secret].self, from: data)
else { return nil }
return list
}
}