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