Compare commits

..
10 Commits
Author SHA1 Message Date
kriss b6d113edcd feat: switch accent to Plum, finalize vault-door app icon, group filter Clear button
- Replace Copper accent with Plum (light #6B4577 / dark #A67FB0) across the main
  app and AutoFill extension.
- Replace the padlock app icon with a vault-door design (ring, hub, locking
  handle) in Plum, plus all macOS sizes.
- Add an AppIconImage asset (separate from the AppIcon icon-set, which SwiftUI
  can't reference directly at runtime) and show it on the unlock screen instead
  of a generic SF Symbol.
- GroupFilterView: move "Clear Filter" out of the list and into a leading
  toolbar button ("Clear", disabled when no filter is active), mirroring the
  trailing "Done" button.
2026-09-20 18:25:41 +02:00
kriss 8bba64076f feat: copper accent theme, always-expanded group filter, app icon; rename MyPass to KeeVault
- 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.
2026-09-20 15:05:19 +02:00
kriss a8d612f745 fix: resolve real-device blockers for vault open, biometrics, and AutoFill
- FileBookmarkService: hold a security scope while creating the bookmark,
  fixing "file doesn't exist" on iCloud Drive-backed vaults (NSCocoaErrorDomain
  Code=4), which only surfaced on a real device since the Simulator strips
  entitlements needed to reproduce this.
- Fix Keychain access group missing the Team ID prefix, which silently broke
  saving the master password so Face ID was never offered after backgrounding.
- Add the autofill-credential-provider entitlement to the main app target
  (previously only on the extension) and declare ProvidesPasswords in the
  extension's Info.plist, so MyPass now registers as a selectable AutoFill
  Passwords provider.
- CredentialMatcher: fall back to a scheme-prefixed re-parse when extracting a
  host, since KDBX entries commonly store bare domains (e.g. "allocine.fr")
  that URL(string:).host can't parse without an authority component. Fixes
  AutoFill suggestions being unranked/wrong for such entries.
2026-09-20 12:39:45 +02:00
krissandClaude Sonnet 5 5a59867d48 fix: align AutoFill keychain-access-group with main app, lowercase bundle IDs
Root-caused the "MyPass doesn't appear in Settings > AutoFill Passwords"
issue: entitlements embed correctly on a real device build but are
silently stripped to empty on every Simulator build in this
environment (confirmed identically via CLI xcodebuild and Xcode's own
GUI build/run, before and after multiple rounds of removing/re-adding
capabilities in Signing & Capabilities). This is a Simulator/SDK-level
limitation in this environment, not a project misconfiguration --
verified by comparing against a real iphoneos device build, which
embeds the full App Group / Keychain Sharing / AutoFill Credential
Provider entitlements correctly.

While diagnosing, found and fixed a real, separate bug: AutoFill's
Keychain Sharing group had been set (via Xcode's capability re-add
flow, which defaults to the target's own bundle ID) to
org.antiloop222.mypass.autofill, while the main app uses
org.antiloop222.mypass -- a mismatch that would have prevented the
extension from ever reading the shared master password from Keychain,
even on a real device where entitlements embed correctly. Now aligned
to org.antiloop222.mypass on both targets, matching what
KeychainStore's hardcoded accessGroup expects in both AutoFill/
CredentialProviderViewController.swift and MyPass/ViewModels/
UnlockViewModel.swift.

Also includes the user's own Signing & Capabilities re-add (repopulated
App Group selections) and a bundle-identifier lowercase normalization
(org.antiloop222.MyPass -> org.antiloop222.mypass, matching the
already-lowercase App Group/Keychain group strings) made via Xcode's
GUI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 22:03:34 +02:00
krissandClaude Sonnet 5 dfb728e4ab chore: explicitly require entitlements for MyPass and AutoFill targets
Sets ENTITLEMENTS_REQUIRED = YES for both targets' build configurations.
Found while investigating why Settings > AutoFill & Passwords doesn't
list MyPass: Xcode's automatic-signing build step (ProcessProductPackaging)
silently produces an EMPTY entitlements blob for both the AutoFill
extension and the main app on this environment's Simulator builds,
despite AutoFill.entitlements/MyPass.entitlements being valid and
correctly referenced (verified with plutil, and confirmed manually
invoking codesign with the same file embeds the entitlements
correctly). This setting alone doesn't fix the underlying issue --
still investigating -- but is a correct, harmless hardening on its own
(surfaces an error if entitlements are ever genuinely missing, instead
of silently signing without them).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 19:49:45 +02:00
krissandClaude Sonnet 5 5281ce4895 fix: address AutoFill extension code review findings
- Remove dead provideCredentialWithoutUserInteraction override and
  unlockSilently() helper (nothing registers credential identities,
  so it could never run, and it was a footgun for future inline
  QuickType work).
- Surface non-cancellation biometric unlock failures into
  errorMessage instead of swallowing them, matching
  UnlockViewModel.unlockWithBiometrics().
- Forward VaultSession.objectWillChange into ExtensionViewModel via
  Combine so the lock/unlock UI transition no longer depends on an
  accidental isUnlocking side effect, matching VaultViewModel's
  pattern.
- Show the "Open MyPass" deep-link escape hatch whenever unlock
  fails (errorMessage set), not only when there's no bookmark yet,
  per the extension constraints spec.
- Add INFOPLIST_KEY_NSFaceIDUsageDescription to the MyPass and
  AutoFill targets' Debug/Release build configs.

Also folds in pre-existing alphabetical reordering of two
PBXBuildFile/PBXFileReference entries in project.pbxproj from an
earlier task, since this same file is already being touched here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 19:24:35 +02:00
krissandClaude Haiku 4.5 e52cf3f39b feat: add CredentialListView for AutoFill extension
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 19:15:16 +02:00
krissandClaude Sonnet 5 a70ea40acd feat: add ExtensionRootView and ExtensionViewModel
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 19:12:49 +02:00
krissandClaude Sonnet 5 3ee729070a feat: add CredentialProviderViewController skeleton, strip unused storyboard UI
Replace the Xcode boilerplate in AutoFill/CredentialProviderViewController.swift
with real logic: prepareCredentialList(for:) builds ExtensionRootView (added in
Task 19) inside a UIHostingController, provideCredentialWithoutUserInteraction(for:)
silently unlocks via the shared Keychain token and completes/cancels the request.
Strip the storyboard's now-unused static nav-bar/button/actions, keeping the same
customClass so the system still instantiates this exact class.

Also add AutoFill target membership for MyPass/Services/FileBookmarkService.swift
and BiometricAuthService.swift (app-target files, referenced directly by the
extension per the plan) via explicit PBXFileReference/PBXBuildFile entries, since
Xcode's synchronized-group file system only auto-includes them in the MyPass
target by default.

Expected build state: exactly one error, "cannot find 'ExtensionRootView' in
scope" (reported once per simulator architecture), until Task 19 adds that type.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 19:08:54 +02:00
krissandClaude Sonnet 5 da5ad145b0 fix: make search bar always visible instead of toggle-behind-icon
Removed the magnifying-glass toolbar button and the isSearching state
it drove -- .searchable() now shows the search field unconditionally,
which is simpler and was requested after hands-on use showed the extra
tap added no value. Updated the spec to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
2026-09-19 19:08:36 +02:00
80 changed files with 750 additions and 272 deletions
@@ -0,0 +1,38 @@
{
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.420",
"green" : "0.271",
"blue" : "0.467",
"alpha" : "1.000"
}
}
},
{
"idiom" : "universal",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.651",
"green" : "0.498",
"blue" : "0.690",
"alpha" : "1.000"
}
}
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.980",
"green" : "0.957",
"blue" : "0.925",
"alpha" : "1.000"
}
}
},
{
"idiom" : "universal",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.141",
"green" : "0.125",
"blue" : "0.094",
"alpha" : "1.000"
}
}
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+4 -4
View File
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.authentication-services.autofill-credential-provider</key> <key>com.apple.developer.authentication-services.autofill-credential-provider</key>
<true/> <true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.org.antiloop222.mypass</string> <string>group.org.antiloop222.keevault</string>
</array> </array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)org.antiloop222.mypass</string> <string>$(AppIdentifierPrefix)org.antiloop222.keevault</string>
</array> </array>
</dict> </dict>
</plist> </plist>
@@ -17,36 +17,7 @@
<view key="view" contentMode="scaleToFill" id="BuU-Ak-iZz"> <view key="view" contentMode="scaleToFill" id="BuU-Ak-iZz">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3wq-kG-lGu">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<items>
<navigationItem id="cbj-pk-SYj">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="bEZ-MG-jDy">
<connections>
<action selector="cancel:" destination="Xki-Si-B7m" id="6ap-3Q-iEX"/>
</connections>
</barButtonItem>
</navigationItem>
</items>
</navigationBar>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a7v-ug-QzG">
<rect key="frame" x="87.5" y="327" width="199" height="33"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Return Example Password"/>
<connections>
<action selector="passwordSelected:" destination="Xki-Si-B7m" eventType="touchUpInside" id="ODd-lr-mud"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Ky8-vK-JVj" firstAttribute="top" secondItem="3wq-kG-lGu" secondAttribute="top" id="BIN-jb-uNd"/>
<constraint firstItem="3wq-kG-lGu" firstAttribute="width" secondItem="BuU-Ak-iZz" secondAttribute="width" id="UkD-v4-BcH"/>
<constraint firstItem="a7v-ug-QzG" firstAttribute="centerY" secondItem="Ky8-vK-JVj" secondAttribute="centerY" id="fAC-0v-NFE"/>
<constraint firstItem="a7v-ug-QzG" firstAttribute="centerX" secondItem="3wq-kG-lGu" secondAttribute="centerX" id="io1-ZS-gwn"/>
<constraint firstItem="3wq-kG-lGu" firstAttribute="centerX" secondItem="BuU-Ak-iZz" secondAttribute="centerX" id="rtV-5c-0bl"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Ky8-vK-JVj"/> <viewLayoutGuide key="safeArea" id="Ky8-vK-JVj"/>
</view> </view>
</viewController> </viewController>
+34 -42
View File
@@ -6,53 +6,45 @@
// //
import AuthenticationServices import AuthenticationServices
import SwiftUI
import KeeVaultCore
class CredentialProviderViewController: ASCredentialProviderViewController { final class CredentialProviderViewController: ASCredentialProviderViewController {
/* private let session = VaultSession()
Prepare your UI to list available credentials for the user to choose from. The items in private let bookmarkService = FileBookmarkService()
'serviceIdentifiers' describe the service the user is logging in to, so your extension can private let keychainStore = KeychainStore(accessGroup: "F2KB6W8N4R.org.antiloop222.keevault")
prioritize the most relevant credentials in the list. private let biometricService = BiometricAuthService()
*/
// Called when the user selects KeeVault from the QuickType bar.
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
let ids = serviceIdentifiers.map(\.identifier)
showUI(serviceIdentifiers: ids)
} }
/* private func showUI(serviceIdentifiers: [String]) {
Implement this method if your extension supports showing credentials in the QuickType bar. let rootView = ExtensionRootView(
When the user selects a credential from your app, this method will be called with the session: session,
ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. serviceIdentifiers: serviceIdentifiers,
Provide the password by completing the extension request with the associated ASPasswordCredential. bookmarkService: bookmarkService,
If using the credential would require showing custom UI for authenticating the user, cancel keychainStore: keychainStore,
the request with error code ASExtensionError.userInteractionRequired. biometricService: biometricService,
onSelect: { [weak self] (entry: Entry) in
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { let credential = ASPasswordCredential(
let databaseIsUnlocked = true user: entry.username,
if (databaseIsUnlocked) { password: entry.password.reveal()
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") )
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) self?.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil)
} else { },
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) onCancel: { [weak self] in
self?.extensionContext.cancelRequest(withError: ASExtensionError(.userCanceled))
} }
)
let host = UIHostingController(rootView: rootView)
addChild(host)
view.addSubview(host.view)
host.view.frame = view.bounds
host.view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
host.didMove(toParent: self)
} }
*/
/*
Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with
ASExtensionError.userInteractionRequired. In this case, the system may present your extension's
UI and call this method. Show appropriate UI for authenticating the user then provide the password
by completing the extension request with the associated ASPasswordCredential.
override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
}
*/
@IBAction func cancel(_ sender: AnyObject?) {
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
}
@IBAction func passwordSelected(_ sender: AnyObject?) {
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
}
} }
+8
View File
@@ -4,6 +4,14 @@
<dict> <dict>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionAttributes</key>
<dict>
<key>ASCredentialProviderExtensionCapabilities</key>
<dict>
<key>ProvidesPasswords</key>
<true/>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key> <key>NSExtensionMainStoryboard</key>
<string>MainInterface</string> <string>MainInterface</string>
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
+64
View File
@@ -0,0 +1,64 @@
// AutoFill/Views/CredentialListView.swift
import SwiftUI
import KeeVaultCore
struct CredentialListView: View {
@ObservedObject var vm: ExtensionViewModel
@State private var searchQuery: String = ""
private var displayedSuggested: [Entry] {
searchQuery.isEmpty ? vm.suggestedEntries : []
}
private var displayedAll: [Entry] {
let entries = searchQuery.isEmpty ? vm.allEntries : vm.session.allEntries()
guard !searchQuery.isEmpty else { return entries }
let q = searchQuery.lowercased()
return entries.filter {
$0.title.lowercased().contains(q)
|| $0.username.lowercased().contains(q)
|| $0.url.lowercased().contains(q)
}
}
var body: some View {
List {
if !displayedSuggested.isEmpty {
Section("Suggested") {
ForEach(displayedSuggested) { entry in
CredentialRow(entry: entry) { vm.select(entry) }
}
}
}
Section(displayedSuggested.isEmpty ? "" : "All Entries") {
if displayedAll.isEmpty {
Text("No entries found").foregroundStyle(.secondary)
} else {
ForEach(displayedAll) { entry in
CredentialRow(entry: entry) { vm.select(entry) }
}
}
}
}
.scrollContentBackground(.hidden)
.background(Color("VaultBackground"))
.searchable(text: $searchQuery, prompt: "Search…")
}
}
private struct CredentialRow: View {
let entry: Entry
let onSelect: () -> Void
var body: some View {
Button(action: onSelect) {
VStack(alignment: .leading, spacing: 2) {
Text(entry.title).font(.body).foregroundStyle(.primary)
Text(entry.username).font(.caption).foregroundStyle(.secondary)
if !entry.url.isEmpty {
Text(entry.url).font(.caption2).foregroundStyle(.tertiary)
}
}
}
}
}
+174
View File
@@ -0,0 +1,174 @@
// AutoFill/Views/ExtensionUnlockView.swift
import SwiftUI
import Combine
import KeeVaultCore
/// Root view for the extension -- shows unlock screen or credential list depending on state.
struct ExtensionRootView: View {
@StateObject private var vm: ExtensionViewModel
init(
session: VaultSession,
serviceIdentifiers: [String],
bookmarkService: FileBookmarkService,
keychainStore: KeychainStore,
biometricService: BiometricAuthService,
onSelect: @escaping (Entry) -> Void,
onCancel: @escaping () -> Void
) {
_vm = StateObject(wrappedValue: ExtensionViewModel(
session: session,
serviceIdentifiers: serviceIdentifiers,
bookmarkService: bookmarkService,
keychainStore: keychainStore,
biometricService: biometricService,
onSelect: onSelect,
onCancel: onCancel
))
}
var body: some View {
NavigationStack {
SwiftUI.Group {
if vm.isLocked {
extensionUnlockView
} else {
CredentialListView(vm: vm)
}
}
.background(Color("VaultBackground"))
.navigationTitle("KeeVault")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", action: vm.cancel)
}
}
}
.tint(Color("AccentColor"))
.task { await vm.tryBiometricUnlock() }
}
private var extensionUnlockView: some View {
VStack(spacing: 24) {
Spacer()
Image(systemName: "lock.shield.fill").font(.system(size: 48)).foregroundStyle(.tint)
Text("Vault Locked").font(.headline)
if vm.canUseBiometrics {
Button(action: { Task { await vm.tryBiometricUnlock() } }) {
Label("Use Face ID / Touch ID", systemImage: "faceid")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
}
SecureField("Master Password", text: $vm.password)
.textFieldStyle(.roundedBorder)
.onSubmit { Task { await vm.unlockWithPassword() } }
Button("Unlock") { Task { await vm.unlockWithPassword() } }
.buttonStyle(.bordered)
.disabled(vm.password.isEmpty || vm.isUnlocking)
if let msg = vm.errorMessage {
Text(msg).foregroundStyle(.red).font(.caption).multilineTextAlignment(.center)
}
if !vm.hasVault || vm.errorMessage != nil {
Link(destination: URL(string: "keevault://unlock")!) {
Label("Open KeeVault to set up vault", systemImage: "arrow.up.right")
.font(.caption)
}
}
Spacer()
}
.padding(24)
}
}
@MainActor
final class ExtensionViewModel: ObservableObject {
@Published var password: String = ""
@Published var errorMessage: String?
@Published var isUnlocking: Bool = false
let session: VaultSession
let serviceIdentifiers: [String]
private let bookmarkService: FileBookmarkService
private let keychainStore: KeychainStore
private let biometricService: BiometricAuthService
private let onSelect: (Entry) -> Void
private let onCancel: () -> Void
private var sessionCancellable: AnyCancellable?
init(
session: VaultSession,
serviceIdentifiers: [String],
bookmarkService: FileBookmarkService,
keychainStore: KeychainStore,
biometricService: BiometricAuthService,
onSelect: @escaping (Entry) -> Void,
onCancel: @escaping () -> Void
) {
self.session = session
self.serviceIdentifiers = serviceIdentifiers
self.bookmarkService = bookmarkService
self.keychainStore = keychainStore
self.biometricService = biometricService
self.onSelect = onSelect
self.onCancel = onCancel
sessionCancellable = session.objectWillChange.sink { [weak self] in
self?.objectWillChange.send()
}
}
var isLocked: Bool { session.isLocked }
var hasVault: Bool { bookmarkService.hasBookmark }
var canUseBiometrics: Bool { biometricService.isAvailable && keychainStore.exists(for: "masterPassword") }
var suggestedEntries: [Entry] {
CredentialMatcher.filter(entries: session.allEntries(), for: serviceIdentifiers).suggested
}
var allEntries: [Entry] {
CredentialMatcher.filter(entries: session.allEntries(), for: serviceIdentifiers).all
}
func select(_ entry: Entry) { onSelect(entry) }
func cancel() { onCancel() }
func tryBiometricUnlock() async {
guard canUseBiometrics, session.isLocked else { return }
isUnlocking = true
do {
try await biometricService.authenticate(reason: "Unlock KeeVault")
try performUnlockFromKeychain()
} catch {
errorMessage = error.localizedDescription
}
isUnlocking = false
}
func unlockWithPassword() async {
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: "masterPassword")
password = ""
} catch {
errorMessage = error.localizedDescription
}
isUnlocking = false
}
private func performUnlockFromKeychain() throws {
let pw = try keychainStore.load(for: "masterPassword")
let url = try bookmarkService.resolveURL()
defer { bookmarkService.stopAccess(url: url) }
try session.unlock(url: url, password: pw)
}
}
@@ -7,10 +7,12 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
205678DB2FC0EB5200251C6B /* MyPassCore in Frameworks */ = {isa = PBXBuildFile; productRef = 205678DA2FC0EB5200251C6B /* MyPassCore */; }; 205678DB2FC0EB5200251C6B /* KeeVaultCore in Frameworks */ = {isa = PBXBuildFile; productRef = 205678DA2FC0EB5200251C6B /* KeeVaultCore */; };
2096B432305EC2B200C2F253 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 205678E32FC0F52A00251C6B /* AuthenticationServices.framework */; }; 2096B432305EC2B200C2F253 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 205678E32FC0F52A00251C6B /* AuthenticationServices.framework */; };
2096B43D305EC2B200C2F253 /* AutoFill.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2096B431305EC2B200C2F253 /* AutoFill.appex */; platformFilters = (ios, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 2096B43D305EC2B200C2F253 /* AutoFill.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2096B431305EC2B200C2F253 /* AutoFill.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
67DE2FCFC21FD6DF5E761C87 /* MyPassCore in Frameworks */ = {isa = PBXBuildFile; productRef = 205678DA2FC0EB5200251C6B /* MyPassCore */; }; 67DE2FCFC21FD6DF5E761C87 /* KeeVaultCore in Frameworks */ = {isa = PBXBuildFile; productRef = 205678DA2FC0EB5200251C6B /* KeeVaultCore */; };
982CE38F924E4FD387BC2C20 /* BiometricAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */; };
EABAC2B148F845C8A20EC2CC /* FileBookmarkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -19,14 +21,14 @@
containerPortal = 2056788B2FBF9CBB00251C6B /* Project object */; containerPortal = 2056788B2FBF9CBB00251C6B /* Project object */;
proxyType = 1; proxyType = 1;
remoteGlobalIDString = 205678922FBF9CBB00251C6B; remoteGlobalIDString = 205678922FBF9CBB00251C6B;
remoteInfo = MyPass; remoteInfo = KeeVault;
}; };
205678AD2FBF9CBC00251C6B /* PBXContainerItemProxy */ = { 205678AD2FBF9CBC00251C6B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 2056788B2FBF9CBB00251C6B /* Project object */; containerPortal = 2056788B2FBF9CBB00251C6B /* Project object */;
proxyType = 1; proxyType = 1;
remoteGlobalIDString = 205678922FBF9CBB00251C6B; remoteGlobalIDString = 205678922FBF9CBB00251C6B;
remoteInfo = MyPass; remoteInfo = KeeVault;
}; };
2096B43B305EC2B200C2F253 /* PBXContainerItemProxy */ = { 2096B43B305EC2B200C2F253 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
@@ -52,11 +54,13 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
205678932FBF9CBB00251C6B /* MyPass.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyPass.app; sourceTree = BUILT_PRODUCTS_DIR; }; 205678932FBF9CBB00251C6B /* KeeVault.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KeeVault.app; sourceTree = BUILT_PRODUCTS_DIR; };
205678A22FBF9CBC00251C6B /* MyPassTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyPassTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 205678A22FBF9CBC00251C6B /* KeeVaultTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeeVaultTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
205678AC2FBF9CBC00251C6B /* MyPassUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyPassUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 205678AC2FBF9CBC00251C6B /* KeeVaultUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeeVaultUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
205678E32FC0F52A00251C6B /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; 205678E32FC0F52A00251C6B /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; };
2096B431305EC2B200C2F253 /* AutoFill.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AutoFill.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 2096B431305EC2B200C2F253 /* AutoFill.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AutoFill.appex; sourceTree = BUILT_PRODUCTS_DIR; };
ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FileBookmarkService.swift; path = KeeVault/Services/FileBookmarkService.swift; sourceTree = SOURCE_ROOT; };
D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BiometricAuthService.swift; path = KeeVault/Services/BiometricAuthService.swift; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -70,19 +74,19 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
205678952FBF9CBB00251C6B /* MyPass */ = { 205678952FBF9CBB00251C6B /* KeeVault */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
path = MyPass; path = KeeVault;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
205678A52FBF9CBC00251C6B /* MyPassTests */ = { 205678A52FBF9CBC00251C6B /* KeeVaultTests */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
path = MyPassTests; path = KeeVaultTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
205678AF2FBF9CBC00251C6B /* MyPassUITests */ = { 205678AF2FBF9CBC00251C6B /* KeeVaultUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
path = MyPassUITests; path = KeeVaultUITests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
2096B433305EC2B200C2F253 /* AutoFill */ = { 2096B433305EC2B200C2F253 /* AutoFill */ = {
@@ -100,7 +104,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
205678DB2FC0EB5200251C6B /* MyPassCore in Frameworks */, 205678DB2FC0EB5200251C6B /* KeeVaultCore in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -123,20 +127,30 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
2096B432305EC2B200C2F253 /* AuthenticationServices.framework in Frameworks */, 2096B432305EC2B200C2F253 /* AuthenticationServices.framework in Frameworks */,
67DE2FCFC21FD6DF5E761C87 /* MyPassCore in Frameworks */, 67DE2FCFC21FD6DF5E761C87 /* KeeVaultCore in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
1A2B3C4D5E6F7A8B9C0D1E2F /* AutoFill Shared Services */ = {
isa = PBXGroup;
children = (
ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */,
D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */,
);
name = "AutoFill Shared Services";
sourceTree = "<group>";
};
2056788A2FBF9CBB00251C6B = { 2056788A2FBF9CBB00251C6B = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
205678952FBF9CBB00251C6B /* MyPass */, 205678952FBF9CBB00251C6B /* KeeVault */,
205678A52FBF9CBC00251C6B /* MyPassTests */, 205678A52FBF9CBC00251C6B /* KeeVaultTests */,
205678AF2FBF9CBC00251C6B /* MyPassUITests */, 205678AF2FBF9CBC00251C6B /* KeeVaultUITests */,
2096B433305EC2B200C2F253 /* AutoFill */, 2096B433305EC2B200C2F253 /* AutoFill */,
1A2B3C4D5E6F7A8B9C0D1E2F /* AutoFill Shared Services */,
205678E22FC0F52A00251C6B /* Frameworks */, 205678E22FC0F52A00251C6B /* Frameworks */,
205678942FBF9CBB00251C6B /* Products */, 205678942FBF9CBB00251C6B /* Products */,
); );
@@ -145,9 +159,9 @@
205678942FBF9CBB00251C6B /* Products */ = { 205678942FBF9CBB00251C6B /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
205678932FBF9CBB00251C6B /* MyPass.app */, 205678932FBF9CBB00251C6B /* KeeVault.app */,
205678A22FBF9CBC00251C6B /* MyPassTests.xctest */, 205678A22FBF9CBC00251C6B /* KeeVaultTests.xctest */,
205678AC2FBF9CBC00251C6B /* MyPassUITests.xctest */, 205678AC2FBF9CBC00251C6B /* KeeVaultUITests.xctest */,
2096B431305EC2B200C2F253 /* AutoFill.appex */, 2096B431305EC2B200C2F253 /* AutoFill.appex */,
); );
name = Products; name = Products;
@@ -164,9 +178,9 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
205678922FBF9CBB00251C6B /* MyPass */ = { 205678922FBF9CBB00251C6B /* KeeVault */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 205678B62FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "MyPass" */; buildConfigurationList = 205678B62FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "KeeVault" */;
buildPhases = ( buildPhases = (
2056788F2FBF9CBB00251C6B /* Sources */, 2056788F2FBF9CBB00251C6B /* Sources */,
205678902FBF9CBB00251C6B /* Frameworks */, 205678902FBF9CBB00251C6B /* Frameworks */,
@@ -179,19 +193,19 @@
2096B43C305EC2B200C2F253 /* PBXTargetDependency */, 2096B43C305EC2B200C2F253 /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = ( fileSystemSynchronizedGroups = (
205678952FBF9CBB00251C6B /* MyPass */, 205678952FBF9CBB00251C6B /* KeeVault */,
); );
name = MyPass; name = KeeVault;
packageProductDependencies = ( packageProductDependencies = (
205678DA2FC0EB5200251C6B /* MyPassCore */, 205678DA2FC0EB5200251C6B /* KeeVaultCore */,
); );
productName = MyPass; productName = KeeVault;
productReference = 205678932FBF9CBB00251C6B /* MyPass.app */; productReference = 205678932FBF9CBB00251C6B /* KeeVault.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
205678A12FBF9CBC00251C6B /* MyPassTests */ = { 205678A12FBF9CBC00251C6B /* KeeVaultTests */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 205678B92FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "MyPassTests" */; buildConfigurationList = 205678B92FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "KeeVaultTests" */;
buildPhases = ( buildPhases = (
2056789E2FBF9CBC00251C6B /* Sources */, 2056789E2FBF9CBC00251C6B /* Sources */,
2056789F2FBF9CBC00251C6B /* Frameworks */, 2056789F2FBF9CBC00251C6B /* Frameworks */,
@@ -203,18 +217,18 @@
205678A42FBF9CBC00251C6B /* PBXTargetDependency */, 205678A42FBF9CBC00251C6B /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = ( fileSystemSynchronizedGroups = (
205678A52FBF9CBC00251C6B /* MyPassTests */, 205678A52FBF9CBC00251C6B /* KeeVaultTests */,
); );
name = MyPassTests; name = KeeVaultTests;
packageProductDependencies = ( packageProductDependencies = (
); );
productName = MyPassTests; productName = KeeVaultTests;
productReference = 205678A22FBF9CBC00251C6B /* MyPassTests.xctest */; productReference = 205678A22FBF9CBC00251C6B /* KeeVaultTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test"; productType = "com.apple.product-type.bundle.unit-test";
}; };
205678AB2FBF9CBC00251C6B /* MyPassUITests */ = { 205678AB2FBF9CBC00251C6B /* KeeVaultUITests */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 205678BC2FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "MyPassUITests" */; buildConfigurationList = 205678BC2FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "KeeVaultUITests" */;
buildPhases = ( buildPhases = (
205678A82FBF9CBC00251C6B /* Sources */, 205678A82FBF9CBC00251C6B /* Sources */,
205678A92FBF9CBC00251C6B /* Frameworks */, 205678A92FBF9CBC00251C6B /* Frameworks */,
@@ -226,13 +240,13 @@
205678AE2FBF9CBC00251C6B /* PBXTargetDependency */, 205678AE2FBF9CBC00251C6B /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = ( fileSystemSynchronizedGroups = (
205678AF2FBF9CBC00251C6B /* MyPassUITests */, 205678AF2FBF9CBC00251C6B /* KeeVaultUITests */,
); );
name = MyPassUITests; name = KeeVaultUITests;
packageProductDependencies = ( packageProductDependencies = (
); );
productName = MyPassUITests; productName = KeeVaultUITests;
productReference = 205678AC2FBF9CBC00251C6B /* MyPassUITests.xctest */; productReference = 205678AC2FBF9CBC00251C6B /* KeeVaultUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing"; productType = "com.apple.product-type.bundle.ui-testing";
}; };
2096B430305EC2B200C2F253 /* AutoFill */ = { 2096B430305EC2B200C2F253 /* AutoFill */ = {
@@ -252,7 +266,7 @@
); );
name = AutoFill; name = AutoFill;
packageProductDependencies = ( packageProductDependencies = (
205678DA2FC0EB5200251C6B /* MyPassCore */, 205678DA2FC0EB5200251C6B /* KeeVaultCore */,
); );
productName = AutoFill; productName = AutoFill;
productReference = 2096B431305EC2B200C2F253 /* AutoFill.appex */; productReference = 2096B431305EC2B200C2F253 /* AutoFill.appex */;
@@ -266,7 +280,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2660; LastSwiftUpdateCheck = 2660;
LastUpgradeCheck = 2650; LastUpgradeCheck = 2660;
TargetAttributes = { TargetAttributes = {
205678922FBF9CBB00251C6B = { 205678922FBF9CBB00251C6B = {
CreatedOnToolsVersion = 26.5; CreatedOnToolsVersion = 26.5;
@@ -284,7 +298,7 @@
}; };
}; };
}; };
buildConfigurationList = 2056788E2FBF9CBB00251C6B /* Build configuration list for PBXProject "MyPass" */; buildConfigurationList = 2056788E2FBF9CBB00251C6B /* Build configuration list for PBXProject "KeeVault" */;
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
@@ -294,16 +308,16 @@
mainGroup = 2056788A2FBF9CBB00251C6B; mainGroup = 2056788A2FBF9CBB00251C6B;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = ( packageReferences = (
205678D92FC0EB5200251C6B /* XCLocalSwiftPackageReference "MyPassCore" */, 205678D92FC0EB5200251C6B /* XCLocalSwiftPackageReference "KeeVaultCore" */,
); );
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = 205678942FBF9CBB00251C6B /* Products */; productRefGroup = 205678942FBF9CBB00251C6B /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
205678922FBF9CBB00251C6B /* MyPass */, 205678922FBF9CBB00251C6B /* KeeVault */,
205678A12FBF9CBC00251C6B /* MyPassTests */, 205678A12FBF9CBC00251C6B /* KeeVaultTests */,
205678AB2FBF9CBC00251C6B /* MyPassUITests */, 205678AB2FBF9CBC00251C6B /* KeeVaultUITests */,
2096B430305EC2B200C2F253 /* AutoFill */, 2096B430305EC2B200C2F253 /* AutoFill */,
); );
}; };
@@ -366,6 +380,8 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EABAC2B148F845C8A20EC2CC /* FileBookmarkService.swift in Sources */,
982CE38F924E4FD387BC2C20 /* BiometricAuthService.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -374,17 +390,17 @@
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
205678A42FBF9CBC00251C6B /* PBXTargetDependency */ = { 205678A42FBF9CBC00251C6B /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 205678922FBF9CBB00251C6B /* MyPass */; target = 205678922FBF9CBB00251C6B /* KeeVault */;
targetProxy = 205678A32FBF9CBC00251C6B /* PBXContainerItemProxy */; targetProxy = 205678A32FBF9CBC00251C6B /* PBXContainerItemProxy */;
}; };
205678AE2FBF9CBC00251C6B /* PBXTargetDependency */ = { 205678AE2FBF9CBC00251C6B /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 205678922FBF9CBB00251C6B /* MyPass */; target = 205678922FBF9CBB00251C6B /* KeeVault */;
targetProxy = 205678AD2FBF9CBC00251C6B /* PBXContainerItemProxy */; targetProxy = 205678AD2FBF9CBC00251C6B /* PBXContainerItemProxy */;
}; };
2096B43C305EC2B200C2F253 /* PBXTargetDependency */ = { 2096B43C305EC2B200C2F253 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilters = (ios, ); platformFilter = ios;
target = 2096B430305EC2B200C2F253 /* AutoFill */; target = 2096B430305EC2B200C2F253 /* AutoFill */;
targetProxy = 2096B43B305EC2B200C2F253 /* PBXContainerItemProxy */; targetProxy = 2096B43B305EC2B200C2F253 /* PBXContainerItemProxy */;
}; };
@@ -425,6 +441,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
@@ -447,6 +464,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
@@ -486,6 +504,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -501,6 +520,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
}; };
name = Release; name = Release;
@@ -510,14 +530,18 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MyPass/MyPass.entitlements; CODE_SIGN_ENTITLEMENTS = KeeVault/KeeVault.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = F2KB6W8N4R; DEVELOPMENT_TEAM = F2KB6W8N4R;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
ENTITLEMENTS_REQUIRED = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID.";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -533,10 +557,8 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.5; MACOSX_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPass; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevault;
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = org.antiloop222.MyPass; "PRODUCT_BUNDLE_IDENTIFIER[sdk=xros*]" = org.antiloop222.KeeVault;
"PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = org.antiloop222.MyPass;
"PRODUCT_BUNDLE_IDENTIFIER[sdk=xros*]" = org.antiloop222.MyPass;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SDKROOT = auto; SDKROOT = auto;
@@ -558,14 +580,18 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MyPass/MyPass.entitlements; CODE_SIGN_ENTITLEMENTS = KeeVault/KeeVault.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = F2KB6W8N4R; DEVELOPMENT_TEAM = F2KB6W8N4R;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
ENTITLEMENTS_REQUIRED = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID.";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -581,10 +607,8 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.5; MACOSX_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPass; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevault;
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = org.antiloop222.MyPass; "PRODUCT_BUNDLE_IDENTIFIER[sdk=xros*]" = org.antiloop222.KeeVault;
"PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = org.antiloop222.MyPass;
"PRODUCT_BUNDLE_IDENTIFIER[sdk=xros*]" = org.antiloop222.MyPass;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SDKROOT = auto; SDKROOT = auto;
@@ -607,11 +631,12 @@
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MACOSX_DEPLOYMENT_TARGET = 26.5; MACOSX_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPassTests; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevaulttests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO; STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -622,7 +647,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyPass.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MyPass"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KeeVault.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/KeeVault";
XROS_DEPLOYMENT_TARGET = 26.5; XROS_DEPLOYMENT_TARGET = 26.5;
}; };
name = Debug; name = Debug;
@@ -633,11 +658,12 @@
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MACOSX_DEPLOYMENT_TARGET = 26.5; MACOSX_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPassTests; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevaulttests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO; STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -648,7 +674,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyPass.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MyPass"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KeeVault.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/KeeVault";
XROS_DEPLOYMENT_TARGET = 26.5; XROS_DEPLOYMENT_TARGET = 26.5;
}; };
name = Release; name = Release;
@@ -658,11 +684,12 @@
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MACOSX_DEPLOYMENT_TARGET = 26.5; MACOSX_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPassUITests; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevaultuitests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO; STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -673,7 +700,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MyPass; TEST_TARGET_NAME = KeeVault;
XROS_DEPLOYMENT_TARGET = 26.5; XROS_DEPLOYMENT_TARGET = 26.5;
}; };
name = Debug; name = Debug;
@@ -683,11 +710,12 @@
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MACOSX_DEPLOYMENT_TARGET = 26.5; MACOSX_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPassUITests; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevaultuitests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO; STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -698,7 +726,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MyPass; TEST_TARGET_NAME = KeeVault;
XROS_DEPLOYMENT_TARGET = 26.5; XROS_DEPLOYMENT_TARGET = 26.5;
}; };
name = Release; name = Release;
@@ -706,13 +734,16 @@
2096B43E305EC2B200C2F253 /* Debug */ = { 2096B43E305EC2B200C2F253 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AutoFill/AutoFill.entitlements; CODE_SIGN_ENTITLEMENTS = AutoFill/AutoFill.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = F2KB6W8N4R; DEVELOPMENT_TEAM = F2KB6W8N4R;
ENTITLEMENTS_REQUIRED = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AutoFill/Info.plist; INFOPLIST_FILE = AutoFill/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AutoFill; INFOPLIST_KEY_CFBundleDisplayName = AutoFill;
INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID.";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -721,7 +752,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPass.AutoFill; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevault.autofill;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -741,13 +772,16 @@
2096B43F305EC2B200C2F253 /* Release */ = { 2096B43F305EC2B200C2F253 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AutoFill/AutoFill.entitlements; CODE_SIGN_ENTITLEMENTS = AutoFill/AutoFill.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = F2KB6W8N4R; DEVELOPMENT_TEAM = F2KB6W8N4R;
ENTITLEMENTS_REQUIRED = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AutoFill/Info.plist; INFOPLIST_FILE = AutoFill/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AutoFill; INFOPLIST_KEY_CFBundleDisplayName = AutoFill;
INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID.";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -756,7 +790,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.MyPass.AutoFill; PRODUCT_BUNDLE_IDENTIFIER = org.antiloop222.keevault.autofill;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -777,7 +811,7 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
2056788E2FBF9CBB00251C6B /* Build configuration list for PBXProject "MyPass" */ = { 2056788E2FBF9CBB00251C6B /* Build configuration list for PBXProject "KeeVault" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
205678B42FBF9CBC00251C6B /* Debug */, 205678B42FBF9CBC00251C6B /* Debug */,
@@ -786,7 +820,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
205678B62FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "MyPass" */ = { 205678B62FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "KeeVault" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
205678B72FBF9CBC00251C6B /* Debug */, 205678B72FBF9CBC00251C6B /* Debug */,
@@ -795,7 +829,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
205678B92FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "MyPassTests" */ = { 205678B92FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "KeeVaultTests" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
205678BA2FBF9CBC00251C6B /* Debug */, 205678BA2FBF9CBC00251C6B /* Debug */,
@@ -804,7 +838,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
205678BC2FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "MyPassUITests" */ = { 205678BC2FBF9CBC00251C6B /* Build configuration list for PBXNativeTarget "KeeVaultUITests" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
205678BD2FBF9CBC00251C6B /* Debug */, 205678BD2FBF9CBC00251C6B /* Debug */,
@@ -825,16 +859,16 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */ /* Begin XCLocalSwiftPackageReference section */
205678D92FC0EB5200251C6B /* XCLocalSwiftPackageReference "MyPassCore" */ = { 205678D92FC0EB5200251C6B /* XCLocalSwiftPackageReference "KeeVaultCore" */ = {
isa = XCLocalSwiftPackageReference; isa = XCLocalSwiftPackageReference;
relativePath = MyPassCore; relativePath = KeeVaultCore;
}; };
/* End XCLocalSwiftPackageReference section */ /* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
205678DA2FC0EB5200251C6B /* MyPassCore */ = { 205678DA2FC0EB5200251C6B /* KeeVaultCore */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = MyPassCore; productName = KeeVaultCore;
}; };
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
@@ -7,12 +7,12 @@
<key>AutoFill.xcscheme_^#shared#^_</key> <key>AutoFill.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>4</integer> <integer>2</integer>
</dict> </dict>
<key>MyPass.xcscheme_^#shared#^_</key> <key>KeeVault.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>3</integer> <integer>1</integer>
</dict> </dict>
</dict> </dict>
</dict> </dict>
@@ -0,0 +1,38 @@
{
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.420",
"green" : "0.271",
"blue" : "0.467",
"alpha" : "1.000"
}
}
},
{
"idiom" : "universal",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.651",
"green" : "0.498",
"blue" : "0.690",
"alpha" : "1.000"
}
}
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -3,7 +3,8 @@
{ {
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024",
"filename" : "icon-1024-light.png"
}, },
{ {
"appearances" : [ "appearances" : [
@@ -14,7 +15,8 @@
], ],
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024",
"filename" : "icon-1024-dark.png"
}, },
{ {
"appearances" : [ "appearances" : [
@@ -25,57 +27,68 @@
], ],
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024",
"filename" : "icon-1024-tinted.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "16x16" "size" : "16x16",
"filename" : "icon-mac-16.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "16x16" "size" : "16x16",
"filename" : "icon-mac-16@2x.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "32x32" "size" : "32x32",
"filename" : "icon-mac-32.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "32x32" "size" : "32x32",
"filename" : "icon-mac-32@2x.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "128x128" "size" : "128x128",
"filename" : "icon-mac-128.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "128x128" "size" : "128x128",
"filename" : "icon-mac-128@2x.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "256x256" "size" : "256x256",
"filename" : "icon-mac-256.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "256x256" "size" : "256x256",
"filename" : "icon-mac-256@2x.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "512x512" "size" : "512x512",
"filename" : "icon-mac-512.png"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "512x512" "size" : "512x512",
"filename" : "icon-mac-512@2x.png"
} }
], ],
"info" : { "info" : {
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

@@ -0,0 +1,32 @@
{
"images" : [
{
"filename" : "icon-light.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "icon-dark.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

@@ -1,9 +1,4 @@
{ {
"colors" : [
{
"idiom" : "universal"
}
],
"info" : { "info" : {
"author" : "xcode", "author" : "xcode",
"version" : 1 "version" : 1
@@ -0,0 +1,38 @@
{
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.980",
"green" : "0.957",
"blue" : "0.925",
"alpha" : "1.000"
}
}
},
{
"idiom" : "universal",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.141",
"green" : "0.125",
"blue" : "0.094",
"alpha" : "1.000"
}
}
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,12 +1,12 @@
// //
// ContentView.swift // ContentView.swift
// MyPass // KeeVault
// //
// Created by Christophe Vila on 21/05/2026. // Created by Christophe Vila on 21/05/2026.
// //
import SwiftUI import SwiftUI
import MyPassCore import KeeVaultCore
#if os(macOS) #if os(macOS)
import AppKit import AppKit
#endif #endif
@@ -22,6 +22,7 @@ struct ContentView: View {
VaultRootView(session: session) VaultRootView(session: session)
} }
} }
.background(Color("VaultBackground"))
.onReceive( .onReceive(
NotificationCenter.default.publisher(for: sceneBackgroundNotification) NotificationCenter.default.publisher(for: sceneBackgroundNotification)
) { _ in ) { _ in
@@ -2,13 +2,15 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
<true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.org.antiloop222.mypass</string> <string>group.org.antiloop222.keevault</string>
</array> </array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)org.antiloop222.mypass</string> <string>$(AppIdentifierPrefix)org.antiloop222.keevault</string>
</array> </array>
</dict> </dict>
</plist> </plist>
@@ -1,6 +1,6 @@
// //
// MyPassApp.swift // KeeVaultApp.swift
// MyPass // KeeVault
// //
// Created by Christophe Vila on 21/05/2026. // Created by Christophe Vila on 21/05/2026.
// //
@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
@main @main
struct MyPassApp: App { struct KeeVaultApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView()
@@ -1,6 +1,6 @@
// //
// BiometricAuthService.swift // BiometricAuthService.swift
// MyPass // KeeVault
// //
import LocalAuthentication import LocalAuthentication
@@ -1,6 +1,6 @@
// //
// ClipboardService.swift // ClipboardService.swift
// MyPass // KeeVault
// //
import Foundation import Foundation
@@ -1,6 +1,6 @@
// //
// FileBookmarkService.swift // FileBookmarkService.swift
// MyPass // KeeVault
// //
import Foundation import Foundation
@@ -10,7 +10,7 @@ public final class FileBookmarkService {
private static let key = "kdbxBookmark" private static let key = "kdbxBookmark"
private let defaults: UserDefaults private let defaults: UserDefaults
public init(appGroup: String = "group.org.antiloop222.mypass") { public init(appGroup: String = "group.org.antiloop222.keevault") {
defaults = UserDefaults(suiteName: appGroup) ?? .standard defaults = UserDefaults(suiteName: appGroup) ?? .standard
} }
@@ -33,6 +33,11 @@ public final class FileBookmarkService {
} }
public func save(url: URL) throws { public func save(url: URL) throws {
// For File Provider-backed URLs (e.g. iCloud Drive), creating a bookmark without
// an active security scope produces one that resolves later with
// NSCocoaErrorDomain Code=4 ("The file doesn't exist"), even immediately after picking.
let accessing = url.startAccessingSecurityScopedResource()
defer { if accessing { url.stopAccessingSecurityScopedResource() } }
let data = try url.bookmarkData( let data = try url.bookmarkData(
options: Self.creationOptions, options: Self.creationOptions,
includingResourceValuesForKeys: nil, includingResourceValuesForKeys: nil,
@@ -1,8 +1,8 @@
// MyPass/ViewModels/EntryEditViewModel.swift // KeeVault/ViewModels/EntryEditViewModel.swift
import Foundation import Foundation
import Combine import Combine
import SwiftUI import SwiftUI
import MyPassCore import KeeVaultCore
@MainActor @MainActor
final class EntryEditViewModel: ObservableObject { final class EntryEditViewModel: ObservableObject {
@@ -20,7 +20,7 @@ final class EntryEditViewModel: ObservableObject {
private let existingEntry: Entry? private let existingEntry: Entry?
/// `groupId` is the target group for a new entry, or the entry's current group when /// `groupId` is the target group for a new entry, or the entry's current group when
/// editing (shown read-only in that case -- MyPass doesn't support re-parenting an /// editing (shown read-only in that case -- KeeVault doesn't support re-parenting an
/// existing entry to a different group yet). /// existing entry to a different group yet).
init(session: VaultSession, groupId: UUID, existing: Entry? = nil) { init(session: VaultSession, groupId: UUID, existing: Entry? = nil) {
self.session = session self.session = session
@@ -1,6 +1,6 @@
import Foundation import Foundation
import Combine import Combine
import MyPassCore import KeeVaultCore
@MainActor @MainActor
final class GroupFilterViewModel: ObservableObject { final class GroupFilterViewModel: ObservableObject {
@@ -16,11 +16,11 @@ final class GroupFilterViewModel: ObservableObject {
self.session = session self.session = session
} }
func isSelected(_ group: MyPassCore.Group) -> Bool { func isSelected(_ group: KeeVaultCore.Group) -> Bool {
selectedGroupIds.contains(group.id) selectedGroupIds.contains(group.id)
} }
func toggle(_ group: MyPassCore.Group) { func toggle(_ group: KeeVaultCore.Group) {
guard let root = session.database?.root else { return } guard let root = session.database?.root else { return }
selectedGroupIds = GroupSelection.toggling(group.id, in: root, current: selectedGroupIds) selectedGroupIds = GroupSelection.toggling(group.id, in: root, current: selectedGroupIds)
lastToggledGroupId = selectedGroupIds.isEmpty ? nil : group.id lastToggledGroupId = selectedGroupIds.isEmpty ? nil : group.id
@@ -1,11 +1,11 @@
// //
// UnlockViewModel.swift // UnlockViewModel.swift
// MyPass // KeeVault
// //
import Foundation import Foundation
import Combine import Combine
import MyPassCore import KeeVaultCore
@MainActor @MainActor
final class UnlockViewModel: ObservableObject { final class UnlockViewModel: ObservableObject {
@@ -24,7 +24,7 @@ final class UnlockViewModel: ObservableObject {
init( init(
session: VaultSession, session: VaultSession,
bookmarkService: FileBookmarkService = .init(), bookmarkService: FileBookmarkService = .init(),
keychainStore: KeychainStore = .init(accessGroup: "org.antiloop222.mypass"), keychainStore: KeychainStore = .init(accessGroup: "F2KB6W8N4R.org.antiloop222.keevault"),
biometricService: BiometricAuthService = .init() biometricService: BiometricAuthService = .init()
) { ) {
self.session = session self.session = session
@@ -44,7 +44,7 @@ final class UnlockViewModel: ObservableObject {
isUnlocking = true isUnlocking = true
errorMessage = nil errorMessage = nil
do { do {
try await biometricService.authenticate(reason: "Unlock MyPass") try await biometricService.authenticate(reason: "Unlock KeeVault")
let storedPassword = try keychainStore.load(for: keychainAccount) let storedPassword = try keychainStore.load(for: keychainAccount)
let url = try bookmarkService.resolveURL() let url = try bookmarkService.resolveURL()
defer { bookmarkService.stopAccess(url: url) } defer { bookmarkService.stopAccess(url: url) }
@@ -1,7 +1,7 @@
// MyPass/ViewModels/VaultViewModel.swift // KeeVault/ViewModels/VaultViewModel.swift
import Foundation import Foundation
import Combine import Combine
import MyPassCore import KeeVaultCore
@MainActor @MainActor
final class VaultViewModel: ObservableObject { final class VaultViewModel: ObservableObject {
@@ -1,11 +1,11 @@
// //
// EntryDetailView.swift // EntryDetailView.swift
// MyPass // KeeVault
// //
import SwiftUI import SwiftUI
import Combine import Combine
import MyPassCore import KeeVaultCore
struct EntryDetailView: View { struct EntryDetailView: View {
let entry: Entry let entry: Entry
@@ -1,6 +1,6 @@
// MyPass/Views/EntryEditView.swift // KeeVault/Views/EntryEditView.swift
import SwiftUI import SwiftUI
import MyPassCore import KeeVaultCore
struct EntryEditView: View { struct EntryEditView: View {
@ObservedObject var vm: EntryEditViewModel @ObservedObject var vm: EntryEditViewModel
@@ -1,10 +1,9 @@
// MyPass/Views/EntryListView.swift // KeeVault/Views/EntryListView.swift
import SwiftUI import SwiftUI
import MyPassCore import KeeVaultCore
struct EntryListView: View { struct EntryListView: View {
@ObservedObject var vm: VaultViewModel @ObservedObject var vm: VaultViewModel
@State private var isSearching = false
@State private var showAddEntry = false @State private var showAddEntry = false
var body: some View { var body: some View {
@@ -24,17 +23,11 @@ struct EntryListView: View {
offsets.map { vm.displayedEntries[$0] }.forEach(vm.deleteEntry) offsets.map { vm.displayedEntries[$0] }.forEach(vm.deleteEntry)
} }
} }
.navigationTitle("MyPass") .scrollContentBackground(.hidden)
.searchable(text: $vm.searchQuery, isPresented: $isSearching, prompt: "Search entries…") .background(Color("VaultBackground"))
.navigationTitle("KeeVault")
.searchable(text: $vm.searchQuery, prompt: "Search entries…")
.toolbar { .toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
if isSearching { vm.searchQuery = "" }
isSearching.toggle()
} label: {
Image(systemName: "magnifyingglass")
}
}
ToolbarItem(placement: .primaryAction) { ToolbarItem(placement: .primaryAction) {
Button { showAddEntry = true } label: { Button { showAddEntry = true } label: {
Image(systemName: "plus") Image(systemName: "plus")
@@ -1,35 +1,39 @@
import SwiftUI import SwiftUI
import MyPassCore import KeeVaultCore
struct GroupFilterView: View { struct GroupFilterView: View {
@ObservedObject var vm: GroupFilterViewModel @ObservedObject var vm: GroupFilterViewModel
let rootGroup: MyPassCore.Group let rootGroup: KeeVaultCore.Group
var body: some View { var body: some View {
List { List {
if !vm.selectedGroupIds.isEmpty {
Button("Clear Filter", action: vm.clear)
}
GroupFilterNode(group: rootGroup, vm: vm) GroupFilterNode(group: rootGroup, vm: vm)
} }
.scrollContentBackground(.hidden)
.background(Color("VaultBackground"))
.navigationTitle("Groups") .navigationTitle("Groups")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Clear", action: vm.clear)
.disabled(vm.selectedGroupIds.isEmpty)
}
}
} }
} }
private struct GroupFilterNode: View { private struct GroupFilterNode: View {
let group: MyPassCore.Group let group: KeeVaultCore.Group
@ObservedObject var vm: GroupFilterViewModel @ObservedObject var vm: GroupFilterViewModel
var depth: Int = 0
var body: some View { var body: some View {
if group.subgroups.isEmpty { // A plain SwiftUI.Group (not a container view) flattens its children into
// separate List rows, so the whole subtree renders always-expanded --
// no DisclosureGroup, no tap-to-reveal -- active filters are visible at a glance.
SwiftUI.Group {
row row
} else {
DisclosureGroup {
ForEach(group.subgroups) { sub in ForEach(group.subgroups) { sub in
GroupFilterNode(group: sub, vm: vm) GroupFilterNode(group: sub, vm: vm, depth: depth + 1)
}
} label: {
row
} }
} }
} }
@@ -45,5 +49,6 @@ private struct GroupFilterNode: View {
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.padding(.leading, CGFloat(depth) * 16)
} }
} }
@@ -1,11 +1,11 @@
// //
// UnlockView.swift // UnlockView.swift
// MyPass // KeeVault
// //
import SwiftUI import SwiftUI
import UniformTypeIdentifiers import UniformTypeIdentifiers
import MyPassCore import KeeVaultCore
struct UnlockView: View { struct UnlockView: View {
@ObservedObject var vm: UnlockViewModel @ObservedObject var vm: UnlockViewModel
@@ -13,10 +13,11 @@ struct UnlockView: View {
var body: some View { var body: some View {
VStack(spacing: 24) { VStack(spacing: 24) {
Spacer() Spacer()
Image(systemName: "lock.shield.fill") Image("AppIconImage")
.font(.system(size: 64)) .resizable()
.foregroundStyle(.tint) .frame(width: 88, height: 88)
Text("MyPass") .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
Text("KeeVault")
.font(.largeTitle.bold()) .font(.largeTitle.bold())
if vm.hasVault { if vm.hasVault {
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>KeeVaultCore.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
</dict>
</dict>
</plist>
@@ -2,24 +2,24 @@
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "MyPassCore", name: "KeeVaultCore",
platforms: [.iOS(.v17), .macOS(.v14)], platforms: [.iOS(.v17), .macOS(.v14)],
products: [ products: [
.library(name: "MyPassCore", targets: ["MyPassCore"]), .library(name: "KeeVaultCore", targets: ["KeeVaultCore"]),
], ],
dependencies: [ dependencies: [
.package(path: "../Vendor/KeePassKit"), .package(path: "../Vendor/KeePassKit"),
], ],
targets: [ targets: [
.target( .target(
name: "MyPassCore", name: "KeeVaultCore",
dependencies: [ dependencies: [
.product(name: "KeePassKit", package: "KeePassKit"), .product(name: "KeePassKit", package: "KeePassKit"),
] ]
), ),
.testTarget( .testTarget(
name: "MyPassCoreTests", name: "KeeVaultCoreTests",
dependencies: ["MyPassCore"], dependencies: ["KeeVaultCore"],
resources: [.copy("Fixtures")] resources: [.copy("Fixtures")]
), ),
] ]
@@ -25,7 +25,14 @@ public enum CredentialMatcher {
} }
private static func host(from urlString: String) -> String? { private static func host(from urlString: String) -> String? {
URL(string: urlString)?.host // KDBX entries and AutoFill service identifiers commonly omit the scheme
// (e.g. "allocine.fr"), but URL(string:) only populates `.host` when an
// authority component ("//") is present -- without it, the whole string
// is parsed as a relative path and `.host` is nil.
if let host = URL(string: urlString)?.host, !host.isEmpty {
return host
}
return URL(string: "https://" + urlString)?.host
} }
private static func hostsMatch(_ a: String, _ b: String) -> Bool { private static func hostsMatch(_ a: String, _ b: String) -> Bool {
@@ -1,7 +1,7 @@
import Foundation import Foundation
/// Maps KeePass's standard icon palette (indices 0-68) to an approximate SF Symbol. /// Maps KeePass's standard icon palette (indices 0-68) to an approximate SF Symbol.
/// This is a best-effort visual match, not the actual KeePass icon artwork -- MyPass's /// This is a best-effort visual match, not the actual KeePass icon artwork -- KeeVault's
/// data model only stores the numeric iconIndex, not custom icon images. /// data model only stores the numeric iconIndex, not custom icon images.
public enum KeePassIconMapper { public enum KeePassIconMapper {
private static let symbolsByIndex: [Int: String] = [ private static let symbolsByIndex: [Int: String] = [
@@ -3,7 +3,7 @@ import KeePassKit
enum KDBXMapper { enum KDBXMapper {
// MARK: - KeePassKit MyPassCore // MARK: - KeePassKit KeeVaultCore
static func database(from tree: KPKTree) -> KDBXDatabase { static func database(from tree: KPKTree) -> KDBXDatabase {
let meta = DatabaseMetadata( let meta = DatabaseMetadata(
@@ -67,7 +67,7 @@ enum KDBXMapper {
) )
} }
// MARK: - MyPassCore KeePassKit // MARK: - KeeVaultCore KeePassKit
static func tree(from db: KDBXDatabase) -> KPKTree { static func tree(from db: KDBXDatabase) -> KPKTree {
let tree = KPKTree() let tree = KPKTree()
@@ -129,7 +129,7 @@ enum KDBXMapper {
var components = URLComponents() var components = URLComponents()
components.scheme = "otpauth" components.scheme = "otpauth"
components.host = "totp" components.host = "totp"
components.path = "/MyPass" components.path = "/KeeVault"
components.queryItems = [ components.queryItems = [
URLQueryItem(name: "secret", value: config.secret), URLQueryItem(name: "secret", value: config.secret),
URLQueryItem(name: "period", value: String(config.period)), URLQueryItem(name: "period", value: String(config.period)),
@@ -5,7 +5,7 @@ public struct KeychainStore {
private let accessGroup: String private let accessGroup: String
private let service: String private let service: String
public init(accessGroup: String, service: String = "org.antiloop222.mypass") { public init(accessGroup: String, service: String = "org.antiloop222.keevault") {
self.accessGroup = accessGroup self.accessGroup = accessGroup
self.service = service self.service = service
} }
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class CredentialMatcherTests: XCTestCase { final class CredentialMatcherTests: XCTestCase {
func makeEntry(url: String) -> Entry { func makeEntry(url: String) -> Entry {
@@ -31,6 +31,21 @@ final class CredentialMatcherTests: XCTestCase {
XCTAssertFalse(CredentialMatcher.matches(entry: e, serviceIdentifier: "https://github.com")) XCTAssertFalse(CredentialMatcher.matches(entry: e, serviceIdentifier: "https://github.com"))
} }
func test_bareDomainSubdomainMatch() {
let e = makeEntry(url: "allocine.fr")
XCTAssertTrue(CredentialMatcher.matches(entry: e, serviceIdentifier: "mon.allocine.fr"))
}
func test_bareDomainExactMatch() {
let e = makeEntry(url: "allocine.fr")
XCTAssertTrue(CredentialMatcher.matches(entry: e, serviceIdentifier: "allocine.fr"))
}
func test_bareDomainDifferentDomain_noMatch() {
let e = makeEntry(url: "allocine.fr")
XCTAssertFalse(CredentialMatcher.matches(entry: e, serviceIdentifier: "notallocine.fr"))
}
func test_filter_suggestedAndRest() { func test_filter_suggestedAndRest() {
let entries = [ let entries = [
makeEntry(url: "https://github.com"), makeEntry(url: "https://github.com"),
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class GroupSelectionTests: XCTestCase { final class GroupSelectionTests: XCTestCase {
private func makeSampleTree() -> (root: Group, work: Group, email: Group, personal: Group) { private func makeSampleTree() -> (root: Group, work: Group, email: Group, personal: Group) {
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class GroupTreeTests: XCTestCase { final class GroupTreeTests: XCTestCase {
private func makeSampleTree() -> (root: Group, work: Group, email: Group) { private func makeSampleTree() -> (root: Group, work: Group, email: Group) {
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class KDBXDocumentTests: XCTestCase { final class KDBXDocumentTests: XCTestCase {
var fixtureURL: URL! var fixtureURL: URL!
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class KeePassIconMapperTests: XCTestCase { final class KeePassIconMapperTests: XCTestCase {
func test_knownIndex_returnsMappedSymbol() { func test_knownIndex_returnsMappedSymbol() {
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class ProtectedStringTests: XCTestCase { final class ProtectedStringTests: XCTestCase {
func test_reveal_returnsOriginalValue() { func test_reveal_returnsOriginalValue() {
@@ -1,5 +1,5 @@
import XCTest import XCTest
@testable import MyPassCore @testable import KeeVaultCore
final class TOTPGeneratorTests: XCTestCase { final class TOTPGeneratorTests: XCTestCase {
// RFC 6238 Section 8 test vectors for SHA-1 // RFC 6238 Section 8 test vectors for SHA-1
@@ -1,13 +1,13 @@
// //
// MyPassTests.swift // KeeVaultTests.swift
// MyPassTests // KeeVaultTests
// //
// Created by Christophe Vila on 21/05/2026. // Created by Christophe Vila on 21/05/2026.
// //
import Testing import Testing
struct MyPassTests { struct KeeVaultTests {
@Test func example() async throws { @Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions. // Write your test here and use APIs like `#expect(...)` to check expected conditions.
@@ -1,13 +1,13 @@
// //
// MyPassUITests.swift // KeeVaultUITests.swift
// MyPassUITests // KeeVaultUITests
// //
// Created by Christophe Vila on 21/05/2026. // Created by Christophe Vila on 21/05/2026.
// //
import XCTest import XCTest
final class MyPassUITests: XCTestCase { final class KeeVaultUITests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
@@ -1,13 +1,13 @@
// //
// MyPassUITestsLaunchTests.swift // KeeVaultUITestsLaunchTests.swift
// MyPassUITests // KeeVaultUITests
// //
// Created by Christophe Vila on 21/05/2026. // Created by Christophe Vila on 21/05/2026.
// //
import XCTest import XCTest
final class MyPassUITestsLaunchTests: XCTestCase { final class KeeVaultUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool { override class var runsForEachTargetApplicationUIConfiguration: Bool {
true true
@@ -152,7 +152,7 @@ NavigationSplitView(columnVisibility:)
`NavigationSplitView` is used on **both** platforms instead of maintaining separate iOS/macOS navigation code. On iPhone-width layouts it automatically collapses the sidebar into an overlay; on iPad/Mac it can sit persistently alongside the detail column. This behavior is built into the component — no platform-specific branching needed for it. `NavigationSplitView` is used on **both** platforms instead of maintaining separate iOS/macOS navigation code. On iPhone-width layouts it automatically collapses the sidebar into an overlay; on iPad/Mac it can sit persistently alongside the detail column. This behavior is built into the component — no platform-specific branching needed for it.
- **Hamburger button** (leading toolbar item, `"line.3.horizontal"`) toggles `columnVisibility` between `.all` and `.detailOnly`, showing/hiding the sidebar. - **Hamburger button** (leading toolbar item, `"line.3.horizontal"`) toggles `columnVisibility` between `.all` and `.detailOnly`, showing/hiding the sidebar.
- **Search icon** (trailing toolbar item, `"magnifyingglass"`) toggles a `.searchable()` bar over `EntryListView`; tapping again hides it and clears the query. - **Search** is always visible via `.searchable()` over `EntryListView` (no toggle icon -- changed 2026-09-19 after hands-on use showed the extra tap added no value over an always-present search field).
- Swipe-to-delete on entry rows; toolbar `+` button opens `EntryEditView` for a new entry (see "Add-entry flow" below). - Swipe-to-delete on entry rows; toolbar `+` button opens `EntryEditView` for a new entry (see "Add-entry flow" below).
- Tapping a password field in `EntryDetailView` copies to clipboard (clears after 30 s). - Tapping a password field in `EntryDetailView` copies to clipboard (clears after 30 s).
- TOTP code displays with a countdown ring and refreshes automatically. - TOTP code displays with a countdown ring and refreshes automatically.