matrix_sdk/encryption/recovery/mod.rs
1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The recovery module
16//!
17//! The recovery module attempts to provide a unified and simplified view over
18//! the secret storage and backup subsystems.
19//!
20//! **Note**: If you are using this module, do not use the [`SecretStorage`] and
21//! [`Backups`] subsystems directly. This module makes assumptions that might be
22//! broken by the direct usage of the respective lower level modules.
23//!
24//! **Note**: The term Recovery used in this submodule is not the same as the
25//! [`Recovery key`] mentioned in the spec. The recovery key from the spec is
26//! solely about backups, while the term recovery in this file includes both the
27//! backups and the secret storage subsystems. The recovery key mentioned in
28//! this file is the secret storage key.
29//!
30//! You should configure your client to bootstrap cross-signing automatically
31//! and may choose to let your client automatically create a backup, if it
32//! doesn't exist, as well:
33//!
34//! ```no_run
35//! use matrix_sdk::{Client, encryption::EncryptionSettings};
36//!
37//! # async {
38//! # let homeserver = "http://example.org";
39//! let client = Client::builder()
40//! .homeserver_url(homeserver)
41//! .with_encryption_settings(EncryptionSettings {
42//! auto_enable_cross_signing: true,
43//! auto_enable_backups: true,
44//! ..Default::default()
45//! })
46//! .build()
47//! .await?;
48//! # anyhow::Ok(()) };
49//! ```
50//!
51//! # Examples
52//!
53//! For a newly registered user you will want to enable recovery, either
54//! immediately or before the user logs out.
55//!
56//! ```no_run
57//! # use matrix_sdk::{Client, encryption::recovery::EnableProgress};
58//! # use url::Url;
59//! # async {
60//! # let homeserver = Url::parse("http://example.com")?;
61//! # let client = Client::new(homeserver).await?;
62//! let recovery = client.encryption().recovery();
63//!
64//! // Create a new recovery key, you can use the provided passphrase, or the returned recovery key
65//! // to recover.
66//! let recovery_key = recovery
67//! .enable()
68//! .wait_for_backups_to_upload()
69//! .with_passphrase("my passphrase")
70//! .await;
71//! # anyhow::Ok(()) };
72//! ```
73//!
74//! If the user logs in with another device, you'll want to let the user recover
75//! its secrets by entering the recovery key or recovery passphrase.
76//!
77//! ```no_run
78//! # use matrix_sdk::{Client, encryption::recovery::EnableProgress};
79//! # use url::Url;
80//! # async {
81//! # let homeserver = Url::parse("http://example.com")?;
82//! # let client = Client::new(homeserver).await?;
83//! let recovery = client.encryption().recovery();
84//!
85//! // Create a new recovery key, you can use the provided passphrase, or the returned recovery key
86//! // to recover.
87//! recovery.recover("my recovery key or passphrase").await;
88//! # anyhow::Ok(()) };
89//! ```
90//!
91//! [`Recovery key`]: https://spec.matrix.org/v1.8/client-server-api/#recovery-key
92
93use futures_core::{Future, Stream};
94use futures_util::StreamExt as _;
95use ruma::{
96 api::client::keys::get_keys,
97 events::{
98 GlobalAccountDataEventType,
99 secret::{request::SecretName, send::ToDeviceSecretSendEvent},
100 secret_storage::{default_key::SecretStorageDefaultKeyEvent, secret::SecretEventContent},
101 },
102 serde::Raw,
103};
104use serde_json::{json, value::to_raw_value};
105use tracing::{error, info, instrument, warn};
106
107#[cfg(doc)]
108use crate::encryption::{
109 backups::Backups,
110 secret_storage::{SecretStorage, SecretStore},
111};
112use crate::{Client, client::WeakClient, encryption::backups::BackupState};
113
114pub mod futures;
115mod types;
116pub use self::types::{EnableProgress, RecoveryError, RecoveryState, Result};
117use self::{
118 futures::{Enable, RecoverAndReset, Reset},
119 types::{BackupDisabledContent, SecretStorageDisabledContent},
120};
121use crate::encryption::{AuthData, CrossSigningResetAuthType, CrossSigningResetHandle};
122
123/// The recovery manager for the [`Client`].
124#[derive(Debug)]
125pub struct Recovery {
126 pub(super) client: Client,
127}
128
129impl Recovery {
130 /// The list of known secrets that are contained in secret storage once
131 /// recover is enabled.
132 pub const KNOWN_SECRETS: &[SecretName] = &[
133 SecretName::CrossSigningMasterKey,
134 SecretName::CrossSigningUserSigningKey,
135 SecretName::CrossSigningSelfSigningKey,
136 SecretName::RecoveryKey,
137 ];
138
139 /// Get the current [`RecoveryState`] for this [`Client`].
140 pub fn state(&self) -> RecoveryState {
141 self.client.inner.e2ee.recovery_state.get()
142 }
143
144 /// Get a stream of updates to the [`RecoveryState`].
145 ///
146 /// This method will send out the current state as the first update.
147 ///
148 /// # Examples
149 ///
150 /// ```no_run
151 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
152 /// # use url::Url;
153 /// # async {
154 /// # let homeserver = Url::parse("http://example.com")?;
155 /// # let client = Client::new(homeserver).await?;
156 /// use futures_util::StreamExt;
157 ///
158 /// let recovery = client.encryption().recovery();
159 ///
160 /// let mut state_stream = recovery.state_stream();
161 ///
162 /// while let Some(update) = state_stream.next().await {
163 /// match update {
164 /// RecoveryState::Enabled => {
165 /// println!("Recovery has been enabled");
166 /// }
167 /// _ => (),
168 /// }
169 /// }
170 /// # anyhow::Ok(()) };
171 /// ```
172 pub fn state_stream(&self) -> impl Stream<Item = RecoveryState> + use<> {
173 self.client.inner.e2ee.recovery_state.subscribe_reset()
174 }
175
176 /// Enable secret storage *and* backups.
177 ///
178 /// This method will create a new secret storage key and a new backup if one
179 /// doesn't already exist. It will then upload all the locally cached
180 /// secrets, including the backup recovery key, to the new secret store.
181 ///
182 /// This method will throw an error if a backup already exists on the
183 /// homeserver but this [`Client`] isn't connected to the existing backup.
184 ///
185 /// # Examples
186 ///
187 /// ```no_run
188 /// # use matrix_sdk::{Client, encryption::recovery::EnableProgress};
189 /// # use url::Url;
190 /// # async {
191 /// # let homeserver = Url::parse("http://example.com")?;
192 /// # let client = Client::new(homeserver).await?;
193 /// use futures_util::StreamExt;
194 ///
195 /// let recovery = client.encryption().recovery();
196 ///
197 /// let enable = recovery
198 /// .enable()
199 /// .wait_for_backups_to_upload()
200 /// .with_passphrase("my passphrase");
201 ///
202 /// let mut progress_stream = enable.subscribe_to_progress();
203 ///
204 /// tokio::spawn(async move {
205 /// while let Some(update) = progress_stream.next().await {
206 /// let Ok(update) = update else {
207 /// panic!("Update to the enable progress lagged")
208 /// };
209 ///
210 /// match update {
211 /// EnableProgress::CreatingBackup => {
212 /// println!("Creating a new backup");
213 /// }
214 /// EnableProgress::CreatingRecoveryKey => {
215 /// println!("Creating a new recovery key");
216 /// }
217 /// EnableProgress::Done { .. } => {
218 /// println!("Recovery has been enabled");
219 /// break;
220 /// }
221 /// _ => (),
222 /// }
223 /// }
224 /// });
225 ///
226 /// let recovery_key = enable.await?;
227 ///
228 /// # anyhow::Ok(()) };
229 /// ```
230 #[instrument(skip_all)]
231 pub fn enable(&self) -> Enable<'_> {
232 Enable::new(self)
233 }
234
235 /// Create a new backup if one does not exist yet.
236 ///
237 /// This method will throw an error if a backup already exists on the
238 /// homeserver but this [`Client`] isn't connected to the existing backup.
239 ///
240 /// # Examples
241 ///
242 /// ```no_run
243 /// # use matrix_sdk::{Client, encryption::backups::BackupState};
244 /// # use url::Url;
245 /// # async {
246 /// # let homeserver = Url::parse("http://example.com")?;
247 /// # let client = Client::new(homeserver).await?;
248 /// let recovery = client.encryption().recovery();
249 ///
250 /// recovery.enable_backup().await?;
251 ///
252 /// assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
253 ///
254 /// # anyhow::Ok(()) };
255 /// ```
256 #[instrument(skip_all)]
257 pub async fn enable_backup(&self) -> Result<()> {
258 if !self.client.encryption().backups().fetch_exists_on_server().await? {
259 self.mark_backup_as_enabled().await?;
260
261 self.client.encryption().backups().create().await?;
262 self.client.encryption().backups().maybe_trigger_backup();
263
264 Ok(())
265 } else {
266 Err(RecoveryError::BackupExistsOnServer)
267 }
268 }
269
270 /// Disable recovery completely.
271 ///
272 /// This method will do the following steps:
273 ///
274 /// 1. Disable the uploading of room keys to a currently active backup.
275 /// 2. Delete the currently active backup.
276 /// 3. Set the `m.secret_storage.default_key` global account data event to
277 /// an empty JSON content.
278 /// 4. Set a global account data event so clients won't attempt to
279 /// automatically re-enable a backup.
280 ///
281 /// # Examples
282 ///
283 /// ```no_run
284 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
285 /// # use url::Url;
286 /// # async {
287 /// # let homeserver = Url::parse("http://example.com")?;
288 /// # let client = Client::new(homeserver).await?;
289 /// let recovery = client.encryption().recovery();
290 ///
291 /// recovery.disable().await?;
292 ///
293 /// assert_eq!(recovery.state(), RecoveryState::Disabled);
294 ///
295 /// # anyhow::Ok(()) };
296 /// ```
297 #[instrument(skip_all)]
298 pub async fn disable(&self) -> Result<()> {
299 self.client.encryption().backups().disable().await?;
300
301 // Why oh why, can't we delete account data events?
302 //
303 // Alright, let's attempt to "delete" the content of our current default key,
304 // for this we first need to check if there is a default key, then
305 // deserialize the content and find out the key ID.
306 //
307 // Then we finally set the event to an empty JSON content.
308 if let Ok(Some(default_event)) =
309 self.client.encryption().secret_storage().fetch_default_key_id().await
310 && let Ok(default_event) = default_event.deserialize()
311 {
312 let key_id = default_event.key_id;
313 let event_type = GlobalAccountDataEventType::SecretStorageKey(key_id);
314
315 self.client
316 .account()
317 .set_account_data_raw(event_type, Raw::new(&json!({})).expect("").cast_unchecked())
318 .await?;
319 }
320
321 // Now let's "delete" the actual `m.secret.storage.default_key` event.
322 self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
323 // Make sure that we don't re-enable backups automatically.
324 self.client.account().set_account_data(BackupDisabledContent { disabled: true }).await?;
325 // Finally, "delete" all the known secrets we have in the account data.
326 self.delete_all_known_secrets().await?;
327
328 self.update_recovery_state().await?;
329
330 Ok(())
331 }
332
333 /// Reset the recovery key.
334 ///
335 /// This will rotate the secret storage key and re-upload all the secrets to
336 /// the [`SecretStore`].
337 ///
338 /// # Examples
339 ///
340 /// ```no_run
341 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
342 /// # use url::Url;
343 /// # async {
344 /// # let homeserver = Url::parse("http://example.com")?;
345 /// # let client = Client::new(homeserver).await?;
346 /// let recovery = client.encryption().recovery();
347 ///
348 /// let new_recovery_key =
349 /// recovery.reset_key().with_passphrase("my passphrase").await;
350 /// # anyhow::Ok(()) };
351 /// ```
352 #[instrument(skip_all)]
353 pub fn reset_key(&self) -> Reset<'_> {
354 // TODO: Should this only be possible if we're in the RecoveryState::Enabled
355 // state? Otherwise we'll create a new secret store but won't be able to
356 // upload all the secrets.
357 Reset::new(self)
358 }
359
360 /// Reset the recovery key but first import all the secrets from secret
361 /// storage.
362 ///
363 /// # Examples
364 ///
365 /// ```no_run
366 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
367 /// # use url::Url;
368 /// # async {
369 /// # let homeserver = Url::parse("http://example.com")?;
370 /// # let client = Client::new(homeserver).await?;
371 /// let recovery = client.encryption().recovery();
372 ///
373 /// let new_recovery_key = recovery
374 /// .recover_and_reset("my old passphrase or key")
375 /// .with_passphrase("my new passphrase")
376 /// .await?;
377 /// # anyhow::Ok(()) };
378 /// ```
379 #[instrument(skip_all)]
380 pub fn recover_and_reset<'a>(&'a self, old_key: &'a str) -> RecoverAndReset<'a> {
381 RecoverAndReset::new(self, old_key)
382 }
383
384 /// Completely reset the current user's crypto identity.
385 /// This method will go through the following steps:
386 ///
387 /// 1. Disable backing up room keys and delete the active backup
388 /// 2. Disable recovery and delete secret storage
389 /// 3. Go through the cross-signing key reset flow
390 /// 4. Finally, re-enable key backups (only if they were already enabled)
391 ///
392 /// Disclaimer: failures in this flow will potentially leave the user in
393 /// an inconsistent state but they're expected to just run the reset flow
394 /// again as presumably the reason they started it to begin with was
395 /// that they no longer had access to any of their data.
396 ///
397 /// # Examples
398 ///
399 /// ```no_run
400 /// # use matrix_sdk::{
401 /// encryption::recovery, encryption::CrossSigningResetAuthType, ruma::api::client::uiaa,
402 /// Client,
403 /// };
404 /// # use url::Url;
405 /// # async {
406 /// # let homeserver = Url::parse("http://example.com")?;
407 /// # let client = Client::new(homeserver).await?;
408 /// # let user_id = unimplemented!();
409 /// let encryption = client.encryption();
410 ///
411 /// if let Some(handle) = encryption.recovery().reset_identity().await? {
412 /// match handle.auth_type() {
413 /// CrossSigningResetAuthType::Uiaa(uiaa) => {
414 /// let password = "1234".to_owned();
415 /// let mut password = uiaa::Password::new(user_id, password);
416 /// password.session = uiaa.session;
417 ///
418 /// handle.reset(Some(uiaa::AuthData::Password(password))).await?;
419 /// }
420 /// CrossSigningResetAuthType::OAuth(o) => {
421 /// println!(
422 /// "To reset your end-to-end encryption cross-signing identity, \
423 /// you first need to approve it at {}",
424 /// o.approval_url
425 /// );
426 /// handle.reset(None).await?;
427 /// }
428 /// }
429 /// }
430 /// # anyhow::Ok(()) };
431 /// ```
432 pub async fn reset_identity(&self) -> Result<Option<IdentityResetHandle>> {
433 self.client.encryption().backups().disable_and_delete().await?; // 1.
434
435 // 2. (We can't delete account data events)
436 self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
437 self.client.encryption().recovery().update_recovery_state().await?;
438
439 let cross_signing_reset_handle = self.client.encryption().reset_cross_signing().await?;
440
441 if let Some(handle) = cross_signing_reset_handle {
442 // Authentication required, backups will be re-enabled after the reset
443 Ok(Some(IdentityResetHandle {
444 client: self.client.clone(),
445 cross_signing_reset_handle: handle,
446 }))
447 } else {
448 // No authentication required, re-enable backups
449 if self.client.encryption().recovery().should_auto_enable_backups().await? {
450 self.client.encryption().recovery().enable_backup().await?; // 4.
451 }
452
453 Ok(None)
454 }
455 }
456
457 /// Recover all the secrets from the homeserver.
458 ///
459 /// This method is a convenience method around the
460 /// [`SecretStore::import_secrets()`] method, please read the documentation
461 /// of this method for more information about what happens if you call
462 /// this method.
463 ///
464 /// In short, this method will turn a newly created [`Client`] into a fully
465 /// end-to-end encryption enabled client.
466 ///
467 /// # Examples
468 ///
469 /// ```no_run
470 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
471 /// # use url::Url;
472 /// # async {
473 /// # let homeserver = Url::parse("http://example.com")?;
474 /// # let client = Client::new(homeserver).await?;
475 /// let recovery = client.encryption().recovery();
476 ///
477 /// recovery.recover("my recovery key or passphrase").await;
478 ///
479 /// assert_eq!(recovery.state(), RecoveryState::Enabled);
480 /// # anyhow::Ok(()) };
481 /// ```
482 #[instrument(skip_all)]
483 pub async fn recover(&self, recovery_key: &str) -> Result<()> {
484 let store =
485 self.client.encryption().secret_storage().open_secret_store(recovery_key).await?;
486
487 store.import_secrets().await?;
488 self.update_recovery_state().await?;
489
490 Ok(())
491 }
492
493 /// Is this device the last device the user has?
494 ///
495 /// This method is useful to check if we should recommend to the user that
496 /// they should enable recovery, typically done before logging out.
497 ///
498 /// If the user does not enable recovery before logging out of their last
499 /// device, they will not be able to decrypt historic messages once they
500 /// create a new device.
501 pub async fn is_last_device(&self) -> Result<bool> {
502 let olm_machine = self.client.olm_machine().await;
503 let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
504 let user_id = olm_machine.user_id();
505
506 self.client.encryption().ensure_initial_key_query().await?;
507
508 let devices = self.client.encryption().get_user_devices(user_id).await?;
509
510 Ok(devices.devices().count() == 1)
511 }
512
513 /// Did we correctly set up cross-signing and backups?
514 async fn all_known_secrets_available(&self) -> Result<bool> {
515 // Cross-signing state is fine if we have all the private cross-signing keys, as
516 // indicated in the status.
517 let cross_signing_complete = self
518 .client
519 .encryption()
520 .cross_signing_status()
521 .await
522 .map(|status| status.is_complete());
523 if !cross_signing_complete.unwrap_or_default() {
524 return Ok(false);
525 }
526
527 // The backup state is fine if we have backups enabled locally, or if backups
528 // have been marked as disabled.
529 if self.client.encryption().backups().are_enabled().await {
530 Ok(true)
531 } else {
532 self.are_backups_marked_as_disabled().await
533 }
534 }
535
536 async fn should_auto_enable_backups(&self) -> Result<bool> {
537 // If we didn't already enable backups, we don't see a backup version on the
538 // server, and finally if backups have not been marked to be explicitly
539 // disabled, then we can automatically enable them.
540 Ok(self.client.inner.e2ee.encryption_settings.auto_enable_backups
541 && !self.client.encryption().backups().are_enabled().await
542 && !self.client.encryption().backups().fetch_exists_on_server().await?
543 && !self.are_backups_marked_as_disabled().await?)
544 }
545
546 pub(crate) async fn setup(&self) -> Result<()> {
547 info!("Setting up account data listeners and trying to setup recovery");
548
549 self.client.add_event_handler(Self::default_key_event_handler);
550 self.client.add_event_handler(Self::secret_send_event_handler);
551 self.client.inner.e2ee.initialize_recovery_state_update_task(&self.client);
552
553 self.update_recovery_state().await?;
554
555 if self.should_auto_enable_backups().await? {
556 info!("Trying to automatically enable backups");
557
558 if let Err(e) = self.enable_backup().await {
559 warn!("Could not automatically enable backups: {e:?}");
560 }
561 }
562
563 Ok(())
564 }
565
566 /// Delete all the known secrets we are keeping in secret storage.
567 ///
568 /// The exact list of secrets is defined in [`Recovery::KNOWN_SECRETS`] and
569 /// might change over time.
570 ///
571 /// Since account data events can't actually be deleted, due to a missing
572 /// DELETE API, we're replacing the events with an empty
573 /// [`SecretEventContent`].
574 async fn delete_all_known_secrets(&self) -> Result<()> {
575 for secret_name in Self::KNOWN_SECRETS {
576 let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
577 let content = SecretEventContent::new(Default::default());
578 let secret_content = Raw::from_json(
579 to_raw_value(&content)
580 .expect("We should be able to serialize a raw empty secret event content"),
581 );
582 self.client.account().set_account_data_raw(event_type, secret_content).await?;
583 }
584
585 Ok(())
586 }
587
588 /// Run a network request to figure whether backups have been disabled at
589 /// the account level.
590 async fn are_backups_marked_as_disabled(&self) -> Result<bool> {
591 Ok(self
592 .client
593 .account()
594 .fetch_account_data_static::<BackupDisabledContent>()
595 .await?
596 .map(|event| event.deserialize().map(|event| event.disabled).unwrap_or(false))
597 .unwrap_or(false))
598 }
599
600 async fn mark_backup_as_enabled(&self) -> Result<()> {
601 self.client.account().set_account_data(BackupDisabledContent { disabled: false }).await?;
602
603 Ok(())
604 }
605
606 async fn check_recovery_state(&self) -> Result<RecoveryState> {
607 Ok(if self.client.encryption().secret_storage().is_enabled().await? {
608 if self.all_known_secrets_available().await? {
609 RecoveryState::Enabled
610 } else {
611 RecoveryState::Incomplete
612 }
613 } else {
614 RecoveryState::Disabled
615 })
616 }
617
618 async fn update_recovery_state(&self) -> Result<()> {
619 let new_state = self.check_recovery_state().await?;
620 let old_state = self.client.inner.e2ee.recovery_state.set(new_state);
621
622 if new_state != old_state {
623 info!("Recovery state changed from {old_state:?} to {new_state:?}");
624 }
625
626 Ok(())
627 }
628
629 async fn update_recovery_state_no_fail(&self) {
630 if let Err(e) = self.update_recovery_state().await {
631 error!("Couldn't update the recovery state: {e:?}");
632 }
633 }
634
635 #[instrument]
636 async fn secret_send_event_handler(_: ToDeviceSecretSendEvent, client: Client) {
637 client.encryption().recovery().update_recovery_state_no_fail().await;
638 }
639
640 #[instrument]
641 async fn default_key_event_handler(_: SecretStorageDefaultKeyEvent, client: Client) {
642 client.encryption().recovery().update_recovery_state_no_fail().await;
643 }
644
645 /// Listen for changes in the [`BackupState`] and, if necessary, update the
646 /// [`RecoveryState`] accordingly.
647 ///
648 /// This should not be called directly, this method is put into a background
649 /// task which is always listening for updates in the [`BackupState`].
650 pub(crate) fn update_state_after_backup_state_change(
651 client: &Client,
652 ) -> impl Future<Output = ()> + use<> {
653 let mut stream = client.encryption().backups().state_stream();
654 let weak = WeakClient::from_client(client);
655
656 async move {
657 while let Some(update) = stream.next().await {
658 if let Some(client) = weak.get() {
659 match update {
660 Ok(update) => {
661 // The recovery state only cares about these two states, the
662 // intermediate states that tell us that
663 // we're creating a backup are not interesting.
664 if matches!(update, BackupState::Unknown | BackupState::Enabled) {
665 client
666 .encryption()
667 .recovery()
668 .update_recovery_state_no_fail()
669 .await;
670 }
671 }
672 Err(_) => {
673 // We missed some updates, let's update our state in case something
674 // changed.
675 client.encryption().recovery().update_recovery_state_no_fail().await;
676 }
677 }
678 } else {
679 break;
680 }
681 }
682 }
683 }
684
685 #[instrument(skip_all)]
686 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
687 if let Some(user_id) = self.client.user_id()
688 && response.master_keys.contains_key(user_id)
689 {
690 // TODO: This is unnecessarily expensive, we could let the crypto crate notify
691 // us that our private keys got erased... But, the OlmMachine
692 // gets recreated and... You know the drill by now...
693 self.update_recovery_state_no_fail().await;
694 }
695 }
696}
697
698/// A helper struct that handles continues resetting a user's crypto identity
699/// after authentication was required and re-enabling backups (if necessary) at
700/// the end of it
701#[derive(Debug)]
702pub struct IdentityResetHandle {
703 client: Client,
704 cross_signing_reset_handle: CrossSigningResetHandle,
705}
706
707impl IdentityResetHandle {
708 /// Get the underlying [`CrossSigningResetAuthType`] this identity reset
709 /// process is using.
710 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
711 &self.cross_signing_reset_handle.auth_type
712 }
713
714 /// This method will retry to upload the device keys after the previous try
715 /// failed due to required authentication
716 pub async fn reset(&self, auth: Option<AuthData>) -> Result<()> {
717 self.cross_signing_reset_handle.auth(auth).await?;
718
719 if self.client.encryption().recovery().should_auto_enable_backups().await? {
720 self.client.encryption().recovery().enable_backup().await?;
721 }
722
723 Ok(())
724 }
725
726 /// Cancel the ongoing identity reset process
727 pub async fn cancel(&self) {
728 self.cross_signing_reset_handle.cancel().await;
729 }
730}