diff --git a/apps/desktop/desktop_native/macos_provider/src/assertion.rs b/apps/desktop/desktop_native/macos_provider/src/assertion.rs index 762ceaaed48..c5b43bb87fa 100644 --- a/apps/desktop/desktop_native/macos_provider/src/assertion.rs +++ b/apps/desktop/desktop_native/macos_provider/src/assertion.rs @@ -2,11 +2,22 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; -use crate::{BitwardenError, Callback, UserVerification}; +use crate::{BitwardenError, Callback, Position, UserVerification}; #[derive(uniffi::Record, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PasskeyAssertionRequest { + rp_id: String, + client_data_hash: Vec, + user_verification: UserVerification, + allowed_credentials: Vec>, + window_xy: Position, + //extension_input: Vec, TODO: Implement support for extensions +} + +#[derive(uniffi::Record, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyAssertionWithoutUserInterfaceRequest { rp_id: String, credential_id: Vec, user_name: String, @@ -14,6 +25,7 @@ pub struct PasskeyAssertionRequest { record_identifier: Option, client_data_hash: Vec, user_verification: UserVerification, + window_xy: Position, } #[derive(uniffi::Record, Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs index 5623436d874..8f2499ae68d 100644 --- a/apps/desktop/desktop_native/macos_provider/src/lib.rs +++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs @@ -15,7 +15,10 @@ uniffi::setup_scaffolding!(); mod assertion; mod registration; -use assertion::{PasskeyAssertionRequest, PreparePasskeyAssertionCallback}; +use assertion::{ + PasskeyAssertionRequest, PasskeyAssertionWithoutUserInterfaceRequest, + PreparePasskeyAssertionCallback, +}; use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback}; #[derive(uniffi::Enum, Debug, Serialize, Deserialize)] @@ -26,6 +29,13 @@ pub enum UserVerification { Discouraged, } +#[derive(uniffi::Record, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Position { + pub x: i32, + pub y: i32, +} + #[derive(Debug, uniffi::Error, Serialize, Deserialize)] pub enum BitwardenError { Internal(String), @@ -141,6 +151,14 @@ impl MacOSProviderClient { ) { self.send_message(request, Box::new(callback)); } + + pub fn prepare_passkey_assertion_without_user_interface( + &self, + request: PasskeyAssertionWithoutUserInterfaceRequest, + callback: Arc, + ) { + self.send_message(request, Box::new(callback)); + } } #[derive(Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/macos_provider/src/registration.rs b/apps/desktop/desktop_native/macos_provider/src/registration.rs index d484af58b6c..9e697b75c16 100644 --- a/apps/desktop/desktop_native/macos_provider/src/registration.rs +++ b/apps/desktop/desktop_native/macos_provider/src/registration.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; -use crate::{BitwardenError, Callback, UserVerification}; +use crate::{BitwardenError, Callback, Position, UserVerification}; #[derive(uniffi::Record, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -13,6 +13,7 @@ pub struct PasskeyRegistrationRequest { client_data_hash: Vec, user_verification: UserVerification, supported_algorithms: Vec, + window_xy: Position, } #[derive(uniffi::Record, Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 92f31cf5f89..ca1fe29e254 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -118,6 +118,10 @@ export declare namespace autofill { Required = 'required', Discouraged = 'discouraged' } + export interface Position { + x: number + y: number + } export interface PasskeyRegistrationRequest { rpId: string userName: string @@ -125,6 +129,7 @@ export declare namespace autofill { clientDataHash: Array userVerification: UserVerification supportedAlgorithms: Array + windowXy: Position } export interface PasskeyRegistrationResponse { rpId: string @@ -133,6 +138,13 @@ export declare namespace autofill { attestationObject: Array } export interface PasskeyAssertionRequest { + rpId: string + clientDataHash: Array + userVerification: UserVerification + allowedCredentials: Array> + windowXy: Position + } + export interface PasskeyAssertionWithoutUserInterfaceRequest { rpId: string credentialId: Array userName: string @@ -140,6 +152,7 @@ export declare namespace autofill { recordIdentifier?: string clientDataHash: Array userVerification: UserVerification + windowXy: Position } export interface PasskeyAssertionResponse { rpId: string @@ -156,7 +169,7 @@ export declare namespace autofill { * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. * @param callback This function will be called whenever a message is received from a client. */ - static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void): Promise + static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void): Promise /** Return the path to the IPC server. */ getPath(): string /** Stop the IPC server. */ diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index d0c859d427c..f02be2b27b6 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -515,6 +515,14 @@ pub mod autofill { pub value: Result, } + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Position { + pub x: i32, + pub y: i32, + } + #[napi(object)] #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -525,6 +533,7 @@ pub mod autofill { pub client_data_hash: Vec, pub user_verification: UserVerification, pub supported_algorithms: Vec, + pub window_xy: Position, } #[napi(object)] @@ -541,6 +550,18 @@ pub mod autofill { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PasskeyAssertionRequest { + pub rp_id: String, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + pub allowed_credentials: Vec>, + pub window_xy: Position, + //extension_input: Vec, TODO: Implement support for extensions + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionWithoutUserInterfaceRequest { pub rp_id: String, pub credential_id: Vec, pub user_name: String, @@ -548,6 +569,7 @@ pub mod autofill { pub record_identifier: Option, pub client_data_hash: Vec, pub user_verification: UserVerification, + pub window_xy: Position, } #[napi(object)] @@ -592,6 +614,13 @@ pub mod autofill { (u32, u32, PasskeyAssertionRequest), ErrorStrategy::CalleeHandled, >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void" + )] + assertion_without_user_interface_callback: ThreadsafeFunction< + (u32, u32, PasskeyAssertionWithoutUserInterfaceRequest), + ErrorStrategy::CalleeHandled, + >, ) -> napi::Result { let (send, mut recv) = tokio::sync::mpsc::channel::(32); tokio::spawn(async move { @@ -628,6 +657,25 @@ pub mod autofill { } } + match serde_json::from_str::< + PasskeyMessage, + >(&message) + { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value)) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + + assertion_without_user_interface_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + println!("[ERROR] Error deserializing message1: {e}"); + } + } + match serde_json::from_str::>( &message, ) { diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib index ace3497a58b..1e47cc54de2 100644 --- a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib +++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib @@ -1,22 +1,23 @@ - + - + + - + - +