diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
index d7f54c4521e..6bcbd589ce3 100644
--- a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
+++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
@@ -1,96 +1,70 @@
-
+
-
-
+
+
+
-
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
\ No newline at end of file
+
diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
index a6d894e0b84..5f59795eefa 100644
--- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
+++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
@@ -48,6 +48,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
}
+
+ override func loadView() {
+ let view = NSView()
+ view.isHidden = true
+ //view.backgroundColor = .clear
+ self.view = view
+ }
/*
Implement this method if your extension supports showing credentials in the QuickType bar.
@@ -77,6 +84,9 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
}
override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) {
+
+ //logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(credentialRequest) called \(request)")
+
if let request = credentialRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity {
@@ -155,6 +165,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) {
logger.log("[autofill-extension] prepareInterface")
+ // Create a timer to show UI after 10 seconds
+ DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
+ guard let self = self else { return }
+ // Configure and show UI elements for manual cancellation
+ self.configureTimeoutUI()
+ }
+
if let request = registrationRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = registrationRequest.credentialIdentity as? ASPasskeyCredentialIdentity {
logger.log("[autofill-extension] prepareInterface(passkey) called \(request)")
@@ -232,6 +249,61 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
for serviceIdentifier in serviceIdentifiers {
logger.log(" service: \(serviceIdentifier.identifier)")
}
+
+
+ class CallbackImpl: PreparePasskeyAssertionCallback {
+ let ctx: ASCredentialProviderExtensionContext
+ required init(_ ctx: ASCredentialProviderExtensionContext) {
+ self.ctx = ctx
+ }
+
+ func onComplete(credential: PasskeyAssertionResponse) {
+ ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential(
+ userHandle: credential.userHandle,
+ relyingParty: credential.rpId,
+ signature: credential.signature,
+ clientDataHash: credential.clientDataHash,
+ authenticatorData: credential.authenticatorData,
+ credentialID: credential.credentialId
+ ))
+ }
+
+ func onError(error: BitwardenError) {
+ ctx.cancelRequest(withError: error)
+ }
+ }
+
+ let userVerification = switch requestParameters.userVerificationPreference {
+ case .preferred:
+ UserVerification.preferred
+ case .required:
+ UserVerification.required
+ default:
+ UserVerification.discouraged
+ }
+
+ // TODO: PasskeyAssertionRequest does not implement allowedCredentials, extensions and required credentialId, username, userhandle, recordIdentifier??
+ let req = PasskeyAssertionRequest(
+ rpId: requestParameters.relyingPartyIdentifier,
+
+ // TODO: remove once the PasskeyAssertionRequest type has been improved
+ credentialId: Data(),
+ userName: "",
+ userHandle: Data(),
+ recordIdentifier: "",
+
+ //allowedCredentials: requestParameters.allowedCredentials,
+ //extensionInput: requestParameters.extensionInput,
+ clientDataHash: requestParameters.clientDataHash,
+ userVerification: userVerification
+ )
+
+ CredentialProviderViewController.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext))
+ return
+ }
+
+ private func configureTimeoutUI() {
+ self.view.isHidden = false;
}
}
diff --git a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts
index 002096cc107..919a9e0365c 100644
--- a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts
+++ b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts
@@ -76,21 +76,32 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private desktopSettingsService: DesktopSettingsService,
) {}
+ private confirmCredentialSubject = new Subject();
+ private createdCipher: Cipher;
+
+ // Method implementation
async pickCredential(
params: PickCredentialParams,
): Promise<{ cipherId: string; userVerified: boolean }> {
- // Add your implementation here
- return { cipherId: "", userVerified: false };
- }
+ this.logService.warning("pickCredential desktop function", params);
- private confirmCredentialSubject = new Subject();
- private createdCipher: Cipher;
+ try {
+ await this.showUi();
+
+ await this.waitForUiCredentialConfirmation();
+
+ return { cipherId: params.cipherIds[0], userVerified: true };
+ } finally {
+ // Make sure to clean up so the app is never stuck in modal mode?
+ await this.desktopSettingsService.setInModalMode(false);
+ }
+ }
/**
* Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS.
*/
- notifyConfirmCredential() {
- this.confirmCredentialSubject.next();
+ notifyConfirmCredential(confirmed: boolean): void {
+ this.confirmCredentialSubject.next(confirmed);
this.confirmCredentialSubject.complete();
}
@@ -98,7 +109,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
* Returns once the UI has confirmed and completed the operation
* @returns
*/
- private async waitForUiCredentialConfirmation(): Promise {
+ private async waitForUiCredentialConfirmation(): Promise {
return lastValueFrom(this.confirmCredentialSubject);
}
@@ -125,8 +136,10 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
await this.showUi(rpId);
// Wait for the UI to wrap up
- await this.waitForUiCredentialConfirmation();
-
+ const confirmation = await this.waitForUiCredentialConfirmation();
+ if (!confirmation) {
+ throw new Error("User cancelled");
+ }
// Create the credential
await this.createCredential({
credentialName,
diff --git a/apps/desktop/src/modal/passkeys/create/fido2-create.component.html b/apps/desktop/src/modal/passkeys/create/fido2-create.component.html
index dac028aac9b..e205ab52fd6 100644
--- a/apps/desktop/src/modal/passkeys/create/fido2-create.component.html
+++ b/apps/desktop/src/modal/passkeys/create/fido2-create.component.html
@@ -9,7 +9,6 @@
{{ "savePasskeyQuestion" | i18n }}
-