bitwarden_shared_unlock/wasm/
biometrics.rs1use bitwarden_core::UserId;
10use bitwarden_ipc::{Endpoint, IpcClientExt, RequestError, RpcHandler, RpcRequest};
11use bitwarden_threading::{
12 ThreadBoundRunner,
13 cancellation_token::wasm::{AbortSignal, AbortSignalExt},
14};
15use serde::{Deserialize, Serialize};
16use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
17
18#[wasm_bindgen(typescript_custom_section)]
19const TS_BIOMETRICS_TYPES: &'static str = r#"
20export interface BiometricsUnlock {
21 /**
22 * Returns the status of biometrics unlock for the given user.
23 */
24 get_biometrics_status(user_id: UserId): Promise<BiometricsStatus>;
25 /**
26 * Triggers a biometric unlock flow for the given user.
27 */
28 unlock_biometrics(user_id: UserId): Promise<void>;
29 /**
30 * Triggers a biometrics UV check. Retruns true if the check succeeded.
31 */
32 authenticate_biometrics(): Promise<boolean>;
33}
34"#;
35
36#[wasm_bindgen]
37extern "C" {
38 #[wasm_bindgen(js_name = BiometricsUnlock, typescript_type = "BiometricsUnlock")]
40 pub type RawJsBiometricsDriver;
41
42 #[wasm_bindgen(method, catch)]
44 async fn get_biometrics_status(
45 this: &RawJsBiometricsDriver,
46 user_id: UserId,
47 ) -> Result<JsValue, JsValue>;
48
49 #[wasm_bindgen(method, catch)]
51 async fn unlock_biometrics(
52 this: &RawJsBiometricsDriver,
53 user_id: UserId,
54 ) -> Result<(), JsValue>;
55
56 #[wasm_bindgen(method, catch)]
58 async fn authenticate_biometrics(this: &RawJsBiometricsDriver) -> Result<bool, JsValue>;
59}
60
61pub(super) struct JsBiometricsUnlock {
62 runner: ThreadBoundRunner<RawJsBiometricsDriver>,
63}
64
65impl JsBiometricsUnlock {
66 pub(super) fn new(runner: ThreadBoundRunner<RawJsBiometricsDriver>) -> Self {
67 Self { runner }
68 }
69
70 pub(super) async fn get_biometrics_status(&self, user_id: UserId) -> BiometricsStatus {
71 self.runner
72 .run_in_thread(move |driver| async move {
73 let status = driver
74 .get_biometrics_status(user_id)
75 .await
76 .unwrap_or(BiometricsStatus::NotEnabled.into());
77 status.try_into().unwrap_or(BiometricsStatus::NotEnabled)
78 })
79 .await
80 .unwrap_or(BiometricsStatus::NotEnabled)
81 }
82
83 pub(super) async fn unlock_biometrics(&self, user_id: UserId) {
84 self.runner
85 .run_in_thread(move |driver| async move {
86 driver.unlock_biometrics(user_id).await.unwrap_or(())
87 })
88 .await
89 .unwrap_or(())
90 }
91
92 pub(super) async fn authenticate_biometrics(&self) -> bool {
93 self.runner
94 .run_in_thread(move |driver| async move {
95 driver.authenticate_biometrics().await.unwrap_or(false)
96 })
97 .await
98 .unwrap_or(false)
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103#[wasm_bindgen]
104pub enum BiometricsStatus {
106 Available,
108 UnlockNeeded,
110 HardwareUnavailable,
112 NotEnabled,
114}
115
116impl TryFrom<JsValue> for BiometricsStatus {
117 type Error = ();
118
119 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
120 let status = value.as_f64().ok_or(())? as u8;
121 match status {
122 0 => Ok(BiometricsStatus::Available),
123 1 => Ok(BiometricsStatus::UnlockNeeded),
124 2 => Ok(BiometricsStatus::HardwareUnavailable),
125 3 => Ok(BiometricsStatus::NotEnabled),
126 _ => Err(()),
127 }
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct GetBiometricsStatusRequest {
134 pub user_id: UserId,
136}
137
138impl RpcRequest for GetBiometricsStatusRequest {
139 type Response = BiometricsStatus;
140
141 const NAME: &str = "GetBiometricsStatus";
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct UnlockBiometricsRequest {
147 pub user_id: UserId,
149}
150
151impl RpcRequest for UnlockBiometricsRequest {
152 type Response = ();
153
154 const NAME: &str = "UnlockBiometrics";
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct AuthenticateBiometricsRequest;
160
161impl RpcRequest for AuthenticateBiometricsRequest {
162 type Response = bool;
163
164 const NAME: &str = "AuthenticateBiometrics";
165}
166
167pub struct GetBiometricsStatusHandler {
169 biometrics_unlock: JsBiometricsUnlock,
170}
171
172impl GetBiometricsStatusHandler {
173 fn new(biometrics_unlock: JsBiometricsUnlock) -> Self {
175 Self { biometrics_unlock }
176 }
177}
178
179impl RpcHandler for GetBiometricsStatusHandler {
180 type Request = GetBiometricsStatusRequest;
181
182 async fn handle(&self, request: Self::Request) -> BiometricsStatus {
183 self.biometrics_unlock
184 .get_biometrics_status(request.user_id)
185 .await
186 }
187}
188
189pub struct UnlockBiometricsHandler {
191 biometrics_unlock: JsBiometricsUnlock,
192}
193
194impl UnlockBiometricsHandler {
195 fn new(biometrics_unlock: JsBiometricsUnlock) -> Self {
197 Self { biometrics_unlock }
198 }
199}
200
201impl RpcHandler for UnlockBiometricsHandler {
202 type Request = UnlockBiometricsRequest;
203
204 async fn handle(&self, request: Self::Request) {
205 self.biometrics_unlock
206 .unlock_biometrics(request.user_id)
207 .await
208 }
209}
210
211pub struct AuthenticateBiometricsHandler {
213 biometrics_unlock: JsBiometricsUnlock,
214}
215
216impl AuthenticateBiometricsHandler {
217 fn new(biometrics_unlock: JsBiometricsUnlock) -> Self {
219 Self { biometrics_unlock }
220 }
221}
222
223impl RpcHandler for AuthenticateBiometricsHandler {
224 type Request = AuthenticateBiometricsRequest;
225
226 async fn handle(&self, _: Self::Request) -> bool {
227 self.biometrics_unlock.authenticate_biometrics().await
228 }
229}
230
231#[wasm_bindgen(js_name = ipcRegisterBiometricsHandlers)]
233pub async fn ipc_register_biometrics_handlers(
234 ipc_client: &bitwarden_ipc::wasm::JsIpcClient,
235 biometrics_unlock: RawJsBiometricsDriver,
236) {
237 let runner = ThreadBoundRunner::new(biometrics_unlock);
238 let handler_driver = JsBiometricsUnlock::new(runner.clone());
239
240 ipc_client
241 .client
242 .register_rpc_handler(GetBiometricsStatusHandler::new(handler_driver))
243 .await;
244 ipc_client
245 .client
246 .register_rpc_handler(UnlockBiometricsHandler::new(JsBiometricsUnlock::new(
247 runner.clone(),
248 )))
249 .await;
250 ipc_client
251 .client
252 .register_rpc_handler(AuthenticateBiometricsHandler::new(JsBiometricsUnlock::new(
253 runner,
254 )))
255 .await;
256}
257
258#[wasm_bindgen(js_name = ipcRequestGetBiometricsStatus)]
260pub async fn ipc_request_get_biometrics_status(
261 ipc_client: &bitwarden_ipc::wasm::JsIpcClient,
262 user_id: UserId,
263 abort_signal: Option<AbortSignal>,
264) -> Result<BiometricsStatus, RequestError> {
265 ipc_client
266 .client
267 .request(
268 GetBiometricsStatusRequest { user_id },
269 Endpoint::DesktopRenderer,
270 abort_signal.map(|signal| signal.to_cancellation_token()),
271 )
272 .await
273}
274
275#[wasm_bindgen(js_name = ipcRequestUnlockBiometrics)]
277pub async fn ipc_request_unlock_biometrics(
278 ipc_client: &bitwarden_ipc::wasm::JsIpcClient,
279 user_id: UserId,
280 abort_signal: Option<AbortSignal>,
281) -> Result<(), RequestError> {
282 ipc_client
283 .client
284 .request(
285 UnlockBiometricsRequest { user_id },
286 Endpoint::DesktopRenderer,
287 abort_signal.map(|signal| signal.to_cancellation_token()),
288 )
289 .await
290}
291
292#[wasm_bindgen(js_name = ipcRequestAuthenticateBiometrics)]
294pub async fn ipc_request_authenticate_biometrics(
295 ipc_client: &bitwarden_ipc::wasm::JsIpcClient,
296 abort_signal: Option<AbortSignal>,
297) -> Result<bool, RequestError> {
298 ipc_client
299 .client
300 .request(
301 AuthenticateBiometricsRequest,
302 Endpoint::DesktopRenderer,
303 abort_signal.map(|signal| signal.to_cancellation_token()),
304 )
305 .await
306}