bitwarden_shared_unlock/wasm/
drivers.rs1use 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 #[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 #[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 #[wasm_bindgen(method, catch)]
49 async fn get_client_name(this: &RawJsSharedUnlockDriver) -> Result<JsValue, JsValue>;
50
51 #[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}