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