diff --git a/AutoFill/Base.lproj/MainInterface.storyboard b/AutoFill/Base.lproj/MainInterface.storyboard
index 255319b..4a328f1 100644
--- a/AutoFill/Base.lproj/MainInterface.storyboard
+++ b/AutoFill/Base.lproj/MainInterface.storyboard
@@ -17,36 +17,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AutoFill/CredentialProviderViewController.swift b/AutoFill/CredentialProviderViewController.swift
index 31ae3f6..d96bbad 100644
--- a/AutoFill/CredentialProviderViewController.swift
+++ b/AutoFill/CredentialProviderViewController.swift
@@ -6,53 +6,71 @@
//
import AuthenticationServices
+import SwiftUI
+import MyPassCore
-class CredentialProviderViewController: ASCredentialProviderViewController {
+final class CredentialProviderViewController: ASCredentialProviderViewController {
- /*
- Prepare your UI to list available credentials for the user to choose from. The items in
- 'serviceIdentifiers' describe the service the user is logging in to, so your extension can
- prioritize the most relevant credentials in the list.
- */
+ private let session = VaultSession()
+ private let bookmarkService = FileBookmarkService()
+ private let keychainStore = KeychainStore(accessGroup: "org.antiloop222.mypass")
+ private let biometricService = BiometricAuthService()
+
+ // Called when the user selects MyPass from the QuickType bar.
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
+ let ids = serviceIdentifiers.map(\.identifier)
+ showUI(serviceIdentifiers: ids)
}
- /*
- Implement this method if your extension supports showing credentials in the QuickType bar.
- When the user selects a credential from your app, this method will be called with the
- ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore.
- Provide the password by completing the extension request with the associated ASPasswordCredential.
- If using the credential would require showing custom UI for authenticating the user, cancel
- the request with error code ASExtensionError.userInteractionRequired.
-
+ // Called for inline QuickType suggestion (no UI shown).
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
- let databaseIsUnlocked = true
- if (databaseIsUnlocked) {
- let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
- self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
- } else {
- self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
+ 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))
+ }
}
}
- */
- /*
- 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))
+ 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)
}
- @IBAction func passwordSelected(_ sender: AnyObject?) {
- let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
- self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
+ private func showUI(serviceIdentifiers: [String]) {
+ let rootView = ExtensionRootView(
+ session: session,
+ serviceIdentifiers: serviceIdentifiers,
+ bookmarkService: bookmarkService,
+ keychainStore: keychainStore,
+ biometricService: biometricService,
+ onSelect: { [weak self] (entry: Entry) in
+ let credential = ASPasswordCredential(
+ user: entry.username,
+ password: entry.password.reveal()
+ )
+ self?.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil)
+ },
+ 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)
}
-
}
diff --git a/MyPass.xcodeproj/project.pbxproj b/MyPass.xcodeproj/project.pbxproj
index 7886a7f..95ecdca 100644
--- a/MyPass.xcodeproj/project.pbxproj
+++ b/MyPass.xcodeproj/project.pbxproj
@@ -7,9 +7,11 @@
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 */; 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 */; };
/* End PBXBuildFile section */
@@ -52,6 +54,8 @@
/* 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; };
@@ -130,6 +134,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 1A2B3C4D5E6F7A8B9C0D1E2F /* AutoFill Shared Services */ = {
+ isa = PBXGroup;
+ children = (
+ ADBBB9F1BA62473294D2B5B5 /* FileBookmarkService.swift */,
+ D6C7DFA4A59640D7BF6B4960 /* BiometricAuthService.swift */,
+ );
+ name = "AutoFill Shared Services";
+ sourceTree = "";
+ };
2056788A2FBF9CBB00251C6B = {
isa = PBXGroup;
children = (
@@ -137,6 +150,7 @@
205678A52FBF9CBC00251C6B /* MyPassTests */,
205678AF2FBF9CBC00251C6B /* MyPassUITests */,
2096B433305EC2B200C2F253 /* AutoFill */,
+ 1A2B3C4D5E6F7A8B9C0D1E2F /* AutoFill Shared Services */,
205678E22FC0F52A00251C6B /* Frameworks */,
205678942FBF9CBB00251C6B /* Products */,
);
@@ -366,6 +380,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ EABAC2B148F845C8A20EC2CC /* FileBookmarkService.swift in Sources */,
+ 982CE38F924E4FD387BC2C20 /* BiometricAuthService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -384,7 +400,7 @@
};
2096B43C305EC2B200C2F253 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
- platformFilters = (ios, );
+ platformFilter = ios;
target = 2096B430305EC2B200C2F253 /* AutoFill */;
targetProxy = 2096B43B305EC2B200C2F253 /* PBXContainerItemProxy */;
};