diff --git a/AutoFill/CredentialProviderViewController.swift b/AutoFill/CredentialProviderViewController.swift index d96bbad..7070df6 100644 --- a/AutoFill/CredentialProviderViewController.swift +++ b/AutoFill/CredentialProviderViewController.swift @@ -22,32 +22,6 @@ final class CredentialProviderViewController: ASCredentialProviderViewController showUI(serviceIdentifiers: ids) } - // Called for inline QuickType suggestion (no UI shown). - override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { - Task { - do { - try await unlockSilently() - let all = session.allEntries() - if let entry = all.first(where: { $0.id.uuidString == credentialIdentity.recordIdentifier }) { - let credential = ASPasswordCredential(user: entry.username, password: entry.password.reveal()) - self.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil) - } else { - self.extensionContext.cancelRequest(withError: ASExtensionError(.credentialIdentityNotFound)) - } - } catch { - self.extensionContext.cancelRequest(withError: ASExtensionError(.userInteractionRequired)) - } - } - } - - private func unlockSilently() async throws { - guard session.isLocked else { return } - let password = try keychainStore.load(for: "masterPassword") - let url = try bookmarkService.resolveURL() - defer { bookmarkService.stopAccess(url: url) } - try session.unlock(url: url, password: password) - } - private func showUI(serviceIdentifiers: [String]) { let rootView = ExtensionRootView( session: session, diff --git a/AutoFill/Views/ExtensionUnlockView.swift b/AutoFill/Views/ExtensionUnlockView.swift index 6ecc1a3..1104e06 100644 --- a/AutoFill/Views/ExtensionUnlockView.swift +++ b/AutoFill/Views/ExtensionUnlockView.swift @@ -73,7 +73,7 @@ struct ExtensionRootView: View { Text(msg).foregroundStyle(.red).font(.caption).multilineTextAlignment(.center) } - if !vm.hasVault { + if !vm.hasVault || vm.errorMessage != nil { Link(destination: URL(string: "mypass://unlock")!) { Label("Open MyPass to set up vault", systemImage: "arrow.up.right") .font(.caption) @@ -98,6 +98,7 @@ final class ExtensionViewModel: ObservableObject { private let biometricService: BiometricAuthService private let onSelect: (Entry) -> Void private let onCancel: () -> Void + private var sessionCancellable: AnyCancellable? init( session: VaultSession, @@ -115,6 +116,9 @@ final class ExtensionViewModel: ObservableObject { self.biometricService = biometricService self.onSelect = onSelect self.onCancel = onCancel + sessionCancellable = session.objectWillChange.sink { [weak self] in + self?.objectWillChange.send() + } } var isLocked: Bool { session.isLocked } @@ -139,7 +143,7 @@ final class ExtensionViewModel: ObservableObject { try await biometricService.authenticate(reason: "Unlock MyPass") try performUnlockFromKeychain() } catch { - // Silently fail -- user can type password + errorMessage = error.localizedDescription } isUnlocking = false } diff --git a/MyPass.xcodeproj/project.pbxproj b/MyPass.xcodeproj/project.pbxproj index 95ecdca..1722d44 100644 --- a/MyPass.xcodeproj/project.pbxproj +++ b/MyPass.xcodeproj/project.pbxproj @@ -7,12 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - EABAC2B148F845C8A20EC2CC /* FileBookmarkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */; }; - 982CE38F924E4FD387BC2C20 /* BiometricAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */; }; 205678DB2FC0EB5200251C6B /* MyPassCore in Frameworks */ = {isa = PBXBuildFile; productRef = 205678DA2FC0EB5200251C6B /* MyPassCore */; }; 2096B432305EC2B200C2F253 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 205678E32FC0F52A00251C6B /* AuthenticationServices.framework */; }; 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 */; }; + 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 */ /* Begin PBXContainerItemProxy section */ @@ -54,13 +54,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FileBookmarkService.swift; path = MyPass/Services/FileBookmarkService.swift; sourceTree = SOURCE_ROOT; }; - D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BiometricAuthService.swift; path = MyPass/Services/BiometricAuthService.swift; sourceTree = SOURCE_ROOT; }; 205678932FBF9CBB00251C6B /* MyPass.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyPass.app; sourceTree = BUILT_PRODUCTS_DIR; }; 205678A22FBF9CBC00251C6B /* MyPassTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyPassTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 205678AC2FBF9CBC00251C6B /* MyPassUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyPassUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; + ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FileBookmarkService.swift; path = MyPass/Services/FileBookmarkService.swift; sourceTree = SOURCE_ROOT; }; + D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BiometricAuthService.swift; path = MyPass/Services/BiometricAuthService.swift; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -534,6 +534,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -582,6 +583,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -729,6 +731,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = AutoFill/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AutoFill; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 26.5; LD_RUNPATH_SEARCH_PATHS = ( @@ -764,6 +767,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = AutoFill/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AutoFill; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock your vault with Face ID."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 26.5; LD_RUNPATH_SEARCH_PATHS = (