diff --git a/Chronos.xcodeproj/project.pbxproj b/Chronos.xcodeproj/project.pbxproj index e4acaf4..08b3532 100644 --- a/Chronos.xcodeproj/project.pbxproj +++ b/Chronos.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 6B3C7A382BE764E70043FEBD /* StorageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3C7A372BE764E70043FEBD /* StorageSetupView.swift */; }; 6B3C7A3D2BE9E0600043FEBD /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 6B3C7A3C2BE9E0600043FEBD /* Logging */; }; 6B3F92A92C1B45AA004125A8 /* Vault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3F92A82C1B45AA004125A8 /* Vault.swift */; }; + 6B3F92AB2C1C7987004125A8 /* VaultService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3F92AA2C1C7987004125A8 /* VaultService.swift */; }; 6B4B48F32BD7BB3C007D357D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4B48F22BD7BB3C007D357D /* Token.swift */; }; 6B5E41CE2BD790F80045DBC6 /* EncryptedToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */; }; 6B66D5E72B526315006DB79D /* AddTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B66D5E62B526315006DB79D /* AddTokenView.swift */; }; @@ -75,6 +76,7 @@ 6B3BB0E12B4ED19300DCEF0B /* TokensTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensTab.swift; sourceTree = ""; }; 6B3C7A372BE764E70043FEBD /* StorageSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSetupView.swift; sourceTree = ""; }; 6B3F92A82C1B45AA004125A8 /* Vault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vault.swift; sourceTree = ""; }; + 6B3F92AA2C1C7987004125A8 /* VaultService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultService.swift; sourceTree = ""; }; 6B4B48F22BD7BB3C007D357D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 6B5E41CD2BD790F80045DBC6 /* EncryptedToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedToken.swift; sourceTree = ""; }; 6B66D5E62B526315006DB79D /* AddTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTokenView.swift; sourceTree = ""; }; @@ -245,6 +247,7 @@ 6BD6D2002C11FEB4004512BF /* OTPService.swift */, 6B9D74642C14ADDC008E6582 /* StateService.swift */, 6B12B09F2C19DB7800E9ED2D /* ExportService.swift */, + 6B3F92AA2C1C7987004125A8 /* VaultService.swift */, ); path = Services; sourceTree = ""; @@ -413,6 +416,7 @@ files = ( 6B3BB0B32B4EA87C00DCEF0B /* ContentView.swift in Sources */, 6B3BB0B12B4EA87C00DCEF0B /* ChronosApp.swift in Sources */, + 6B3F92AB2C1C7987004125A8 /* VaultService.swift in Sources */, 6B12B0A22C19F1E600E9ED2D /* ExportVault.swift in Sources */, 6B7383E52B9C4230008E8867 /* Container.swift in Sources */, 6B3C7A382BE764E70043FEBD /* StorageSetupView.swift in Sources */, diff --git a/Chronos/App/Onboarding/PasswordSetupView.swift b/Chronos/App/Onboarding/PasswordSetupView.swift index bd7aa6a..36f4b69 100644 --- a/Chronos/App/Onboarding/PasswordSetupView.swift +++ b/Chronos/App/Onboarding/PasswordSetupView.swift @@ -18,8 +18,6 @@ struct PasswordSetupView: View { @FocusState private var focusedField: FocusedField? - @AppStorage(StateEnum.VAULT.rawValue) var stateVaultId: String? - let cryptoService = Container.shared.cryptoService() let stateService = Container.shared.stateService() diff --git a/Chronos/Services/Container.swift b/Chronos/Services/Container.swift index df42b03..bb6eb3d 100644 --- a/Chronos/Services/Container.swift +++ b/Chronos/Services/Container.swift @@ -10,6 +10,10 @@ extension Container { .singleton } + var vaultService: Factory { + Factory(self) { VaultService() } + } + var cryptoService: Factory { Factory(self) { CryptoService() } } diff --git a/Chronos/Services/CryptoService.swift b/Chronos/Services/CryptoService.swift index 7f3482f..df9a24b 100644 --- a/Chronos/Services/CryptoService.swift +++ b/Chronos/Services/CryptoService.swift @@ -13,10 +13,9 @@ public class CryptoService { private let logger = Logger(label: "CryptoService") private let stateService = Container.shared.stateService() + private let vaultService = Container.shared.vaultService() private let swiftDataService = Container.shared.swiftDataService() - private let defaults = UserDefaults.standard - // scrypt paramaters - n: 2^17, r: 8, p: 1 private let kdfParams = KdfParams(type: 0, n: 1 << 17, r: 8, p: 1) @@ -34,7 +33,7 @@ public class CryptoService { let keyParams = KeyParams(iv: iv, tag: encrypt.authenticationTag) - let newPasswordCrypto = ChronosCrypto(vault: stateService.vault!, key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) + let newPasswordCrypto = ChronosCrypto(vault: vaultService.getVault()!, key: encrypt.cipherText, keyParams: keyParams, passwordParams: passwordParams, kdfParams: kdfParams) let context = ModelContext(swiftDataService.getModelContainer()) @@ -46,16 +45,7 @@ public class CryptoService { } func unwrapMasterKeyWithUserPassword(password: [UInt8], isRestore: Bool = false) async -> Bool { - let context = ModelContext(swiftDataService.getModelContainer(isRestore: isRestore)) - - // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. - guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { - logger.error("No vaults found") - return false - } - - guard let vault = vaultArr.first else { - logger.error("Empty vaultArr") + guard let vault = vaultService.getFirstVault() else { return false } @@ -88,6 +78,8 @@ public class CryptoService { if decrypt.success { stateService.masterKey = SecureBytes(bytes: decrypt.plainText) + stateService.setVaultId(vaultId: vault.vaultId!) + decrypt.plainText.removeAll() return true } else { @@ -110,7 +102,7 @@ extension CryptoService { let tokenJson = try JSONEncoder().encode(token) let encrypt = try AEADXChaCha20Poly1305.encrypt(Array(tokenJson), key: Array(stateService.masterKey), iv: iv, authenticationHeader: header) - return EncryptedToken(vault: stateService.vault!, encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) + return EncryptedToken(vault: vaultService.getVault()!, encryptedTokenCiper: encrypt.cipherText, iv: iv, authenticationTag: encrypt.authenticationTag, createdAt: Date()) } catch { fatalError(error.localizedDescription) } diff --git a/Chronos/Services/StateService.swift b/Chronos/Services/StateService.swift index 84d1534..3b537be 100644 --- a/Chronos/Services/StateService.swift +++ b/Chronos/Services/StateService.swift @@ -2,7 +2,7 @@ import Factory import Foundation enum StateEnum: String { - case VAULT + case VAULT_ID case ICLOUD_BACKUP_ENABLED case BIOMETRICS_AUTH_ENABLED @@ -16,8 +16,21 @@ public class StateService { var masterKey: SecureBytes = .init(bytes: []) var vault: Vault? - + + func setVaultId(vaultId: UUID) { + defaults.setValue(vaultId.uuidString, forKey: StateEnum.VAULT_ID.rawValue) + } + + func getVaultId() -> UUID? { + guard let vaultIdStr = defaults.string(forKey: StateEnum.VAULT_ID.rawValue) else { + return nil + } + + return UUID(uuidString: vaultIdStr) + } + func resetAllStates() { + defaults.setValue(false, forKey: StateEnum.VAULT_ID.rawValue) defaults.setValue(false, forKey: StateEnum.ICLOUD_BACKUP_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.BIOMETRICS_AUTH_ENABLED.rawValue) defaults.setValue(false, forKey: StateEnum.ONBOARDING_COMPLETED.rawValue) diff --git a/Chronos/Services/VaultService.swift b/Chronos/Services/VaultService.swift new file mode 100644 index 0000000..2075d56 --- /dev/null +++ b/Chronos/Services/VaultService.swift @@ -0,0 +1,48 @@ +import Factory +import Foundation +import SwiftData +import Logging + +public class VaultService { + private let logger = Logger(label: "VaultService") + + private let stateService = Container.shared.stateService() + private let swiftDataService = Container.shared.swiftDataService() + + // TODO(joeldavidw): Selects first vault for now. Selection page should be shown if there are more than one vault. + func getFirstVault() -> Vault? { + let context = ModelContext(swiftDataService.getCloudModelContainer()) + + guard let vaultArr = try? context.fetch(FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)])) else { + logger.error("No vaults found") + return nil + } + + guard let vault = vaultArr.first else { + logger.error("Empty vaultArr") + return nil + } + + stateService.setVaultId(vaultId: vault.vaultId!) + + return vault + } + + func getVault() -> Vault? { + guard let vaultId: UUID = stateService.getVaultId() else { + logger.error("vaultId not found in AppStorage") + return nil + } + + let context = ModelContext(swiftDataService.getModelContainer()) + + let predicate = #Predicate { $0.vaultId == vaultId } + + guard let vaultArr = try? context.fetch(FetchDescriptor(predicate: predicate)) else { + logger.error("No vaults found") + return nil + } + + return vaultArr.first + } +}