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 }}

-