1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 19:53:59 +00:00

Wire up client user verification over IPC channel

This commit is contained in:
Isaiah Inuwa
2026-01-09 07:30:10 -06:00
parent cb38151cac
commit 969900e22f
9 changed files with 141 additions and 33 deletions

View File

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

View File

@@ -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<u8>,
}
#[napi(object)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserVerificationRequest {
pub transaction_id: u32,
pub display_hint: String,
pub username: Option<String>,
}
#[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<UserVerificationResponse> {
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<u32> {
self.server

View File

@@ -14,6 +14,9 @@ export default {
runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> =>
ipcRenderer.invoke("autofill.runCommand", params),
verifyUser: (request: autofill.UserVerificationRequest): Promise<autofill.UserVerificationResponse> =>
ipcRenderer.invoke("autofill.userVerification", request),
listenerReady: () => ipcRenderer.send("autofill.listenerReady"),
listenPasskeyRegistration: (

View File

@@ -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<NativeAutofillUserVerificationCommand>({
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<void> {

View File

@@ -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<C extends CommandDefinition> = (
) => Promise<CommandOutput<C["output"]>>;
/** A list of all available commands */
export type Command = NativeAutofillSyncCommand | NativeAutofillStatusCommand | NativeAutofillUserVerificationCommand;
export type Command = NativeAutofillSyncCommand | NativeAutofillStatusCommand;

View File

@@ -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<C extends CommandDefinition> = {
export type RunCommandResult<C extends CommandDefinition> = C["output"];
export type HostRequestParams<R extends HostRequestDefinition> = {
namespace: R["namespace"];
command: R["name"];
params: R["input"];
};
export type HostRequestResult<R extends HostRequestDefinition> = 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<autofill.UserVerificationResponse> =>{
return this.ipcServer.verifyUser(params)
});
this.ipcServer = await autofill.AutofillIpcServer.listen(
"af",
// RegistrationCallback

View File

@@ -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<string, unknown>;
output: Record<string, unknown>;
}
/** A list of all available host requests */
export type HostRequest = NativeAutofillUserVerificationRequest;

View File

@@ -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<{}>;

View File

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