// // UnlockViewModel.swift // MyPass // import Foundation import Combine import MyPassCore @MainActor final class UnlockViewModel: ObservableObject { @Published var password: String = "" @Published var errorMessage: String? @Published var isUnlocking: Bool = false @Published var showFilePicker: Bool = false private let session: VaultSession private let bookmarkService: FileBookmarkService private let keychainStore: KeychainStore private let biometricService: BiometricAuthService private let keychainAccount = "masterPassword" init( session: VaultSession, bookmarkService: FileBookmarkService = .init(), keychainStore: KeychainStore = .init(accessGroup: "org.antiloop222.mypass"), biometricService: BiometricAuthService = .init() ) { self.session = session self.bookmarkService = bookmarkService self.keychainStore = keychainStore self.biometricService = biometricService } var canUseBiometrics: Bool { biometricService.isAvailable && keychainStore.exists(for: keychainAccount) } var hasVault: Bool { bookmarkService.hasBookmark } func unlockWithBiometrics() { Task { isUnlocking = true errorMessage = nil do { try await biometricService.authenticate(reason: "Unlock MyPass") let storedPassword = try keychainStore.load(for: keychainAccount) let url = try bookmarkService.resolveURL() defer { bookmarkService.stopAccess(url: url) } try session.unlock(url: url, password: storedPassword) } catch { errorMessage = error.localizedDescription } isUnlocking = false } } func unlockWithPassword() { Task { isUnlocking = true errorMessage = nil do { let url = try bookmarkService.resolveURL() defer { bookmarkService.stopAccess(url: url) } try session.unlock(url: url, password: password) try keychainStore.save(password: password, for: keychainAccount) password = "" } catch KDBXError.invalidPassword { errorMessage = "Incorrect password." } catch { errorMessage = error.localizedDescription } isUnlocking = false } } func openFile(url: URL) { do { try bookmarkService.save(url: url) // Don't attempt to unlock yet -- picking a file only saves the bookmark. // The view now shows the password field for the user to enter their password. } catch { errorMessage = error.localizedDescription } } }