matrix_sdk/authentication/matrix/
mod.rs

1// Copyright 2020 Damir Jelić
2// Copyright 2020 The Matrix.org Foundation C.I.C.
3// Copyright 2022 Famedly GmbH
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! Types to interact with the native Matrix authentication API.
18
19use std::fmt;
20#[cfg(feature = "sso-login")]
21use std::future::Future;
22
23use matrix_sdk_base::{SessionMeta, store::RoomLoadSettings};
24use ruma::{
25    api::{
26        OutgoingRequest, SendAccessToken,
27        client::{
28            account::register,
29            session::{
30                get_login_types, login, logout, refresh_token, sso_login, sso_login_with_provider,
31            },
32            uiaa::UserIdentifier,
33        },
34    },
35    serde::JsonObject,
36};
37use serde::{Deserialize, Serialize};
38use thiserror::Error;
39use tracing::{debug, error, info, instrument};
40use url::Url;
41
42use crate::{
43    Client, Error, RefreshTokenError, Result,
44    authentication::AuthData,
45    client::SessionChange,
46    error::{HttpError, HttpResult},
47};
48
49mod login_builder;
50
51pub use self::login_builder::LoginBuilder;
52#[cfg(feature = "sso-login")]
53pub use self::login_builder::SsoLoginBuilder;
54use super::SessionTokens;
55
56/// A high-level API to interact with the native Matrix authentication API.
57///
58/// To access this API, use [`Client::matrix_auth()`].
59#[derive(Debug, Clone)]
60pub struct MatrixAuth {
61    client: Client,
62}
63
64/// Errors that can occur when using the SSO API.
65#[derive(Debug, Error)]
66pub enum SsoError {
67    /// The supplied callback URL used to complete SSO is invalid.
68    #[error("callback URL invalid")]
69    CallbackUrlInvalid,
70}
71
72impl MatrixAuth {
73    pub(crate) fn new(client: Client) -> Self {
74        Self { client }
75    }
76
77    /// Gets the homeserver’s supported login types.
78    ///
79    /// This should be the first step when trying to log in so you can call the
80    /// appropriate method for the next step.
81    pub async fn get_login_types(&self) -> HttpResult<get_login_types::v3::Response> {
82        let request = get_login_types::v3::Request::new();
83        self.client.send(request).await
84    }
85
86    /// Get the URL to use to log in via Single Sign-On.
87    ///
88    /// Returns a URL that should be opened in a web browser to let the user
89    /// log in.
90    ///
91    /// After a successful login, the loginToken received at the redirect URL
92    /// should be used to log in with [`login_token`].
93    ///
94    /// # Arguments
95    ///
96    /// * `redirect_url` - The URL that will receive a `loginToken` after a
97    ///   successful SSO login.
98    ///
99    /// * `idp_id` - The optional ID of the identity provider to log in with.
100    ///
101    /// [`login_token`]: #method.login_token
102    pub async fn get_sso_login_url(
103        &self,
104        redirect_url: &str,
105        idp_id: Option<&str>,
106    ) -> Result<String> {
107        let homeserver = self.client.homeserver();
108        let supported_versions = self.client.supported_versions().await?;
109
110        let request = if let Some(id) = idp_id {
111            sso_login_with_provider::v3::Request::new(id.to_owned(), redirect_url.to_owned())
112                .try_into_http_request::<Vec<u8>>(
113                    homeserver.as_str(),
114                    SendAccessToken::None,
115                    &supported_versions,
116                )
117        } else {
118            sso_login::v3::Request::new(redirect_url.to_owned()).try_into_http_request::<Vec<u8>>(
119                homeserver.as_str(),
120                SendAccessToken::None,
121                &supported_versions,
122            )
123        };
124
125        match request {
126            Ok(req) => Ok(req.uri().to_string()),
127            Err(err) => Err(Error::from(HttpError::IntoHttp(err))),
128        }
129    }
130
131    /// Log into the server with a username and password.
132    ///
133    /// This can be used for the first login as well as for subsequent logins,
134    /// note that if the device ID isn't provided a new device will be created.
135    ///
136    /// If this isn't the first login, a device ID should be provided through
137    /// [`LoginBuilder::device_id`] to restore the correct stores.
138    ///
139    /// Alternatively the [`restore_session`] method can be used to restore a
140    /// logged-in client without the password.
141    ///
142    /// # Arguments
143    ///
144    /// * `user` - The user ID or user ID localpart of the user that should be
145    ///   logged into the homeserver.
146    ///
147    /// * `password` - The password of the user.
148    ///
149    /// # Examples
150    ///
151    /// ```no_run
152    /// # use url::Url;
153    /// # let homeserver = Url::parse("http://example.com").unwrap();
154    /// # futures_executor::block_on(async {
155    /// use matrix_sdk::Client;
156    ///
157    /// let client = Client::new(homeserver).await?;
158    /// let user = "example";
159    ///
160    /// let response = client
161    ///     .matrix_auth()
162    ///     .login_username(user, "wordpass")
163    ///     .initial_device_display_name("My bot")
164    ///     .await?;
165    ///
166    /// println!(
167    ///     "Logged in as {user}, got device_id {} and access_token {}",
168    ///     response.device_id, response.access_token,
169    /// );
170    /// # anyhow::Ok(()) });
171    /// ```
172    ///
173    /// [`restore_session`]: #method.restore_session
174    pub fn login_username(&self, id: impl AsRef<str>, password: &str) -> LoginBuilder {
175        self.login_identifier(UserIdentifier::UserIdOrLocalpart(id.as_ref().to_owned()), password)
176    }
177
178    /// Log into the server with a user identifier and password.
179    ///
180    /// This is a more general form of [`login_username`][Self::login_username]
181    /// that also accepts third-party identifiers instead of just the user ID or
182    /// its localpart.
183    pub fn login_identifier(&self, id: UserIdentifier, password: &str) -> LoginBuilder {
184        LoginBuilder::new_password(self.clone(), id, password.to_owned())
185    }
186
187    /// Log into the server with a custom login type.
188    ///
189    /// # Arguments
190    ///
191    /// * `login_type` - Identifier of the custom login type, e.g.
192    ///   `org.matrix.login.jwt`
193    ///
194    /// * `data` - The additional data which should be attached to the login
195    ///   request.
196    ///
197    /// # Examples
198    ///
199    /// ```no_run
200    /// # use url::Url;
201    /// # let homeserver = Url::parse("http://example.com").unwrap();
202    /// # async {
203    /// use matrix_sdk::Client;
204    ///
205    /// let client = Client::new(homeserver).await?;
206    /// let user = "example";
207    ///
208    /// let response = client
209    ///     .matrix_auth()
210    ///     .login_custom(
211    ///         "org.matrix.login.jwt",
212    ///         [("token".to_owned(), "jwt_token_content".into())]
213    ///             .into_iter()
214    ///             .collect(),
215    ///     )?
216    ///     .initial_device_display_name("My bot")
217    ///     .await?;
218    ///
219    /// println!(
220    ///     "Logged in as {user}, got device_id {} and access_token {}",
221    ///     response.device_id, response.access_token,
222    /// );
223    /// # anyhow::Ok(()) };
224    /// ```
225    pub fn login_custom(
226        &self,
227        login_type: &str,
228        data: JsonObject,
229    ) -> serde_json::Result<LoginBuilder> {
230        LoginBuilder::new_custom(self.clone(), login_type, data)
231    }
232
233    /// Log into the server with a token.
234    ///
235    /// This token is usually received in the SSO flow after following the URL
236    /// provided by [`get_sso_login_url`], note that this is not the access
237    /// token of a session.
238    ///
239    /// This should only be used for the first login.
240    ///
241    /// The [`restore_session`] method should be used to restore a logged-in
242    /// client after the first login.
243    ///
244    /// A device ID should be provided through [`LoginBuilder::device_id`] to
245    /// restore the correct stores, if the device ID isn't provided a new
246    /// device will be created.
247    ///
248    /// # Arguments
249    ///
250    /// * `token` - A login token.
251    ///
252    /// # Examples
253    ///
254    /// ```no_run
255    /// use matrix_sdk::Client;
256    /// # use url::Url;
257    /// # let homeserver = Url::parse("https://example.com").unwrap();
258    /// # let redirect_url = "http://localhost:1234";
259    /// # let login_token = "token";
260    /// # async {
261    /// let client = Client::new(homeserver).await.unwrap();
262    /// let auth = client.matrix_auth();
263    /// let sso_url = auth.get_sso_login_url(redirect_url, None);
264    ///
265    /// // Let the user authenticate at the SSO URL.
266    /// // Receive the loginToken param at the redirect_url.
267    ///
268    /// let response = auth
269    ///     .login_token(login_token)
270    ///     .initial_device_display_name("My app")
271    ///     .await
272    ///     .unwrap();
273    ///
274    /// println!(
275    ///     "Logged in as {}, got device_id {} and access_token {}",
276    ///     response.user_id, response.device_id, response.access_token,
277    /// );
278    /// # };
279    /// ```
280    ///
281    /// [`get_sso_login_url`]: #method.get_sso_login_url
282    /// [`restore_session`]: #method.restore_session
283    pub fn login_token(&self, token: &str) -> LoginBuilder {
284        LoginBuilder::new_token(self.clone(), token.to_owned())
285    }
286
287    /// A higher level wrapper around the methods to complete an SSO login after
288    /// the user has logged in through a webview. This method should be used
289    /// in tandem with [`MatrixAuth::get_sso_login_url`].
290    ///
291    /// # Arguments
292    ///
293    /// * `callback_url` - The received callback URL carrying the login token.
294    ///
295    /// # Examples
296    ///
297    /// ```no_run
298    /// use matrix_sdk::Client;
299    /// # use url::Url;
300    /// # let homeserver = Url::parse("https://example.com").unwrap();
301    /// # let redirect_url = "http://localhost:1234";
302    /// # let callback_url = Url::parse("http://localhost:1234?loginToken=token").unwrap();
303    /// # async {
304    /// let client = Client::new(homeserver).await.unwrap();
305    /// let auth = client.matrix_auth();
306    /// let sso_url = auth.get_sso_login_url(redirect_url, None);
307    ///
308    /// // Let the user authenticate at the SSO URL.
309    /// // Receive the callback_url.
310    ///
311    /// let response = auth
312    ///     .login_with_sso_callback(callback_url)
313    ///     .unwrap()
314    ///     .initial_device_display_name("My app")
315    ///     .await
316    ///     .unwrap();
317    ///
318    /// println!(
319    ///     "Logged in as {}, got device_id {} and access_token {}",
320    ///     response.user_id, response.device_id, response.access_token,
321    /// );
322    /// # };
323    /// ```
324    pub fn login_with_sso_callback(&self, callback_url: Url) -> Result<LoginBuilder, SsoError> {
325        #[derive(Deserialize)]
326        struct QueryParameters {
327            #[serde(rename = "loginToken")]
328            login_token: Option<String>,
329        }
330
331        let query_string = callback_url.query().unwrap_or_default();
332        let query: QueryParameters =
333            serde_html_form::from_str(query_string).map_err(|_| SsoError::CallbackUrlInvalid)?;
334        let token = query.login_token.ok_or(SsoError::CallbackUrlInvalid)?;
335
336        Ok(self.login_token(token.as_str()))
337    }
338
339    /// Log into the server via Single Sign-On.
340    ///
341    /// This takes care of the whole SSO flow:
342    ///   * Spawn a local http server
343    ///   * Provide a callback to open the SSO login URL in a web browser
344    ///   * Wait for the local http server to get the loginToken
345    ///   * Call [`login_token`]
346    ///
347    /// If cancellation is needed the method should be wrapped in a cancellable
348    /// task. **Note** that users with root access to the system have the
349    /// ability to snoop in on the data/token that is passed to the local
350    /// HTTP server that will be spawned.
351    ///
352    /// If you need more control over the SSO login process, you should use
353    /// [`get_sso_login_url`] and [`login_token`] directly.
354    ///
355    /// This should only be used for the first login.
356    ///
357    /// The [`restore_session`] method should be used to restore a logged-in
358    /// client after the first login.
359    ///
360    /// # Arguments
361    ///
362    /// * `use_sso_login_url` - A callback that will receive the SSO Login URL.
363    ///   It should usually be used to open the SSO URL in a browser and must
364    ///   return `Ok(())` if the URL was successfully opened. If it returns
365    ///   `Err`, the error will be forwarded.
366    ///
367    /// # Examples
368    ///
369    /// ```no_run
370    /// use matrix_sdk::Client;
371    /// # use url::Url;
372    /// # let homeserver = Url::parse("https://example.com").unwrap();
373    /// # async {
374    /// let client = Client::new(homeserver).await.unwrap();
375    ///
376    /// let response = client
377    ///     .matrix_auth()
378    ///     .login_sso(|sso_url| async move {
379    ///         // Open sso_url
380    ///         Ok(())
381    ///     })
382    ///     .initial_device_display_name("My app")
383    ///     .await
384    ///     .unwrap();
385    ///
386    /// println!(
387    ///     "Logged in as {}, got device_id {} and access_token {}",
388    ///     response.user_id, response.device_id, response.access_token
389    /// );
390    /// # };
391    /// ```
392    ///
393    /// [`get_sso_login_url`]: #method.get_sso_login_url
394    /// [`login_token`]: #method.login_token
395    /// [`restore_session`]: #method.restore_session
396    #[cfg(feature = "sso-login")]
397    pub fn login_sso<F, Fut>(&self, use_sso_login_url: F) -> SsoLoginBuilder<F>
398    where
399        F: FnOnce(String) -> Fut + Send,
400        Fut: Future<Output = Result<()>> + Send,
401    {
402        SsoLoginBuilder::new(self.clone(), use_sso_login_url)
403    }
404
405    /// Is the client logged in using the native Matrix authentication API.
406    pub fn logged_in(&self) -> bool {
407        self.client
408            .auth_ctx()
409            .auth_data
410            .get()
411            .is_some_and(|auth_data| matches!(auth_data, AuthData::Matrix))
412    }
413
414    /// Refresh the access token.
415    ///
416    /// When support for [refreshing access tokens] is activated on both the
417    /// homeserver and the client, access tokens have an expiration date and
418    /// need to be refreshed periodically. To activate support for refresh
419    /// tokens in the [`Client`], it needs to be done at login with the
420    /// [`LoginBuilder::request_refresh_token()`] method, or during account
421    /// registration.
422    ///
423    /// This method doesn't need to be called if
424    /// [`ClientBuilder::handle_refresh_tokens()`] is called during construction
425    /// of the `Client`. Otherwise, it should be called once when a refresh
426    /// token is available and an [`UnknownToken`] error is received.
427    /// If this call fails with another [`UnknownToken`] error, it means that
428    /// the session needs to be logged in again.
429    ///
430    /// It can also be called at any time when a refresh token is available, it
431    /// will invalidate the previous access token.
432    ///
433    /// The new tokens in the response will be used by the `Client` and should
434    /// be persisted to be able to [restore the session]. The response will
435    /// always contain an access token that replaces the previous one. It
436    /// can also contain a refresh token, in which case it will also replace
437    /// the previous one.
438    ///
439    /// This method is protected behind a lock, so calling this method several
440    /// times at once will only call the endpoint once and all subsequent calls
441    /// will wait for the result of the first call. The first call will
442    /// return `Ok(Some(response))` or the [`HttpError`] returned by the
443    /// endpoint, while the others will return `Ok(None)` if the token was
444    /// refreshed by the first call or a [`RefreshTokenError`] error, if it
445    /// failed.
446    ///
447    /// # Examples
448    ///
449    /// ```no_run
450    /// use matrix_sdk::{Client, Error};
451    /// use url::Url;
452    /// # async {
453    /// # fn get_credentials() -> (&'static str, &'static str) { ("", "") };
454    /// # fn persist_session(_: Option<matrix_sdk::AuthSession>) {};
455    ///
456    /// let homeserver = Url::parse("http://example.com")?;
457    /// let client = Client::new(homeserver).await?;
458    ///
459    /// let (user, password) = get_credentials();
460    /// let response = client
461    ///     .matrix_auth()
462    ///     .login_username(user, password)
463    ///     .initial_device_display_name("My App")
464    ///     .request_refresh_token()
465    ///     .send()
466    ///     .await?;
467    ///
468    /// persist_session(client.session());
469    ///
470    /// // Handle when an `M_UNKNOWN_TOKEN` error is encountered.
471    /// async fn on_unknown_token_err(client: &Client) -> Result<(), Error> {
472    ///     let auth = client.matrix_auth();
473    ///
474    ///     if client
475    ///         .session_tokens()
476    ///         .and_then(|tokens| tokens.refresh_token)
477    ///         .is_some()
478    ///         && auth.refresh_access_token().await.is_ok()
479    ///     {
480    ///         persist_session(client.session());
481    ///         return Ok(());
482    ///     }
483    ///
484    ///     let (user, password) = get_credentials();
485    ///     auth.login_username(user, password)
486    ///         .request_refresh_token()
487    ///         .send()
488    ///         .await?;
489    ///
490    ///     persist_session(client.session());
491    ///
492    ///     Ok(())
493    /// }
494    /// # anyhow::Ok(()) };
495    /// ```
496    ///
497    /// [refreshing access tokens]: https://spec.matrix.org/v1.3/client-server-api/#refreshing-access-tokens
498    /// [`UnknownToken`]: ruma::api::client::error::ErrorKind::UnknownToken
499    /// [restore the session]: Client::restore_session
500    /// [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens
501    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
502        macro_rules! fail {
503            ($lock:expr, $err:expr) => {
504                let error = $err;
505                *$lock = Err(error.clone());
506                return Err(error);
507            };
508        }
509
510        let refresh_token_lock = &self.client.auth_ctx().refresh_token_lock;
511        let Ok(mut guard) = refresh_token_lock.try_lock() else {
512            // Somebody else is also doing a token refresh; wait for it to finish first.
513            return refresh_token_lock.lock().await.clone();
514        };
515
516        let Some(mut session_tokens) = self.client.session_tokens() else {
517            fail!(guard, RefreshTokenError::RefreshTokenRequired);
518        };
519        let Some(refresh_token) = session_tokens.refresh_token.clone() else {
520            fail!(guard, RefreshTokenError::RefreshTokenRequired);
521        };
522
523        let request = refresh_token::v3::Request::new(refresh_token);
524        let res = self.client.send_inner(request, None, Default::default()).await;
525
526        match res {
527            Ok(res) => {
528                *guard = Ok(());
529
530                session_tokens.access_token = res.access_token;
531                if let Some(refresh_token) = res.refresh_token {
532                    session_tokens.refresh_token = Some(refresh_token);
533                }
534
535                self.client.auth_ctx().set_session_tokens(session_tokens);
536
537                if let Some(save_session_callback) =
538                    self.client.inner.auth_ctx.save_session_callback.get()
539                    && let Err(err) = save_session_callback(self.client.clone())
540                {
541                    error!("when saving session after refresh: {err}");
542                }
543
544                _ = self
545                    .client
546                    .inner
547                    .auth_ctx
548                    .session_change_sender
549                    .send(SessionChange::TokensRefreshed);
550
551                Ok(())
552            }
553            Err(error) => {
554                let error = RefreshTokenError::MatrixAuth(error.into());
555                fail!(guard, error);
556            }
557        }
558    }
559
560    /// Register a user to the server.
561    ///
562    /// If registration was successful and a session token was returned by the
563    /// server, the client session is set (the client is logged in).
564    ///
565    /// # Arguments
566    ///
567    /// * `registration` - The easiest way to create this request is using the
568    ///   [`register::v3::Request`] itself.
569    ///
570    /// # Examples
571    ///
572    /// ```no_run
573    /// use matrix_sdk::{
574    ///     Client,
575    ///     ruma::api::client::{
576    ///         account::register::v3::Request as RegistrationRequest, uiaa,
577    ///     },
578    /// };
579    /// # use url::Url;
580    /// # let homeserver = Url::parse("http://example.com").unwrap();
581    /// # async {
582    ///
583    /// let mut request = RegistrationRequest::new();
584    /// request.username = Some("user".to_owned());
585    /// request.password = Some("password".to_owned());
586    /// request.auth = Some(uiaa::AuthData::FallbackAcknowledgement(
587    ///     uiaa::FallbackAcknowledgement::new("foobar".to_owned()),
588    /// ));
589    ///
590    /// let client = Client::new(homeserver).await.unwrap();
591    /// client.matrix_auth().register(request).await;
592    /// # };
593    /// ```
594    #[instrument(skip_all)]
595    pub async fn register(&self, request: register::v3::Request) -> Result<register::v3::Response> {
596        let homeserver = self.client.homeserver();
597        info!("Registering to {homeserver}");
598
599        #[cfg(feature = "e2e-encryption")]
600        let login_info = match (&request.username, &request.password) {
601            (Some(u), Some(p)) => Some(login::v3::LoginInfo::Password(login::v3::Password::new(
602                UserIdentifier::UserIdOrLocalpart(u.into()),
603                p.clone(),
604            ))),
605            _ => None,
606        };
607
608        let response = self.client.send(request).await?;
609        if let Some(session) = MatrixSession::from_register_response(&response) {
610            let _ = self
611                .set_session(
612                    session,
613                    RoomLoadSettings::default(),
614                    #[cfg(feature = "e2e-encryption")]
615                    login_info,
616                )
617                .await;
618        }
619        Ok(response)
620    }
621    /// Log out the current user.
622    pub async fn logout(&self) -> HttpResult<logout::v3::Response> {
623        let request = logout::v3::Request::new();
624        self.client.send(request).await
625    }
626
627    /// Get the whole native Matrix authentication session info of this client.
628    ///
629    /// Will be `None` if the client has not been logged in with the native
630    /// Matrix Authentication API.
631    ///
632    /// Can be used with [`MatrixAuth::restore_session`] to restore a previously
633    /// logged-in session.
634    pub fn session(&self) -> Option<MatrixSession> {
635        let meta = self.client.session_meta()?;
636        let tokens = self.client.session_tokens()?;
637        Some(MatrixSession { meta: meta.to_owned(), tokens })
638    }
639
640    /// Restore a previously logged in session.
641    ///
642    /// This can be used to restore the client to a logged in state, loading all
643    /// the stored state and encryption keys.
644    ///
645    /// Alternatively, if the whole session isn't stored the [`login`] method
646    /// can be used with a device ID.
647    ///
648    /// # Arguments
649    ///
650    /// * `session` - A session that the user already has from a previous login
651    ///   call.
652    ///
653    /// * `room_load_settings` — Specify how many rooms must be restored; use
654    ///   `::default()` if you don't know which value to pick.
655    ///
656    /// # Panics
657    ///
658    /// Panics if a session was already restored or logged in.
659    ///
660    /// # Examples
661    ///
662    /// ```no_run
663    /// use matrix_sdk::{
664    ///     Client, SessionMeta, SessionTokens,
665    ///     authentication::matrix::MatrixSession,
666    ///     ruma::{device_id, user_id},
667    /// };
668    /// # use url::Url;
669    /// # async {
670    ///
671    /// let homeserver = Url::parse("http://example.com")?;
672    /// let client = Client::new(homeserver).await?;
673    ///
674    /// let session = MatrixSession {
675    ///     meta: SessionMeta {
676    ///         user_id: user_id!("@example:localhost").to_owned(),
677    ///         device_id: device_id!("MYDEVICEID").to_owned(),
678    ///     },
679    ///     tokens: SessionTokens {
680    ///         access_token: "My-Token".to_owned(),
681    ///         refresh_token: None,
682    ///     },
683    /// };
684    ///
685    /// client.restore_session(session).await?;
686    /// # anyhow::Ok(()) };
687    /// ```
688    ///
689    /// The `MatrixSession` object can also be created from the response the
690    /// [`LoginBuilder::send()`] method returns:
691    ///
692    /// ```no_run
693    /// use matrix_sdk::{Client, store::RoomLoadSettings};
694    /// use url::Url;
695    /// # async {
696    ///
697    /// let homeserver = Url::parse("http://example.com")?;
698    /// let client = Client::new(homeserver).await?;
699    /// let auth = client.matrix_auth();
700    ///
701    /// let response = auth.login_username("example", "my-password").send().await?;
702    ///
703    /// // Persist the `MatrixSession` so it can later be used to restore the login.
704    ///
705    /// auth.restore_session((&response).into(), RoomLoadSettings::default())
706    ///     .await?;
707    /// # anyhow::Ok(()) };
708    /// ```
709    ///
710    /// [`login`]: #method.login
711    /// [`LoginBuilder::send()`]: crate::authentication::matrix::LoginBuilder::send
712    #[instrument(skip_all)]
713    pub async fn restore_session(
714        &self,
715        session: MatrixSession,
716        room_load_settings: RoomLoadSettings,
717    ) -> Result<()> {
718        debug!("Restoring Matrix auth session");
719        self.set_session(
720            session,
721            room_load_settings,
722            #[cfg(feature = "e2e-encryption")]
723            None,
724        )
725        .await?;
726        debug!("Done restoring Matrix auth session");
727        Ok(())
728    }
729
730    /// Receive a login response and update the homeserver and the base client
731    /// if needed.
732    ///
733    /// # Arguments
734    ///
735    /// * `response` - A successful login response.
736    pub(crate) async fn receive_login_response(
737        &self,
738        response: &login::v3::Response,
739        #[cfg(feature = "e2e-encryption")] login_info: Option<login::v3::LoginInfo>,
740    ) -> Result<()> {
741        self.client.maybe_update_login_well_known(response.well_known.as_ref());
742
743        self.set_session(
744            response.into(),
745            RoomLoadSettings::default(),
746            #[cfg(feature = "e2e-encryption")]
747            login_info,
748        )
749        .await?;
750
751        Ok(())
752    }
753
754    /// Set the Matrix authentication session.
755    ///
756    /// # Arguments
757    ///
758    /// * `session` — The session being opened.
759    ///
760    /// * `room_load_settings` — Specify how much rooms must be restored; use
761    ///   `::default()` if you don't know which value to pick.
762    ///
763    /// # Panic
764    ///
765    /// Panics if authentication data was already set.
766    async fn set_session(
767        &self,
768        session: MatrixSession,
769        room_load_settings: RoomLoadSettings,
770        #[cfg(feature = "e2e-encryption")] login_info: Option<login::v3::LoginInfo>,
771    ) -> Result<()> {
772        // This API doesn't have any data but by setting this variant we protect the
773        // user from using both authentication APIs at once.
774        self.client
775            .auth_ctx()
776            .auth_data
777            .set(AuthData::Matrix)
778            .expect("Client authentication data was already set");
779        self.client.auth_ctx().set_session_tokens(session.tokens);
780        self.client
781            .base_client()
782            .activate(
783                session.meta,
784                room_load_settings,
785                #[cfg(feature = "e2e-encryption")]
786                None,
787            )
788            .await?;
789
790        #[cfg(feature = "e2e-encryption")]
791        {
792            use ruma::api::client::uiaa::{AuthData, Password};
793
794            let auth_data = match login_info {
795                Some(login::v3::LoginInfo::Password(login::v3::Password {
796                    identifier: Some(identifier),
797                    password,
798                    ..
799                })) => Some(AuthData::Password(Password::new(identifier, password))),
800                // Other methods can't be immediately translated to an auth.
801                _ => None,
802            };
803
804            self.client.encryption().spawn_initialization_task(auth_data).await;
805        }
806
807        Ok(())
808    }
809}
810
811/// A user session using the native Matrix authentication API.
812///
813/// # Examples
814///
815/// ```
816/// use matrix_sdk::{
817///     SessionMeta, SessionTokens, authentication::matrix::MatrixSession,
818/// };
819/// use ruma::{device_id, user_id};
820///
821/// let session = MatrixSession {
822///     meta: SessionMeta {
823///         user_id: user_id!("@example:localhost").to_owned(),
824///         device_id: device_id!("MYDEVICEID").to_owned(),
825///     },
826///     tokens: SessionTokens {
827///         access_token: "My-Token".to_owned(),
828///         refresh_token: None,
829///     },
830/// };
831///
832/// assert_eq!(session.meta.device_id.as_str(), "MYDEVICEID");
833/// ```
834#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
835pub struct MatrixSession {
836    /// The Matrix user session info.
837    #[serde(flatten)]
838    pub meta: SessionMeta,
839
840    /// The tokens used for authentication.
841    #[serde(flatten)]
842    pub tokens: SessionTokens,
843}
844
845#[cfg(not(tarpaulin_include))]
846impl fmt::Debug for MatrixSession {
847    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
848        f.debug_struct("MatrixSession").field("meta", &self.meta).finish_non_exhaustive()
849    }
850}
851
852impl From<&login::v3::Response> for MatrixSession {
853    fn from(response: &login::v3::Response) -> Self {
854        let login::v3::Response { user_id, access_token, device_id, refresh_token, .. } = response;
855        Self {
856            meta: SessionMeta { user_id: user_id.clone(), device_id: device_id.clone() },
857            tokens: SessionTokens {
858                access_token: access_token.clone(),
859                refresh_token: refresh_token.clone(),
860            },
861        }
862    }
863}
864
865impl MatrixSession {
866    #[allow(clippy::question_mark)] // clippy falsely complains about the let-unpacking
867    fn from_register_response(response: &register::v3::Response) -> Option<Self> {
868        let register::v3::Response { user_id, access_token, device_id, refresh_token, .. } =
869            response;
870        Some(Self {
871            meta: SessionMeta { user_id: user_id.clone(), device_id: device_id.clone()? },
872            tokens: SessionTokens {
873                access_token: access_token.clone()?,
874                refresh_token: refresh_token.clone(),
875            },
876        })
877    }
878}