diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 1e0c9cca5cd..75510b53a78 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -18,7 +18,11 @@ export declare namespace autofill { completeAssertion(clientId: number, sequenceNumber: number, response: PasskeyAssertionResponse): number completeWindowHandleQuery(clientId: number, sequenceNumber: number, response: WindowHandleQueryResponse): number completeError(clientId: number, sequenceNumber: number, error: string): number + /** Prompt a user for user verification using OS APIs. */ + verifyUser(request: UserVerificationRequest): Promise } + export type HostResponse = + | { type: 'UserVerification', field0: UserVerificationResponse } export interface NativeStatus { key: string value: string @@ -80,6 +84,14 @@ export declare namespace autofill { Required = 'required', Discouraged = 'discouraged' } + export interface UserVerificationRequest { + transactionId: number + displayHint: string + username?: string + } + export interface UserVerificationResponse { + userVerified: boolean + } export interface WindowHandleQueryRequest { windowHandle: string } diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 0ca1b7edb36..7d93e0a466a 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -635,6 +635,11 @@ pub mod autostart { #[napi] pub mod autofill { + use std::{ + collections::HashMap, + sync::{atomic::AtomicU32, Arc, Mutex}, + }; + use desktop_core::ipc::server::{Message, MessageType}; use napi::{ bindgen_prelude::FnArgs, @@ -793,6 +798,27 @@ pub mod autofill { pub credential_id: Vec, } + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct UserVerificationRequest { + pub transaction_id: u32, + pub display_hint: String, + pub username: Option, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct UserVerificationResponse { + pub user_verified: bool, + } + + #[napi] + pub enum HostResponse { + UserVerification(UserVerificationResponse), + } + #[napi] pub struct AutofillIpcServer { server: desktop_core::ipc::server::Server, @@ -1040,6 +1066,42 @@ pub mod autofill { self.send(client_id, serde_json::to_string(&message).unwrap()) } + /// Prompt a user for user verification using OS APIs. + #[napi] + pub async fn verify_user( + &self, + request: UserVerificationRequest, + ) -> napi::Result { + let request_id = self + .host_callbacks_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let (tx, rx) = oneshot::channel(); + let command = HostRequestMessage { + request_id, + request: HostRequest::UserVerification(request), + }; + self.host_callbacks + .lock() + .expect("not poisoned") + .insert(request_id, tx); + let json = serde_json::to_string(&command).expect("serde to serialize"); + tracing::debug!(json, "Sending verify user message"); + self.send(0, json)?; + + match rx.await { + Ok(Ok(HostResponse::UserVerification(response))) => Ok(response), + Ok(Ok(_)) => Err(napi::Error::from_reason( + "Invalid repsonse received for user verification request", + )), + Ok(Err(err)) => Err(napi::Error::from_reason(format!( + "UV request failed: {err}" + ))), + Err(err) => Err(napi::Error::from_reason(format!( + "Failed to receive UV request: {err}" + ))), + } + } + // TODO: Add a way to send a message to a specific client? fn send(&self, _client_id: u32, message: String) -> napi::Result { self.server diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index 10c00fdbf16..570be38b784 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -14,6 +14,9 @@ export default { runCommand: (params: RunCommandParams): Promise> => ipcRenderer.invoke("autofill.runCommand", params), + verifyUser: (request: autofill.UserVerificationRequest): Promise => + ipcRenderer.invoke("autofill.userVerification", request), + listenerReady: () => ipcRenderer.send("autofill.listenerReady"), listenPasskeyRegistration: ( 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 b636a677a1b..dc00a90456f 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 @@ -32,7 +32,7 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { NativeAutofillUserVerificationCommand } from "../../platform/main/autofill/user-verification.command"; +import { NativeAutofillUserVerificationRequest } from "../../platform/main/autofill/user-verification.request"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; @@ -421,21 +421,19 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi this.logService.debug("Prompting for user verification"); - const uvResult = await ipc.autofill.runCommand({ - namespace: "autofill", - command: "user-verification", - params: { - windowHandle: Utils.fromBufferToB64(windowHandle), - transactionContext: this.transactionContext, + try { + const uvResult = await ipc.autofill.verifyUser({ + // windowHandle: Utils.fromBufferToB64(windowHandle), + // transactionContext: this.transactionContext, + transactionId: 0, username, displayHint, - }, - }); - if (uvResult.type === "error") { - this.logService.error("Error getting user verification", uvResult.error); + }); + return uvResult.userVerified; + } catch (err) { + this.logService.error("Error getting user verification", err); return false; } - return uvResult.type === "success"; } async informExcludedCredential(existingCipherIds: string[]): Promise { diff --git a/apps/desktop/src/platform/main/autofill/command.ts b/apps/desktop/src/platform/main/autofill/command.ts index 2549e617679..a8b5548052b 100644 --- a/apps/desktop/src/platform/main/autofill/command.ts +++ b/apps/desktop/src/platform/main/autofill/command.ts @@ -1,6 +1,5 @@ import { NativeAutofillStatusCommand } from "./status.command"; import { NativeAutofillSyncCommand } from "./sync.command"; -import { NativeAutofillUserVerificationCommand } from "./user-verification.command"; export type CommandDefinition = { namespace: string; @@ -21,4 +20,4 @@ export type IpcCommandInvoker = ( ) => Promise>; /** A list of all available commands */ -export type Command = NativeAutofillSyncCommand | NativeAutofillStatusCommand | NativeAutofillUserVerificationCommand; +export type Command = NativeAutofillSyncCommand | NativeAutofillStatusCommand; diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts index 0ba062a18bc..5dbde141a31 100644 --- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -6,6 +6,7 @@ import { autofill } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../../main/window.main"; import { CommandDefinition } from "./command"; +import { HostRequestDefinition } from "./request"; type BufferedMessage = { channel: string; @@ -20,6 +21,14 @@ export type RunCommandParams = { export type RunCommandResult = C["output"]; +export type HostRequestParams = { + namespace: R["namespace"]; + command: R["name"]; + params: R["input"]; +}; + +export type HostRequestResult = R["output"]; + export class NativeAutofillMain { private ipcServer?: autofill.AutofillIpcServer; private messageBuffer: BufferedMessage[] = []; @@ -70,6 +79,15 @@ export class NativeAutofillMain { }, ); + ipcMain.handle( + "autofill.userVerification", + ( + _event: any, + params: autofill.UserVerificationRequest, + ): Promise =>{ + return this.ipcServer.verifyUser(params) + }); + this.ipcServer = await autofill.AutofillIpcServer.listen( "af", // RegistrationCallback diff --git a/apps/desktop/src/platform/main/autofill/request.ts b/apps/desktop/src/platform/main/autofill/request.ts new file mode 100644 index 00000000000..9e374579ea2 --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/request.ts @@ -0,0 +1,14 @@ +/** + * Contains types for request/response messages from the host app to the provider over IPC. + */ +import { NativeAutofillUserVerificationRequest } from "./user-verification.request"; + +export type HostRequestDefinition = { + namespace: string; + name: string; + input: Record; + output: Record; +} + +/** A list of all available host requests */ +export type HostRequest = NativeAutofillUserVerificationRequest; \ No newline at end of file diff --git a/apps/desktop/src/platform/main/autofill/user-verification.command.ts b/apps/desktop/src/platform/main/autofill/user-verification.command.ts deleted file mode 100644 index 688e39f3bb8..00000000000 --- a/apps/desktop/src/platform/main/autofill/user-verification.command.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CommandDefinition, CommandOutput } from "./command"; - -export interface NativeAutofillUserVerificationCommand extends CommandDefinition { - name: "user-verification"; - input: NativeAutofillUserVerificationParams; - output: NativeAutofillUserVerificationResult; -} - -export type NativeAutofillUserVerificationParams = { - /** base64 string representing native window handle */ - windowHandle: string; - /** base64 string representing native transaction context */ - transactionContext: string; - displayHint: string; - username: string; -}; - - -export type NativeAutofillUserVerificationResult = CommandOutput<{}>; \ No newline at end of file diff --git a/apps/desktop/src/platform/main/autofill/user-verification.request.ts b/apps/desktop/src/platform/main/autofill/user-verification.request.ts new file mode 100644 index 00000000000..c1df81097ed --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/user-verification.request.ts @@ -0,0 +1,21 @@ +import { HostRequestDefinition } from "./request"; + +export interface NativeAutofillUserVerificationRequest extends HostRequestDefinition { + name: "user-verification"; + input: NativeAutofillUserVerificationParams; + output: NativeAutofillUserVerificationResult; +} + +export type NativeAutofillUserVerificationParams = { + // /** base64 string representing native window handle */ + // windowHandle: string; + // /** base64 string representing native transaction context */ + transactionId: number; + displayHint: string; + username: string; +}; + +export type NativeAutofillUserVerificationResult = { + /** Whether the user was verified. */ + userVerified: boolean +} \ No newline at end of file