From ddb73f9e415bdc8a6cc594a30a0cf77c70bc21a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Wed, 29 Jan 2025 17:13:10 +0100 Subject: [PATCH 1/3] This hides the swift UI --- .../CredentialProviderViewController.xib | 114 +++++++----------- .../CredentialProviderViewController.swift | 20 +++ .../components/fido2placeholder.component.ts | 4 +- .../desktop-fido2-user-interface.service.ts | 14 ++- 4 files changed, 75 insertions(+), 77 deletions(-) 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..38a1e707fc1 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,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController { } override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) { + + if let request = credentialRequest as? ASPasskeyCredentialRequest { if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity { @@ -155,6 +164,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)") @@ -234,4 +250,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController { } } + private func configureTimeoutUI() { + self.view.isHidden = false; + } + } diff --git a/apps/desktop/src/app/components/fido2placeholder.component.ts b/apps/desktop/src/app/components/fido2placeholder.component.ts index e5d2b9f85bb..1e0b55b7c23 100644 --- a/apps/desktop/src/app/components/fido2placeholder.component.ts +++ b/apps/desktop/src/app/components/fido2placeholder.component.ts @@ -65,7 +65,7 @@ export class Fido2PlaceholderComponent implements OnInit { // userVerification: true, // }); - this.session.notifyConfirmCredential(); + this.session.notifyConfirmCredential(true); // Not sure this clean up should happen here or in session. // The session currently toggles modal on and send us here @@ -80,5 +80,7 @@ export class Fido2PlaceholderComponent implements OnInit { async closeModal() { await this.router.navigate(["/"]); await this.desktopSettingsService.setInModalMode(false); + + this.session.notifyConfirmCredential(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 46171aa130a..f030d093856 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 @@ -80,14 +80,14 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi params: PickCredentialParams, ) => Promise<{ cipherId: string; userVerified: boolean }>; - private confirmCredentialSubject = new Subject(); + private confirmCredentialSubject = new Subject(); private createdCipher: Cipher; /** * 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(); } @@ -95,7 +95,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); } @@ -122,8 +122,10 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi await this.showUi(); // 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, From 43e7e18c9c79a67971da8a23911c6023fb5533ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Wed, 12 Feb 2025 23:15:40 +0100 Subject: [PATCH 2/3] pick credential, draft --- .../CredentialProviderViewController.swift | 54 ++++++++++++++++++- .../desktop-fido2-user-interface.service.ts | 22 ++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift index 38a1e707fc1..115b9a1ea88 100644 --- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -85,7 +85,8 @@ 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 { @@ -248,6 +249,57 @@ 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() { 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 f030d093856..0c83c19e914 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,13 +76,27 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi private desktopSettingsService: DesktopSettingsService, ) {} - pickCredential: ( - params: PickCredentialParams, - ) => Promise<{ cipherId: string; userVerified: boolean }>; - private confirmCredentialSubject = new Subject(); private createdCipher: Cipher; + // Method implementation + async pickCredential( + params: PickCredentialParams, + ): Promise<{ cipherId: string; userVerified: boolean }> { + this.logService.warning("pickCredential desktop function", params); + + 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. */ From 350554f82639ab9ff5b82c1cd956bbcb16312969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Wed, 12 Feb 2025 23:37:37 +0100 Subject: [PATCH 3/3] Remove logger --- .../autofill-extension/CredentialProviderViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift index 115b9a1ea88..5f59795eefa 100644 --- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -85,7 +85,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController { override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) { - logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(credentialRequest) called \(request)") + //logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(credentialRequest) called \(request)") if let request = credentialRequest as? ASPasskeyCredentialRequest { if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity {