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::{LockState, 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 get_user_key(user_id: UserId): Promise<SymmetricKey | undefined>;
17 suppress_vault_timeout(user_id: UserId, suppression_duration: number): Promise<void>;
18 get_client_name(): Promise<string>;
19 get_vault_url(user_id: UserId): Promise<string | undefined>;
20}
21"#;
22
23#[wasm_bindgen]
24extern "C" {
25 #[wasm_bindgen(js_name = SharedUnlockDriver, typescript_type = "SharedUnlockDriver")]
27 pub type RawJsSharedUnlockDriver;
28
29 #[wasm_bindgen(method, catch)]
30 async fn lock_user(this: &RawJsSharedUnlockDriver, user_id: UserId) -> Result<(), JsValue>;
31 #[wasm_bindgen(method, catch)]
32 async fn unlock_user(
33 this: &RawJsSharedUnlockDriver,
34 user_id: UserId,
35 user_key: SymmetricCryptoKey,
36 ) -> Result<(), JsValue>;
37 #[wasm_bindgen(method, catch)]
38 async fn list_users(this: &RawJsSharedUnlockDriver) -> Result<js_sys::Array, JsValue>;
39 #[wasm_bindgen(method, catch)]
40 async fn get_user_key(
41 this: &RawJsSharedUnlockDriver,
42 user_id: UserId,
43 ) -> Result<JsValue, JsValue>;
44
45 #[wasm_bindgen(method, catch)]
47 async fn suppress_vault_timeout(
48 this: &RawJsSharedUnlockDriver,
49 user_id: UserId,
50 suppression_duration: f64,
51 ) -> Result<(), JsValue>;
52
53 #[wasm_bindgen(method, catch)]
55 async fn get_client_name(this: &RawJsSharedUnlockDriver) -> Result<JsValue, JsValue>;
56
57 #[wasm_bindgen(method, catch)]
60 async fn get_vault_url(
61 this: &RawJsSharedUnlockDriver,
62 user_id: UserId,
63 ) -> Result<JsValue, JsValue>;
64}
65
66pub(super) struct JsSharedUnlockDriver {
67 runner: ThreadBoundRunner<RawJsSharedUnlockDriver>,
68}
69
70impl JsSharedUnlockDriver {
71 pub(super) fn new(driver: RawJsSharedUnlockDriver) -> Self {
72 Self {
73 runner: ThreadBoundRunner::new(driver),
74 }
75 }
76}
77
78#[async_trait::async_trait]
79impl SharedUnlockDriver for JsSharedUnlockDriver {
80 async fn lock_user(&self, user_id: UserId) -> Result<(), ()> {
81 self.runner
82 .run_in_thread(
83 move |driver| async move { driver.lock_user(user_id).await.map_err(|_| ()) },
84 )
85 .await
86 .map_err(|_| ())?
87 }
88
89 async fn unlock_user(&self, user_id: UserId, user_key: SymmetricCryptoKey) -> Result<(), ()> {
90 self.runner
91 .run_in_thread(move |driver| async move {
92 driver.unlock_user(user_id, user_key).await.map_err(|_| ())
93 })
94 .await
95 .map_err(|_| ())?
96 }
97
98 async fn list_users(&self) -> Vec<UserId> {
99 self.runner
100 .run_in_thread(move |driver| async move {
101 match driver.list_users().await {
102 Ok(array) => array
103 .iter()
104 .filter_map(|js_value| js_value.as_string())
105 .filter_map(|s| s.parse().ok())
106 .collect(),
107 Err(_) => vec![],
108 }
109 })
110 .await
111 .unwrap_or_default()
112 }
113
114 async fn get_user_lock_state(&self, user_id: UserId) -> LockState {
115 self.runner
116 .run_in_thread(move |driver| async move {
117 let js_value = driver.get_user_key(user_id).await.ok()?;
118 if js_value.is_null() || js_value.is_undefined() {
119 return None;
120 }
121 let user_key = SymmetricCryptoKey::try_from(js_value).ok()?;
122 Some(LockState::Unlocked { user_key })
123 })
124 .await
125 .ok()
126 .flatten()
127 .unwrap_or(LockState::Locked)
128 }
129
130 async fn get_vault_url(&self, user_id: UserId) -> Option<String> {
131 self.runner
132 .run_in_thread(move |driver| async move {
133 driver
134 .get_vault_url(user_id)
135 .await
136 .ok()
137 .and_then(|js_value| js_value.as_string())
138 })
139 .await
140 .ok()
141 .flatten()
142 }
143
144 async fn suppress_vault_timeout(
145 &self,
146 user_id: UserId,
147 suppression_duration: std::time::Duration,
148 ) {
149 let result = self
150 .runner
151 .run_in_thread(move |driver| async move {
152 driver
153 .suppress_vault_timeout(user_id, suppression_duration.as_millis() as f64)
154 .await
155 })
156 .await;
157 match result {
158 Ok(Ok(())) => {}
159 Ok(Err(error)) => {
160 tracing::error!(
161 ?error,
162 "Failed to suppress vault timeout for user_id: {}",
163 user_id
164 )
165 }
166 Err(error) => {
167 tracing::error!(
168 ?error,
169 "Failed to suppress vault timeout for user_id: {}",
170 user_id
171 )
172 }
173 }
174 }
175
176 async fn discover_leader(&self) -> Option<Endpoint> {
177 self.runner
178 .run_in_thread(move |driver| async move {
179 let client_name = match driver.get_client_name().await {
180 Ok(name) => name.as_string()?,
181 Err(_) => return None,
182 };
183 match client_name.as_str() {
184 "web" => Some(Endpoint::BrowserBackground { id: HostId::Own }),
185 "browser" => Some(Endpoint::DesktopRenderer),
186 "cli" => Some(Endpoint::DesktopRenderer),
187 _ => None,
188 }
189 })
190 .await
191 .ok()
192 .flatten()
193 }
194}