Skip to main content

bitwarden_shared_unlock/wasm/
drivers.rs

1use bitwarden_core::UserId;
2use bitwarden_crypto::SymmetricCryptoKey;
3use bitwarden_ipc::{Endpoint, HostId};
4use bitwarden_threading::ThreadBoundRunner;
5use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
6use wasm_bindgen_futures::js_sys;
7
8use crate::SharedUnlockDriver;
9
10#[wasm_bindgen(typescript_custom_section)]
11const TS_CUSTOM_TYPES: &'static str = r#"
12export interface SharedUnlockDriver {
13    lock_user(user_id: UserId): Promise<void>;
14    unlock_user(user_id: UserId, user_key: SymmetricKey): Promise<void>;
15    list_users(): Promise<UserId[]>;
16    suppress_vault_timeout(user_id: UserId, suppression_duration: number): Promise<void>;
17    get_client_name(): Promise<string>;
18    get_vault_url(user_id: UserId): Promise<string | undefined>;
19}
20"#;
21
22#[wasm_bindgen]
23extern "C" {
24    /// JavaScript implementation of shared unlock operations used by shared unlock protocol.
25    #[wasm_bindgen(js_name = SharedUnlockDriver, typescript_type = "SharedUnlockDriver")]
26    pub type RawJsSharedUnlockDriver;
27
28    #[wasm_bindgen(method, catch)]
29    async fn lock_user(this: &RawJsSharedUnlockDriver, user_id: UserId) -> Result<(), JsValue>;
30    #[wasm_bindgen(method, catch)]
31    async fn unlock_user(
32        this: &RawJsSharedUnlockDriver,
33        user_id: UserId,
34        user_key: SymmetricCryptoKey,
35    ) -> Result<(), JsValue>;
36    #[wasm_bindgen(method, catch)]
37    async fn list_users(this: &RawJsSharedUnlockDriver) -> Result<js_sys::Array, JsValue>;
38
39    /// Supress the vault timeout for the given duration (in milliseconds).
40    #[wasm_bindgen(method, catch)]
41    async fn suppress_vault_timeout(
42        this: &RawJsSharedUnlockDriver,
43        user_id: UserId,
44        suppression_duration: f64,
45    ) -> Result<(), JsValue>;
46
47    /// Get the client type of the current device
48    #[wasm_bindgen(method, catch)]
49    async fn get_client_name(this: &RawJsSharedUnlockDriver) -> Result<JsValue, JsValue>;
50
51    /// Get vault URL for the user with the given ID, if available. This is used to verify IPC
52    /// message sources.
53    #[wasm_bindgen(method, catch)]
54    async fn get_vault_url(
55        this: &RawJsSharedUnlockDriver,
56        user_id: UserId,
57    ) -> Result<JsValue, JsValue>;
58}
59
60pub(super) struct JsSharedUnlockDriver {
61    runner: ThreadBoundRunner<RawJsSharedUnlockDriver>,
62}
63
64impl JsSharedUnlockDriver {
65    pub(super) fn new(driver: RawJsSharedUnlockDriver) -> Self {
66        Self {
67            runner: ThreadBoundRunner::new(driver),
68        }
69    }
70}
71
72async fn list_users(driver: &RawJsSharedUnlockDriver) -> Vec<UserId> {
73    match driver.list_users().await {
74        Ok(array) => array
75            .iter()
76            .filter_map(|js_value| js_value.as_string())
77            .filter_map(|s| s.parse().ok())
78            .collect(),
79        Err(error) => {
80            tracing::error!(?error, "Failed to list users");
81            vec![]
82        }
83    }
84}
85
86#[async_trait::async_trait]
87impl SharedUnlockDriver for JsSharedUnlockDriver {
88    async fn lock_user(&self, user_id: UserId) -> Result<(), ()> {
89        self.runner
90            .run_in_thread(
91                move |driver| async move { driver.lock_user(user_id).await.map_err(|_| ()) },
92            )
93            .await
94            .map_err(|_| ())?
95    }
96
97    async fn unlock_user(&self, user_id: UserId, user_key: SymmetricCryptoKey) -> Result<(), ()> {
98        self.runner
99            .run_in_thread(move |driver| async move {
100                driver.unlock_user(user_id, user_key).await.map_err(|_| ())
101            })
102            .await
103            .map_err(|_| ())?
104    }
105
106    async fn list_users(&self) -> Vec<UserId> {
107        self.runner
108            .run_in_thread(move |driver| async move { list_users(&driver).await })
109            .await
110            .unwrap_or_default()
111    }
112
113    async fn get_vault_url(&self, user_id: UserId) -> Option<String> {
114        self.runner
115            .run_in_thread(move |driver| async move {
116                driver
117                    .get_vault_url(user_id)
118                    .await
119                    .ok()
120                    .and_then(|js_value| js_value.as_string())
121            })
122            .await
123            .ok()
124            .flatten()
125    }
126
127    async fn suppress_vault_timeout(
128        &self,
129        user_id: UserId,
130        suppression_duration: std::time::Duration,
131    ) {
132        let result = self
133            .runner
134            .run_in_thread(move |driver| async move {
135                driver
136                    .suppress_vault_timeout(user_id, suppression_duration.as_millis() as f64)
137                    .await
138            })
139            .await;
140        match result {
141            Ok(Ok(())) => {}
142            Ok(Err(error)) => {
143                tracing::error!(
144                    ?error,
145                    "Failed to suppress vault timeout for user_id: {}",
146                    user_id
147                )
148            }
149            Err(error) => {
150                tracing::error!(
151                    ?error,
152                    "Failed to suppress vault timeout for user_id: {}",
153                    user_id
154                )
155            }
156        }
157    }
158
159    async fn discover_leader(&self) -> Option<Endpoint> {
160        self.runner
161            .run_in_thread(move |driver| async move {
162                let client_name = match driver.get_client_name().await {
163                    Ok(name) => name.as_string()?,
164                    Err(_) => return None,
165                };
166                match client_name.as_str() {
167                    "web" => Some(Endpoint::BrowserBackground { id: HostId::Own }),
168                    "browser" => Some(Endpoint::DesktopRenderer),
169                    "cli" => Some(Endpoint::DesktopRenderer),
170                    _ => None,
171                }
172            })
173            .await
174            .ok()
175            .flatten()
176    }
177}