diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index ec715762b25..ac8cb284387 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -159,6 +159,7 @@ export declare namespace autofill { supportedAlgorithms: Array windowXy: Position excludedCredentials: Array> + context?: Array } export interface PasskeyRegistrationResponse { rpId: string diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 67c9f214540..0f5bd6c9840 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -683,6 +683,7 @@ pub mod autofill { pub supported_algorithms: Vec, pub window_xy: Position, pub excluded_credentials: Vec>, + pub context: Option>, } #[napi(object)] diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc2/registration.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc2/registration.rs index 3ac6808cb49..f9ac9033155 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc2/registration.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc2/registration.rs @@ -15,6 +15,7 @@ pub struct PasskeyRegistrationRequest { pub supported_algorithms: Vec, pub window_xy: Position, pub excluded_credentials: Vec>, + pub context: Vec, } #[derive(Debug, Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs index 2f263b38cac..6a17361d426 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs @@ -627,6 +627,8 @@ pub unsafe fn plugin_make_credential( )); } + let transaction_id = req.transaction_id.to_u128().to_le_bytes().to_vec(); + // Create Windows registration request let registration_request = PasskeyRegistrationRequest { rp_id: rpid.clone(), @@ -641,6 +643,7 @@ pub unsafe fn plugin_make_credential( x: coords.0, y: coords.1, }, + context: transaction_id, }; debug_log(&format!( diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts index f25fcc40f35..b2e59ed38d5 100644 --- a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts +++ b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts @@ -135,7 +135,10 @@ export class Fido2CreateComponent implements OnInit, OnDestroy { throw new Error("Missing session"); } - this.session.notifyConfirmCreateCredential(true); + // TODO: We should know the username by now; we should pass that context here. + const username = "New Account" // placeholder + const isConfirmed = await this.session.promptForUserVerification("New Account", "Verify it's you to update a new credential") + this.session.notifyConfirmCreateCredential(isConfirmed); } catch { await this.showErrorDialog(this.DIALOG_MESSAGES.unableToSavePasskey); } @@ -208,7 +211,9 @@ export class Fido2CreateComponent implements OnInit, OnDestroy { return this.passwordRepromptService.showPasswordPrompt(); } - return true; + let cred = cipher.login.fido2Credentials[0]; + const username = cred.userName ?? cred.userDisplayName + return this.session.promptForUserVerification(username, "Verify it's you to update a new credential") } private async showErrorDialog(config: SimpleDialogOptions): Promise { diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts index 2589e8cc456..6faa3430b1c 100644 --- a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts +++ b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts @@ -154,7 +154,9 @@ export class Fido2VaultComponent implements OnInit, OnDestroy { if (cipher.reprompt !== CipherRepromptType.None) { return this.passwordRepromptService.showPasswordPrompt(); } else { - return this.session.promptForUserVerification(cipher) + let cred = cipher.login.fido2Credentials[0]; + const username = cred.userName ?? cred.userDisplayName + return this.session.promptForUserVerification(username, "Verify it's you to log in") } } } diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 16e6f6c229f..cc4f400c955 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -210,12 +210,14 @@ export class DesktopAutofillService implements OnDestroy { this.logService.debug("listenPasskeyRegistration2", this.convertRegistrationRequest(request)); const controller = new AbortController(); + const ctx = request.context ? new Uint8Array(request.context).buffer : null; try { const response = await this.fido2AuthenticatorService.makeCredential( this.convertRegistrationRequest(request), { windowXy: request.windowXy }, controller, + ctx ); this.logService.debug("Sending registration response to plugin via callback"); 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 833498f75fa..8e393567039 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 @@ -322,10 +322,8 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi } /** Called by the UI to prompt the user for verification. May be fulfilled by the OS. */ - async promptForUserVerification(cipher: CipherView): Promise { + async promptForUserVerification(username: string, displayHint: string): Promise { this.logService.info("DesktopFido2UserInterfaceSession] Prompting for user verification") - let cred = cipher.login.fido2Credentials[0]; - const username = cred.userName ?? cred.userDisplayName let windowHandle = await ipc.platform.getNativeWindowHandle(); const uvResult = await ipc.autofill.runCommand({ @@ -335,7 +333,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi windowHandle: Utils.fromBufferToB64(windowHandle), transactionContext: Utils.fromBufferToB64(this.transactionContext), username, - displayHint: `Logging in as ${cipher.name}`, + displayHint, }, }); if (uvResult.type === "error") { diff --git a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts index 3fc6a346783..34c4e545eac 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts @@ -19,6 +19,7 @@ export abstract class Fido2AuthenticatorService { params: Fido2AuthenticatorMakeCredentialsParams, window: ParentWindowReference, abortController?: AbortController, + transactionContext?: ArrayBuffer, ): Promise; /** diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 8726ba37174..4c3b1d59eb3 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -61,11 +61,13 @@ export class Fido2AuthenticatorService params: Fido2AuthenticatorMakeCredentialsParams, window: ParentWindowReference, abortController?: AbortController, + transactionContext?: ArrayBuffer, ): Promise { const userInterfaceSession = await this.userInterface.newSession( params.fallbackSupported, window, abortController, + transactionContext, ); try {