Skip to content

Commit

Permalink
Add messaging for macos provider
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Dec 5, 2024
1 parent 5a9cc88 commit 212519a
Show file tree
Hide file tree
Showing 28 changed files with 1,122 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { BrowserApi } from "../../../platform/browser/browser-api";
import { BrowserScriptInjectorService } from "../../../platform/services/browser-script-injector.service";
import { AbortManager } from "../../../vault/background/abort-manager";
import { BrowserFido2ParentWindowReference } from "../../../vault/fido2/browser-fido2-user-interface.service";
import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum";
import { Fido2PortName } from "../enums/fido2-port-name.enum";

Expand Down Expand Up @@ -56,7 +57,7 @@ describe("Fido2Background", () => {
let senderMock!: MockProxy<chrome.runtime.MessageSender>;
let logService!: MockProxy<LogService>;
let fido2ActiveRequestManager: MockProxy<Fido2ActiveRequestManager>;
let fido2ClientService!: MockProxy<Fido2ClientService>;
let fido2ClientService!: MockProxy<Fido2ClientService<BrowserFido2ParentWindowReference>>;
let vaultSettingsService!: MockProxy<VaultSettingsService>;
let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>;
let configServiceMock!: MockProxy<ConfigService>;
Expand All @@ -73,7 +74,7 @@ describe("Fido2Background", () => {
});
senderMock = mock<chrome.runtime.MessageSender>({ id: "1", tab: tabMock });
logService = mock<LogService>();
fido2ClientService = mock<Fido2ClientService>();
fido2ClientService = mock<Fido2ClientService<BrowserFido2ParentWindowReference>>();
vaultSettingsService = mock<VaultSettingsService>();
abortManagerMock = mock<AbortManager>();
abortController = mock<AbortController>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault
import { BrowserApi } from "../../../platform/browser/browser-api";
import { ScriptInjectorService } from "../../../platform/services/abstractions/script-injector.service";
import { AbortManager } from "../../../vault/background/abort-manager";
import { BrowserFido2ParentWindowReference } from "../../../vault/fido2/browser-fido2-user-interface.service";

Check failure on line 22 in apps/browser/src/autofill/fido2/background/fido2.background.ts

View workflow job for this annotation

GitHub Actions / Chromatic

Cannot find module '../../../vault/fido2/browser-fido2-user-interface.service' or its corresponding type declarations.
import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum";
import { Fido2PortName } from "../enums/fido2-port-name.enum";

import {
Fido2Background as Fido2BackgroundInterface,
Fido2BackgroundExtensionMessageHandlers,
Fido2Background as Fido2BackgroundInterface,
Fido2ExtensionMessage,
SharedFido2ScriptInjectionDetails,
SharedFido2ScriptRegistrationOptions,
Expand Down Expand Up @@ -54,7 +55,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
constructor(
private logService: LogService,
private fido2ActiveRequestManager: Fido2ActiveRequestManager,
private fido2ClientService: Fido2ClientService,
private fido2ClientService: Fido2ClientService<BrowserFido2ParentWindowReference>,
private vaultSettingsService: VaultSettingsService,
private scriptInjectorService: ScriptInjectorService,
private configService: ConfigService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,15 @@ export type BrowserFido2Message = { sessionId: string } & (
}
);

export type BrowserFido2ParentWindowReference = chrome.tabs.Tab;

/**
* Browser implementation of the {@link Fido2UserInterfaceService}.
* The user interface is implemented as a popout and the service uses the browser's messaging API to communicate with it.
*/
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
export class BrowserFido2UserInterfaceService
implements Fido2UserInterfaceServiceAbstraction<BrowserFido2ParentWindowReference>
{
constructor(private authService: AuthService) {}

async newSession(
Expand Down
15 changes: 8 additions & 7 deletions apps/browser/src/background/main.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/a
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";

Check failure on line 35 in apps/browser/src/background/main.background.ts

View workflow job for this annotation

GitHub Actions / Lint

`@bitwarden/common/auth/abstractions/devices/devices.service.abstraction` import should occur before import of `@bitwarden/common/auth/abstractions/devices-api.service.abstraction`
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
Expand All @@ -45,8 +45,8 @@ import { AccountServiceImplementation } from "@bitwarden/common/auth/services/ac
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";

Check failure on line 49 in apps/browser/src/background/main.background.ts

View workflow job for this annotation

GitHub Actions / Lint

`@bitwarden/common/auth/services/devices/devices.service.implementation` import should occur before import of `@bitwarden/common/auth/services/devices-api.service.implementation`
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
Expand Down Expand Up @@ -199,11 +199,11 @@ import {
ImportServiceAbstraction,
} from "@bitwarden/importer/core";
import {
DefaultKdfConfigService,
KdfConfigService,
BiometricStateService,
BiometricsService,
DefaultBiometricStateService,
DefaultKdfConfigService,
KdfConfigService,
KeyService as KeyServiceAbstraction,
} from "@bitwarden/key-management";
import {
Expand Down Expand Up @@ -264,6 +264,7 @@ import { SyncServiceListener } from "../platform/sync/sync-service.listener";
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
import FilelessImporterBackground from "../tools/background/fileless-importer.background";
import { BrowserFido2ParentWindowReference } from "../vault/fido2/browser-fido2-user-interface.service";

Check failure on line 267 in apps/browser/src/background/main.background.ts

View workflow job for this annotation

GitHub Actions / Chromatic

Cannot find module '../vault/fido2/browser-fido2-user-interface.service' or its corresponding type declarations.
import { VaultFilterService } from "../vault/services/vault-filter.service";

import CommandsBackground from "./commands.background";
Expand Down Expand Up @@ -335,10 +336,10 @@ export default class MainBackground {
policyApiService: PolicyApiServiceAbstraction;
sendApiService: SendApiServiceAbstraction;
userVerificationApiService: UserVerificationApiServiceAbstraction;
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction<BrowserFido2ParentWindowReference>;
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction<BrowserFido2ParentWindowReference>;
fido2ActiveRequestManager: Fido2ActiveRequestManagerAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction<BrowserFido2ParentWindowReference>;
avatarService: AvatarServiceAbstraction;
mainContextMenuHandler: MainContextMenuHandler;
cipherContextMenuHandler: CipherContextMenuHandler;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/desktop_native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions apps/desktop/desktop_native/macos_provider/src/assertion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::sync::Arc;

use serde::{Deserialize, Serialize};

use crate::{BitwardenError, Callback, UserVerification};

#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyAssertionRequest {
relying_party_id: String,
credential_id: Vec<u8>,
user_name: String,
user_handle: Vec<u8>,
record_identifier: Option<String>,
client_data_hash: Vec<u8>,
user_verification: UserVerification,
}

#[derive(uniffi::Record, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyAssertionResponse {
relying_party_id: String,
user_handle: Vec<u8>,
signature: Vec<u8>,
client_data_hash: Vec<u8>,
authenticator_data: Vec<u8>,
credential_id: Vec<u8>,
}

#[uniffi::export(with_foreign)]
pub trait PreparePasskeyAssertionCallback: Send + Sync {
fn on_complete(&self, credential: PasskeyAssertionResponse);
fn on_error(&self, error: BitwardenError);
}

impl Callback for Arc<dyn PreparePasskeyAssertionCallback> {
fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error> {
let credential = serde_json::from_value(credential)?;
PreparePasskeyAssertionCallback::on_complete(self.as_ref(), credential);
Ok(())
}

fn error(&self, error: BitwardenError) {
PreparePasskeyAssertionCallback::on_error(self.as_ref(), error);
}
}
79 changes: 57 additions & 22 deletions apps/desktop/desktop_native/macos_provider/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
use std::{
collections::HashMap,
sync::{atomic::AtomicU64, Arc, Mutex},
sync::{atomic::AtomicU32, Arc, Mutex},
time::Instant,
};

use futures::FutureExt;
use log::{error, info, warn};
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

uniffi::setup_scaffolding!();

mod assertion;
mod registration;

use assertion::{PasskeyAssertionRequest, PreparePasskeyAssertionCallback};
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};

#[derive(uniffi::Enum, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum UserVerification {
Preferred,
Required,
Discouraged,
}

#[derive(uniffi::Error, Serialize, Deserialize)]
#[derive(Debug, uniffi::Error, Serialize, Deserialize)]
pub enum BitwardenError {
Internal(String),
}
Expand All @@ -37,8 +42,9 @@ pub struct MacOSProviderClient {
to_server_send: tokio::sync::mpsc::Sender<String>,

// We need to keep track of the callbacks so we can call them when we receive a response
response_callbacks_counter: AtomicU64,
response_callbacks_queue: Arc<Mutex<HashMap<u64, Box<dyn Callback>>>>,
response_callbacks_counter: AtomicU32,
#[allow(clippy::type_complexity)]
response_callbacks_queue: Arc<Mutex<HashMap<u32, (Box<dyn Callback>, Instant)>>>,
}

#[uniffi::export]
Expand All @@ -54,13 +60,14 @@ impl MacOSProviderClient {

let client = MacOSProviderClient {
to_server_send,
response_callbacks_counter: AtomicU64::new(0),
response_callbacks_counter: AtomicU32::new(0),
response_callbacks_queue: Arc::new(Mutex::new(HashMap::new())),
};

let path = desktop_core::ipc::path("autofill");

let queue = client.response_callbacks_queue.clone();

std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
Expand All @@ -75,30 +82,41 @@ impl MacOSProviderClient {
rt.block_on(async move {
while let Some(message) = from_server_recv.recv().await {
match serde_json::from_str::<SerializedMessage>(&message) {
Ok(SerializedMessage::Connected) => {
Ok(SerializedMessage::Command(CommandMessage::Connected)) => {
info!("Connected to server");
}
Ok(SerializedMessage::Disconnected) => {
Ok(SerializedMessage::Command(CommandMessage::Disconnected)) => {
info!("Disconnected from server");
}
Ok(SerializedMessage::Message {
sequence_number,
value,
}) => match queue.lock().unwrap().remove(&sequence_number) {
Some(cb) => match value {
Ok(value) => {
if let Err(e) = cb.complete(value) {
error!("Error deserializing message: {}", e);
Some((cb, request_start_time)) => {
warn!("Received request: {sequence_number} {value:?}");

info!(
"Time to process request: {:?}",
request_start_time.elapsed()
);
match value {
Ok(value) => {
if let Err(e) = cb.complete(value) {
error!("Error deserializing message: {e}");
}
}
Err(e) => {
error!("Error processing message: {e:?}");
cb.error(e)
}
}
Err(e) => cb.error(e),
},
}
None => {
error!("No callback found for sequence number: {}", sequence_number)
error!("No callback found for sequence number: {sequence_number}")
}
},
Err(e) => {
error!("Error deserializing message: {}", e);
error!("Error deserializing message: {e}");
}
};
}
Expand All @@ -117,28 +135,45 @@ impl MacOSProviderClient {

self.send_message(request, Box::new(callback));
}

pub fn prepare_passkey_assertion(
&self,
request: PasskeyAssertionRequest,
callback: Arc<dyn PreparePasskeyAssertionCallback>,
) {
warn!("prepare_passkey_assertion: {:?}", request);

self.send_message(request, Box::new(callback));
}
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "command")]
enum SerializedMessage {
#[serde(tag = "command", rename_all = "camelCase")]
enum CommandMessage {
Connected,
Disconnected,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
enum SerializedMessage {
Command(CommandMessage),
Message {
sequence_number: u64,
sequence_number: u32,
value: Result<serde_json::Value, BitwardenError>,
},
}

impl MacOSProviderClient {
fn add_callback(&self, callback: Box<dyn Callback>) -> u64 {
fn add_callback(&self, callback: Box<dyn Callback>) -> u32 {
let sequence_number = self
.response_callbacks_counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);

self.response_callbacks_queue
.lock()
.unwrap()
.insert(sequence_number, callback);
.insert(sequence_number, (callback, Instant::now()));

sequence_number
}
Expand All @@ -158,7 +193,7 @@ impl MacOSProviderClient {

if let Err(e) = self.to_server_send.blocking_send(message) {
// Make sure we remove the callback from the queue if we can't send the message
if let Some(cb) = self
if let Some((cb, _)) = self
.response_callbacks_queue
.lock()
.unwrap()
Expand Down
10 changes: 6 additions & 4 deletions apps/desktop/desktop_native/macos_provider/src/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,28 @@ use serde::{Deserialize, Serialize};
use crate::{BitwardenError, Callback, UserVerification};

#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyRegistrationRequest {
relying_party_id: String,
user_name: String,
user_handle: Vec<u8>,

client_data_hash: Vec<u8>,
user_verification: UserVerification,
supported_algorithms: Vec<i32>,
}

#[derive(uniffi::Record, Serialize, Deserialize)]
pub struct PasskeyRegistrationCredential {
relying_party: String,
#[serde(rename_all = "camelCase")]
pub struct PasskeyRegistrationResponse {
relying_party_id: String,
client_data_hash: Vec<u8>,
credential_id: Vec<u8>,
attestation_object: Vec<u8>,
}

#[uniffi::export(with_foreign)]
pub trait PreparePasskeyRegistrationCallback: Send + Sync {
fn on_complete(&self, credential: PasskeyRegistrationCredential);
fn on_complete(&self, credential: PasskeyRegistrationResponse);
fn on_error(&self, error: BitwardenError);
}

Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/desktop_native/napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ anyhow = "=1.0.93"
desktop_core = { path = "../core" }
napi = { version = "=2.16.13", features = ["async"] }
napi-derive = "=2.16.13"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127"
tokio = { version = "=1.41.1" }
tokio-util = "=0.7.12"
tokio-stream = "=0.1.15"
Expand Down
Loading

0 comments on commit 212519a

Please sign in to comment.