bitwarden_shared_unlock/
follower.rs1use std::{ops::Add, sync::Arc};
2
3use bitwarden_error::bitwarden_error;
4use bitwarden_ipc::{
5 Endpoint, IpcClient, IpcClientExt, RequestError, SubscribeError, TypedIncomingMessage,
6};
7use bitwarden_threading::{cancellation_token, time::sleep};
8use thiserror::Error;
9
10use crate::{DeviceEvent, FollowerMessage, LeaderMessage, LockState, drivers::SharedUnlockDriver};
11
12#[bitwarden_error(basic)]
14#[derive(Debug, Error)]
15#[error("Could not start shared unlock follower: {0}")]
16pub struct FollowerStartError(#[from] SubscribeError);
17
18pub struct Follower<L: SharedUnlockDriver>(Arc<InnerFollower<L>>);
20
21impl<L: SharedUnlockDriver> Clone for Follower<L> {
22 fn clone(&self) -> Self {
23 Self(self.0.clone())
24 }
25}
26
27struct InnerFollower<D: SharedUnlockDriver> {
31 driver: D,
32 ipc_client: Arc<dyn IpcClient>,
33}
34
35impl<L: SharedUnlockDriver + Send + Sync + 'static> Follower<L> {
36 pub fn create(driver: L, ipc_client: Arc<dyn IpcClient>) -> Self {
41 Self(Arc::new(InnerFollower { driver, ipc_client }))
42 }
43
44 pub(crate) async fn start_sessions(&self) {
45 let users: Vec<bitwarden_core::UserId> = self.0.driver.list_users().await;
46 let leader = self
47 .0
48 .driver
49 .discover_leader()
50 .await
51 .expect("leader discovery should return a leader");
52
53 if !users.is_empty() {
54 tracing::info!("Starting shared unlock sessions for users: {:?}", users);
55 }
56
57 for user_id in users {
58 let lock_state = self.0.driver.get_user_lock_state(user_id).await;
59 let message = FollowerMessage::StartSession {
60 user_id,
61 lock_state,
62 };
63 self.send_message(message, leader.clone()).await;
64 }
65 }
66
67 pub async fn start(
69 &self,
70 cancellation_token: Option<cancellation_token::CancellationToken>,
71 ) -> Result<(), FollowerStartError> {
72 let cancellation_token = cancellation_token.unwrap_or_default();
73 let mut subscription = self.0.ipc_client.subscribe_typed::<LeaderMessage>().await?;
74 let follower = self.clone();
75
76 let cancellation_token_clone = cancellation_token.clone();
77 let future = async move {
78 loop {
79 let result = subscription
80 .receive(Some(cancellation_token_clone.clone()))
81 .await;
82 match result {
83 Ok(message) => {
84 if let Err(error) = follower.receive_message(message).await {
85 tracing::error!(
86 ?error,
87 "Failed to handle shared unlock follower message"
88 );
89 }
90 }
91 Err(bitwarden_ipc::TypedReceiveError::Cancelled) => {
92 tracing::info!("Shared unlock follower stopped by cancellation");
93 break;
94 }
95 Err(bitwarden_ipc::TypedReceiveError::Channel(
97 tokio::sync::broadcast::error::RecvError::Closed,
98 )) => {
99 tracing::info!("Transport channel closed. Waiting for it to open");
100 sleep(std::time::Duration::from_secs(1)).await;
101 break;
102 }
103 Err(error) => {
104 tracing::error!(?error, "Failed to receive shared unlock IPC message");
105 }
106 }
107 }
108 };
109
110 #[cfg(not(target_arch = "wasm32"))]
111 tokio::spawn(future);
112
113 #[cfg(target_arch = "wasm32")]
114 wasm_bindgen_futures::spawn_local(future);
115
116 let cancellation_token = cancellation_token.clone();
117 let follower = self.clone();
118 let timer_future = async move {
119 loop {
120 tokio::select! {
121 _ = cancellation_token.cancelled() => {
122 tracing::debug!("Shared unlock follower timer cancelled");
123 break;
124 }
125 _ = bitwarden_threading::time::sleep(crate::HEARTBEAT_INTERVAL) => {
126 if let Some(leader) = follower.0.driver.discover_leader().await {
127 for user_id in follower.0.driver.list_users().await {
129 let message = FollowerMessage::HeartBeat { user_id };
130 follower.send_message(message, leader.clone()).await;
131 }
132 }
133 }
134 }
135 }
136 };
137
138 #[cfg(not(target_arch = "wasm32"))]
139 tokio::spawn(timer_future);
140
141 #[cfg(target_arch = "wasm32")]
142 wasm_bindgen_futures::spawn_local(timer_future);
143
144 self.start_sessions().await;
145 Ok(())
146 }
147
148 pub async fn receive_message(
153 &self,
154 incoming_message: TypedIncomingMessage<LeaderMessage>,
155 ) -> Result<(), ()> {
156 let message = incoming_message.payload;
157 match message {
158 LeaderMessage::LockStateUpdate {
159 user_id,
160 lock_state,
161 } => {
162 let current_state = self.0.driver.get_user_lock_state(user_id).await;
165
166 match (current_state, lock_state) {
167 (LockState::Unlocked { .. }, LockState::Locked) => {
168 self.0.driver.lock_user(user_id).await?;
172 }
173 (LockState::Locked, LockState::Unlocked { user_key }) => {
174 self.0.driver.unlock_user(user_id, user_key).await?;
178 }
179 (LockState::Locked, LockState::Locked)
180 | (LockState::Unlocked { .. }, LockState::Unlocked { .. }) => {
181 }
184 }
185 }
186 LeaderMessage::HeartBeat { user_id } => {
187 self.0
188 .driver
189 .suppress_vault_timeout(
190 user_id,
191 crate::HEARTBEAT_INTERVAL.add(crate::VAULT_TIMEOUT_GRACE_PERIOD),
192 )
193 .await;
194 }
195 LeaderMessage::RequestSessionStart { user_id } => {
196 let lock_state = self.0.driver.get_user_lock_state(user_id).await;
197 let message = FollowerMessage::StartSession {
198 user_id,
199 lock_state,
200 };
201 self.send_message(message, self.0.driver.discover_leader().await.ok_or(())?)
202 .await;
203 }
204 }
205
206 Ok(())
207 }
208
209 pub async fn handle_device_event(&self, event: DeviceEvent) -> Result<(), ()> {
214 let leader = self.0.driver.discover_leader().await.ok_or(())?;
215
216 match event {
217 DeviceEvent::ManualLock { user_id } => {
218 let message = FollowerMessage::LockStateUpdate {
219 user_id,
220 lock_state: LockState::Locked,
221 };
222 self.send_message(message, leader).await;
223 }
224 DeviceEvent::ManualUnlock {
225 user_id,
226 ref user_key,
227 } => {
228 let message = FollowerMessage::LockStateUpdate {
229 user_id,
230 lock_state: LockState::Unlocked {
231 user_key: user_key.to_owned(),
232 },
233 };
234 self.send_message(message, leader).await;
235 }
236 }
237
238 Ok(())
239 }
240
241 async fn send_message(&self, message: FollowerMessage, recipient: Endpoint) {
242 if let Err(error) = self.0.ipc_client.send_typed(message, recipient).await {
243 match error {
244 RequestError::Unreachable => {
245 }
247 RequestError::Timeout(_) => {
248 tracing::warn!(
249 ?error,
250 "Timeout sending shared unlock follower message to leader"
251 );
252 }
253 _ => {
254 tracing::error!(?error, "Failed to send shared unlock IPC message");
255 }
256 }
257 }
258 }
259}