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::{store::RoomLoadSettings, SessionMeta};
24use ruma::{
25 api::{
26 client::{
27 account::register,
28 session::{
29 get_login_types, login, logout, refresh_token, sso_login, sso_login_with_provider,
30 },
31 uiaa::UserIdentifier,
32 },
33 OutgoingRequest, SendAccessToken,
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 authentication::AuthData,
44 client::SessionChange,
45 error::{HttpError, HttpResult},
46 Client, Error, RefreshTokenError, Result,
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 server_versions = self.client.server_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 &server_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 &server_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 {
540 if let Err(err) = save_session_callback(self.client.clone()) {
541 error!("when saving session after refresh: {err}");
542 }
543 }
544
545 _ = self
546 .client
547 .inner
548 .auth_ctx
549 .session_change_sender
550 .send(SessionChange::TokensRefreshed);
551
552 Ok(())
553 }
554 Err(error) => {
555 let error = RefreshTokenError::MatrixAuth(error.into());
556 fail!(guard, error);
557 }
558 }
559 }
560
561 /// Register a user to the server.
562 ///
563 /// If registration was successful and a session token was returned by the
564 /// server, the client session is set (the client is logged in).
565 ///
566 /// # Arguments
567 ///
568 /// * `registration` - The easiest way to create this request is using the
569 /// [`register::v3::Request`] itself.
570 ///
571 /// # Examples
572 ///
573 /// ```no_run
574 /// use matrix_sdk::{
575 /// ruma::api::client::{
576 /// account::register::v3::Request as RegistrationRequest, uiaa,
577 /// },
578 /// Client,
579 /// };
580 /// # use url::Url;
581 /// # let homeserver = Url::parse("http://example.com").unwrap();
582 /// # async {
583 ///
584 /// let mut request = RegistrationRequest::new();
585 /// request.username = Some("user".to_owned());
586 /// request.password = Some("password".to_owned());
587 /// request.auth = Some(uiaa::AuthData::FallbackAcknowledgement(
588 /// uiaa::FallbackAcknowledgement::new("foobar".to_owned()),
589 /// ));
590 ///
591 /// let client = Client::new(homeserver).await.unwrap();
592 /// client.matrix_auth().register(request).await;
593 /// # };
594 /// ```
595 #[instrument(skip_all)]
596 pub async fn register(&self, request: register::v3::Request) -> Result<register::v3::Response> {
597 let homeserver = self.client.homeserver();
598 info!("Registering to {homeserver}");
599
600 #[cfg(feature = "e2e-encryption")]
601 let login_info = match (&request.username, &request.password) {
602 (Some(u), Some(p)) => Some(login::v3::LoginInfo::Password(login::v3::Password::new(
603 UserIdentifier::UserIdOrLocalpart(u.into()),
604 p.clone(),
605 ))),
606 _ => None,
607 };
608
609 let response = self.client.send(request).await?;
610 if let Some(session) = MatrixSession::from_register_response(&response) {
611 let _ = self
612 .set_session(
613 session,
614 RoomLoadSettings::default(),
615 #[cfg(feature = "e2e-encryption")]
616 login_info,
617 )
618 .await;
619 }
620 Ok(response)
621 }
622 /// Log out the current user.
623 pub async fn logout(&self) -> HttpResult<logout::v3::Response> {
624 let request = logout::v3::Request::new();
625 self.client.send(request).await
626 }
627
628 /// Get the whole native Matrix authentication session info of this client.
629 ///
630 /// Will be `None` if the client has not been logged in with the native
631 /// Matrix Authentication API.
632 ///
633 /// Can be used with [`MatrixAuth::restore_session`] to restore a previously
634 /// logged-in session.
635 pub fn session(&self) -> Option<MatrixSession> {
636 let meta = self.client.session_meta()?;
637 let tokens = self.client.session_tokens()?;
638 Some(MatrixSession { meta: meta.to_owned(), tokens })
639 }
640
641 /// Restore a previously logged in session.
642 ///
643 /// This can be used to restore the client to a logged in state, loading all
644 /// the stored state and encryption keys.
645 ///
646 /// Alternatively, if the whole session isn't stored the [`login`] method
647 /// can be used with a device ID.
648 ///
649 /// # Arguments
650 ///
651 /// * `session` - A session that the user already has from a previous login
652 /// call.
653 ///
654 /// * `room_load_settings` — Specify how many rooms must be restored; use
655 /// `::default()` if you don't know which value to pick.
656 ///
657 /// # Panics
658 ///
659 /// Panics if a session was already restored or logged in.
660 ///
661 /// # Examples
662 ///
663 /// ```no_run
664 /// use matrix_sdk::{
665 /// authentication::matrix::MatrixSession,
666 /// ruma::{device_id, user_id},
667 /// Client, SessionMeta, SessionTokens,
668 /// };
669 /// # use url::Url;
670 /// # async {
671 ///
672 /// let homeserver = Url::parse("http://example.com")?;
673 /// let client = Client::new(homeserver).await?;
674 ///
675 /// let session = MatrixSession {
676 /// meta: SessionMeta {
677 /// user_id: user_id!("@example:localhost").to_owned(),
678 /// device_id: device_id!("MYDEVICEID").to_owned(),
679 /// },
680 /// tokens: SessionTokens {
681 /// access_token: "My-Token".to_owned(),
682 /// refresh_token: None,
683 /// },
684 /// };
685 ///
686 /// client.restore_session(session).await?;
687 /// # anyhow::Ok(()) };
688 /// ```
689 ///
690 /// The `MatrixSession` object can also be created from the response the
691 /// [`LoginBuilder::send()`] method returns:
692 ///
693 /// ```no_run
694 /// use matrix_sdk::{store::RoomLoadSettings, Client};
695 /// use url::Url;
696 /// # async {
697 ///
698 /// let homeserver = Url::parse("http://example.com")?;
699 /// let client = Client::new(homeserver).await?;
700 /// let auth = client.matrix_auth();
701 ///
702 /// let response = auth.login_username("example", "my-password").send().await?;
703 ///
704 /// // Persist the `MatrixSession` so it can later be used to restore the login.
705 ///
706 /// auth.restore_session((&response).into(), RoomLoadSettings::default())
707 /// .await?;
708 /// # anyhow::Ok(()) };
709 /// ```
710 ///
711 /// [`login`]: #method.login
712 /// [`LoginBuilder::send()`]: crate::authentication::matrix::LoginBuilder::send
713 #[instrument(skip_all)]
714 pub async fn restore_session(
715 &self,
716 session: MatrixSession,
717 room_load_settings: RoomLoadSettings,
718 ) -> Result<()> {
719 debug!("Restoring Matrix auth session");
720 self.set_session(
721 session,
722 room_load_settings,
723 #[cfg(feature = "e2e-encryption")]
724 None,
725 )
726 .await?;
727 debug!("Done restoring Matrix auth session");
728 Ok(())
729 }
730
731 /// Receive a login response and update the homeserver and the base client
732 /// if needed.
733 ///
734 /// # Arguments
735 ///
736 /// * `response` - A successful login response.
737 pub(crate) async fn receive_login_response(
738 &self,
739 response: &login::v3::Response,
740 #[cfg(feature = "e2e-encryption")] login_info: Option<login::v3::LoginInfo>,
741 ) -> Result<()> {
742 self.client.maybe_update_login_well_known(response.well_known.as_ref());
743
744 self.set_session(
745 response.into(),
746 RoomLoadSettings::default(),
747 #[cfg(feature = "e2e-encryption")]
748 login_info,
749 )
750 .await?;
751
752 Ok(())
753 }
754
755 /// Set the Matrix authentication session.
756 ///
757 /// # Arguments
758 ///
759 /// * `session` — The session being opened.
760 ///
761 /// * `room_load_settings` — Specify how much rooms must be restored; use
762 /// `::default()` if you don't know which value to pick.
763 ///
764 /// # Panic
765 ///
766 /// Panics if authentication data was already set.
767 async fn set_session(
768 &self,
769 session: MatrixSession,
770 room_load_settings: RoomLoadSettings,
771 #[cfg(feature = "e2e-encryption")] login_info: Option<login::v3::LoginInfo>,
772 ) -> Result<()> {
773 // This API doesn't have any data but by setting this variant we protect the
774 // user from using both authentication APIs at once.
775 self.client
776 .auth_ctx()
777 .auth_data
778 .set(AuthData::Matrix)
779 .expect("Client authentication data was already set");
780 self.client.auth_ctx().set_session_tokens(session.tokens);
781 self.client
782 .base_client()
783 .activate(
784 session.meta,
785 room_load_settings,
786 #[cfg(feature = "e2e-encryption")]
787 None,
788 )
789 .await?;
790
791 #[cfg(feature = "e2e-encryption")]
792 {
793 use ruma::api::client::uiaa::{AuthData, Password};
794
795 let auth_data = match login_info {
796 Some(login::v3::LoginInfo::Password(login::v3::Password {
797 identifier: Some(identifier),
798 password,
799 ..
800 })) => Some(AuthData::Password(Password::new(identifier, password))),
801 // Other methods can't be immediately translated to an auth.
802 _ => None,
803 };
804
805 self.client.encryption().spawn_initialization_task(auth_data);
806 }
807
808 Ok(())
809 }
810}
811
812/// A user session using the native Matrix authentication API.
813///
814/// # Examples
815///
816/// ```
817/// use matrix_sdk::{
818/// authentication::matrix::MatrixSession, SessionMeta, SessionTokens,
819/// };
820/// use ruma::{device_id, user_id};
821///
822/// let session = MatrixSession {
823/// meta: SessionMeta {
824/// user_id: user_id!("@example:localhost").to_owned(),
825/// device_id: device_id!("MYDEVICEID").to_owned(),
826/// },
827/// tokens: SessionTokens {
828/// access_token: "My-Token".to_owned(),
829/// refresh_token: None,
830/// },
831/// };
832///
833/// assert_eq!(session.meta.device_id.as_str(), "MYDEVICEID");
834/// ```
835#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
836pub struct MatrixSession {
837 /// The Matrix user session info.
838 #[serde(flatten)]
839 pub meta: SessionMeta,
840
841 /// The tokens used for authentication.
842 #[serde(flatten)]
843 pub tokens: SessionTokens,
844}
845
846#[cfg(not(tarpaulin_include))]
847impl fmt::Debug for MatrixSession {
848 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
849 f.debug_struct("MatrixSession").field("meta", &self.meta).finish_non_exhaustive()
850 }
851}
852
853impl From<&login::v3::Response> for MatrixSession {
854 fn from(response: &login::v3::Response) -> Self {
855 let login::v3::Response { user_id, access_token, device_id, refresh_token, .. } = response;
856 Self {
857 meta: SessionMeta { user_id: user_id.clone(), device_id: device_id.clone() },
858 tokens: SessionTokens {
859 access_token: access_token.clone(),
860 refresh_token: refresh_token.clone(),
861 },
862 }
863 }
864}
865
866impl MatrixSession {
867 #[allow(clippy::question_mark)] // clippy falsely complains about the let-unpacking
868 fn from_register_response(response: ®ister::v3::Response) -> Option<Self> {
869 let register::v3::Response { user_id, access_token, device_id, refresh_token, .. } =
870 response;
871 Some(Self {
872 meta: SessionMeta { user_id: user_id.clone(), device_id: device_id.clone()? },
873 tokens: SessionTokens {
874 access_token: access_token.clone()?,
875 refresh_token: refresh_token.clone(),
876 },
877 })
878 }
879}