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::{encryption::EncryptionSettings, Client};
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        secret::{request::SecretName, send::ToDeviceSecretSendEvent},
99        secret_storage::{default_key::SecretStorageDefaultKeyEvent, secret::SecretEventContent},
100        GlobalAccountDataEventType,
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::WeakClient, encryption::backups::BackupState, Client};
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> {
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        {
311            if let Ok(default_event) = default_event.deserialize() {
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())
318                    .await?;
319            }
320        }
321
322        // Now let's "delete" the actual `m.secret.storage.default_key` event.
323        self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
324        // Make sure that we don't re-enable backups automatically.
325        self.client.account().set_account_data(BackupDisabledContent { disabled: true }).await?;
326        // Finally, "delete" all the known secrets we have in the account data.
327        self.delete_all_known_secrets().await?;
328
329        self.update_recovery_state().await?;
330
331        Ok(())
332    }
333
334    /// Reset the recovery key.
335    ///
336    /// This will rotate the secret storage key and re-upload all the secrets to
337    /// the [`SecretStore`].
338    ///
339    /// # Examples
340    ///
341    /// ```no_run
342    /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
343    /// # use url::Url;
344    /// # async {
345    /// # let homeserver = Url::parse("http://example.com")?;
346    /// # let client = Client::new(homeserver).await?;
347    /// let recovery = client.encryption().recovery();
348    ///
349    /// let new_recovery_key =
350    ///     recovery.reset_key().with_passphrase("my passphrase").await;
351    /// # anyhow::Ok(()) };
352    /// ```
353    #[instrument(skip_all)]
354    pub fn reset_key(&self) -> Reset<'_> {
355        // TODO: Should this only be possible if we're in the RecoveryState::Enabled
356        // state? Otherwise we'll create a new secret store but won't be able to
357        // upload all the secrets.
358        Reset::new(self)
359    }
360
361    /// Reset the recovery key but first import all the secrets from secret
362    /// storage.
363    ///
364    /// # Examples
365    ///
366    /// ```no_run
367    /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
368    /// # use url::Url;
369    /// # async {
370    /// # let homeserver = Url::parse("http://example.com")?;
371    /// # let client = Client::new(homeserver).await?;
372    /// let recovery = client.encryption().recovery();
373    ///
374    /// let new_recovery_key = recovery
375    ///     .recover_and_reset("my old passphrase or key")
376    ///     .with_passphrase("my new passphrase")
377    ///     .await?;
378    /// # anyhow::Ok(()) };
379    /// ```
380    #[instrument(skip_all)]
381    pub fn recover_and_reset<'a>(&'a self, old_key: &'a str) -> RecoverAndReset<'a> {
382        RecoverAndReset::new(self, old_key)
383    }
384
385    /// Completely reset the current user's crypto identity.
386    /// This method will go through the following steps:
387    ///
388    /// 1. Disable backing up room keys and delete the active backup
389    /// 2. Disable recovery and delete secret storage
390    /// 3. Go through the cross-signing key reset flow
391    /// 4. Finally, re-enable key backups (only if they were already enabled)
392    ///
393    /// Disclaimer: failures in this flow will potentially leave the user in
394    /// an inconsistent state but they're expected to just run the reset flow
395    /// again as presumably the reason they started it to begin with was
396    /// that they no longer had access to any of their data.
397    ///
398    /// # Examples
399    ///
400    /// ```no_run
401    /// # use matrix_sdk::{
402    ///     encryption::recovery, encryption::CrossSigningResetAuthType, ruma::api::client::uiaa,
403    ///     Client,
404    ///   };
405    /// # use url::Url;
406    /// # async {
407    /// # let homeserver = Url::parse("http://example.com")?;
408    /// # let client = Client::new(homeserver).await?;
409    /// # let user_id = unimplemented!();
410    /// let encryption = client.encryption();
411    ///       
412    /// if let Some(handle) = encryption.recovery().reset_identity().await? {
413    ///     match handle.auth_type() {
414    ///         CrossSigningResetAuthType::Uiaa(uiaa) => {
415    ///             let password = "1234".to_owned();
416    ///             let mut password = uiaa::Password::new(user_id, password);
417    ///             password.session = uiaa.session;
418    ///
419    ///             handle.reset(Some(uiaa::AuthData::Password(password))).await?;
420    ///         }
421    ///         CrossSigningResetAuthType::Oidc(o) => {
422    ///             println!(
423    ///                 "To reset your end-to-end encryption cross-signing identity, \
424    ///                 you first need to approve it at {}",
425    ///                 o.approval_url
426    ///             );
427    ///             handle.reset(None).await?;
428    ///         }
429    ///     }
430    /// }
431    /// # anyhow::Ok(()) };
432    /// ```
433    pub async fn reset_identity(&self) -> Result<Option<IdentityResetHandle>> {
434        self.client.encryption().backups().disable_and_delete().await?; // 1.
435
436        // 2. (We can't delete account data events)
437        self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
438        self.client.encryption().recovery().update_recovery_state().await?;
439
440        let cross_signing_reset_handle = self.client.encryption().reset_cross_signing().await?;
441
442        if let Some(handle) = cross_signing_reset_handle {
443            // Authentication required, backups will be re-enabled after the reset
444            Ok(Some(IdentityResetHandle {
445                client: self.client.clone(),
446                cross_signing_reset_handle: handle,
447            }))
448        } else {
449            // No authentication required, re-enable backups
450            if self.client.encryption().recovery().should_auto_enable_backups().await? {
451                self.client.encryption().recovery().enable_backup().await?; // 4.
452            }
453
454            Ok(None)
455        }
456    }
457
458    /// Recover all the secrets from the homeserver.
459    ///
460    /// This method is a convenience method around the
461    /// [`SecretStore::import_secrets()`] method, please read the documentation
462    /// of this method for more information about what happens if you call
463    /// this method.
464    ///
465    /// In short, this method will turn a newly created [`Client`] into a fully
466    /// end-to-end encryption enabled client.
467    ///
468    /// # Examples
469    ///
470    /// ```no_run
471    /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
472    /// # use url::Url;
473    /// # async {
474    /// # let homeserver = Url::parse("http://example.com")?;
475    /// # let client = Client::new(homeserver).await?;
476    /// let recovery = client.encryption().recovery();
477    ///
478    /// recovery.recover("my recovery key or passphrase").await;
479    ///
480    /// assert_eq!(recovery.state(), RecoveryState::Enabled);
481    /// # anyhow::Ok(()) };
482    /// ```
483    #[instrument(skip_all)]
484    pub async fn recover(&self, recovery_key: &str) -> Result<()> {
485        let store =
486            self.client.encryption().secret_storage().open_secret_store(recovery_key).await?;
487
488        store.import_secrets().await?;
489        self.update_recovery_state().await?;
490
491        Ok(())
492    }
493
494    /// Is this device the last device the user has?
495    ///
496    /// This method is useful to check if we should recommend to the user that
497    /// they should enable recovery, typically done before logging out.
498    ///
499    /// If the user does not enable recovery before logging out of their last
500    /// device, they will not be able to decrypt historic messages once they
501    /// create a new device.
502    pub async fn is_last_device(&self) -> Result<bool> {
503        let olm_machine = self.client.olm_machine().await;
504        let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
505        let user_id = olm_machine.user_id();
506
507        self.client.encryption().ensure_initial_key_query().await?;
508
509        let devices = self.client.encryption().get_user_devices(user_id).await?;
510
511        Ok(devices.devices().count() == 1)
512    }
513
514    /// Did we correctly set up cross-signing and backups?
515    async fn all_known_secrets_available(&self) -> Result<bool> {
516        // Cross-signing state is fine if we have all the private cross-signing keys, as
517        // indicated in the status.
518        let cross_signing_complete = self
519            .client
520            .encryption()
521            .cross_signing_status()
522            .await
523            .map(|status| status.is_complete());
524        if !cross_signing_complete.unwrap_or_default() {
525            return Ok(false);
526        }
527
528        // The backup state is fine if we have backups enabled locally, or if backups
529        // have been marked as disabled.
530        if self.client.encryption().backups().are_enabled().await {
531            Ok(true)
532        } else {
533            self.are_backups_marked_as_disabled().await
534        }
535    }
536
537    async fn should_auto_enable_backups(&self) -> Result<bool> {
538        // If we didn't already enable backups, we don't see a backup version on the
539        // server, and finally if backups have not been marked to be explicitly
540        // disabled, then we can automatically enable them.
541        Ok(self.client.inner.e2ee.encryption_settings.auto_enable_backups
542            && !self.client.encryption().backups().are_enabled().await
543            && !self.client.encryption().backups().fetch_exists_on_server().await?
544            && !self.are_backups_marked_as_disabled().await?)
545    }
546
547    pub(crate) async fn setup(&self) -> Result<()> {
548        info!("Setting up account data listeners and trying to setup recovery");
549
550        self.client.add_event_handler(Self::default_key_event_handler);
551        self.client.add_event_handler(Self::secret_send_event_handler);
552        self.client.inner.e2ee.initialize_recovery_state_update_task(&self.client);
553
554        self.update_recovery_state().await?;
555
556        if self.should_auto_enable_backups().await? {
557            info!("Trying to automatically enable backups");
558
559            if let Err(e) = self.enable_backup().await {
560                warn!("Could not automatically enable backups: {e:?}");
561            }
562        }
563
564        Ok(())
565    }
566
567    /// Delete all the known secrets we are keeping in secret storage.
568    ///
569    /// The exact list of secrets is defined in [`Recovery::KNOWN_SECRETS`] and
570    /// might change over time.
571    ///
572    /// Since account data events can't actually be deleted, due to a missing
573    /// DELETE API, we're replacing the events with an empty
574    /// [`SecretEventContent`].
575    async fn delete_all_known_secrets(&self) -> Result<()> {
576        for secret_name in Self::KNOWN_SECRETS {
577            let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
578            let content = SecretEventContent::new(Default::default());
579            let secret_content = Raw::from_json(
580                to_raw_value(&content)
581                    .expect("We should be able to serialize a raw empty secret event content"),
582            );
583            self.client.account().set_account_data_raw(event_type, secret_content).await?;
584        }
585
586        Ok(())
587    }
588
589    /// Run a network request to figure whether backups have been disabled at
590    /// the account level.
591    async fn are_backups_marked_as_disabled(&self) -> Result<bool> {
592        Ok(self
593            .client
594            .account()
595            .fetch_account_data(BackupDisabledContent::event_type())
596            .await?
597            .map(|event| {
598                event
599                    .deserialize_as::<BackupDisabledContent>()
600                    .map(|event| event.disabled)
601                    .unwrap_or(false)
602            })
603            .unwrap_or(false))
604    }
605
606    async fn mark_backup_as_enabled(&self) -> Result<()> {
607        self.client.account().set_account_data(BackupDisabledContent { disabled: false }).await?;
608
609        Ok(())
610    }
611
612    async fn check_recovery_state(&self) -> Result<RecoveryState> {
613        Ok(if self.client.encryption().secret_storage().is_enabled().await? {
614            if self.all_known_secrets_available().await? {
615                RecoveryState::Enabled
616            } else {
617                RecoveryState::Incomplete
618            }
619        } else {
620            RecoveryState::Disabled
621        })
622    }
623
624    async fn update_recovery_state(&self) -> Result<()> {
625        let new_state = self.check_recovery_state().await?;
626        let old_state = self.client.inner.e2ee.recovery_state.set(new_state);
627
628        if new_state != old_state {
629            info!("Recovery state changed from {old_state:?} to {new_state:?}");
630        }
631
632        Ok(())
633    }
634
635    async fn update_recovery_state_no_fail(&self) {
636        if let Err(e) = self.update_recovery_state().await {
637            error!("Couldn't update the recovery state: {e:?}");
638        }
639    }
640
641    #[instrument]
642    async fn secret_send_event_handler(_: ToDeviceSecretSendEvent, client: Client) {
643        client.encryption().recovery().update_recovery_state_no_fail().await;
644    }
645
646    #[instrument]
647    async fn default_key_event_handler(_: SecretStorageDefaultKeyEvent, client: Client) {
648        client.encryption().recovery().update_recovery_state_no_fail().await;
649    }
650
651    /// Listen for changes in the [`BackupState`] and, if necessary, update the
652    /// [`RecoveryState`] accordingly.
653    ///
654    /// This should not be called directly, this method is put into a background
655    /// task which is always listening for updates in the [`BackupState`].
656    pub(crate) fn update_state_after_backup_state_change(
657        client: &Client,
658    ) -> impl Future<Output = ()> {
659        let mut stream = client.encryption().backups().state_stream();
660        let weak = WeakClient::from_client(client);
661
662        async move {
663            while let Some(update) = stream.next().await {
664                if let Some(client) = weak.get() {
665                    match update {
666                        Ok(update) => {
667                            // The recovery state only cares about these two states, the
668                            // intermediate states that tell us that
669                            // we're creating a backup are not interesting.
670                            if matches!(update, BackupState::Unknown | BackupState::Enabled) {
671                                client
672                                    .encryption()
673                                    .recovery()
674                                    .update_recovery_state_no_fail()
675                                    .await;
676                            }
677                        }
678                        Err(_) => {
679                            // We missed some updates, let's update our state in case something
680                            // changed.
681                            client.encryption().recovery().update_recovery_state_no_fail().await;
682                        }
683                    }
684                } else {
685                    break;
686                }
687            }
688        }
689    }
690
691    #[instrument(skip_all)]
692    pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
693        if let Some(user_id) = self.client.user_id() {
694            if response.master_keys.contains_key(user_id) {
695                // TODO: This is unnecessarily expensive, we could let the crypto crate notify
696                // us that our private keys got erased... But, the OlmMachine
697                // gets recreated and... You know the drill by now...
698                self.update_recovery_state_no_fail().await;
699            }
700        }
701    }
702}
703
704/// A helper struct that handles continues resetting a user's crypto identity
705/// after authentication was required and re-enabling backups (if necessary) at
706/// the end of it
707#[derive(Debug)]
708pub struct IdentityResetHandle {
709    client: Client,
710    cross_signing_reset_handle: CrossSigningResetHandle,
711}
712
713impl IdentityResetHandle {
714    /// Get the underlying [`CrossSigningResetAuthType`] this identity reset
715    /// process is using.
716    pub fn auth_type(&self) -> &CrossSigningResetAuthType {
717        &self.cross_signing_reset_handle.auth_type
718    }
719
720    /// This method will retry to upload the device keys after the previous try
721    /// failed due to required authentication
722    pub async fn reset(&self, auth: Option<AuthData>) -> Result<()> {
723        self.cross_signing_reset_handle.auth(auth).await?;
724
725        if self.client.encryption().recovery().should_auto_enable_backups().await? {
726            self.client.encryption().recovery().enable_backup().await?;
727        }
728
729        Ok(())
730    }
731
732    /// Cancel the ongoing identity reset process
733    pub async fn cancel(&self) {
734        self.cross_signing_reset_handle.cancel().await;
735    }
736}