- Custom copper AccentColor and warm VaultBackground color assets (light/dark), applied to both the main app and the AutoFill extension (which needed its own copy of the asset catalog plus ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME and an explicit .tint() modifier, since extensions don't pick up the app-wide accent color the same way a full SwiftUI App/Scene does). - GroupFilterView no longer uses DisclosureGroup for subgroups -- the whole tree renders always-expanded (indented per depth) so active filters are visible at a glance instead of requiring tap-to-expand. - Added a placeholder app icon (padlock glyph, light/dark/tinted variants plus macOS sizes) -- previously the icon slot existed but had no actual images, which blocks App Store/TestFlight archive validation. - Added ITSAppUsesNonExemptEncryption = NO (standard encryption only, via KeePassKit) to skip the export-compliance prompt on each upload. - Renamed the app from MyPass to KeeVault throughout: Xcode project/targets/ schemes, the MyPassCore package (now KeeVaultCore) and every import site, folder and file names, bundle identifiers (org.antiloop222.keevault) and their App Group/Keychain-group entitlements, and remaining UI/string references -- MyPass was already taken as an App Store app name.
86 lines
2.7 KiB
Swift
86 lines
2.7 KiB
Swift
//
|
|
// UnlockViewModel.swift
|
|
// KeeVault
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
import KeeVaultCore
|
|
|
|
@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: "F2KB6W8N4R.org.antiloop222.keevault"),
|
|
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 KeeVault")
|
|
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 {
|
|
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
|
|
}
|
|
}
|
|
}
|