2026-09-19 15:58:15 +02:00
|
|
|
//
|
|
|
|
|
// 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)
|
2026-09-19 16:14:10 +02:00
|
|
|
// 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.
|
2026-09-19 15:58:15 +02:00
|
|
|
} catch {
|
|
|
|
|
errorMessage = error.localizedDescription
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|