Skip to main content

matrix_sdk/authentication/oauth/
mod.rs

1// Copyright 2022 Kévin Commaille
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! High-level OAuth 2.0 API.
16//!
17//! The OAuth 2.0 interactions with the Matrix API are currently a
18//! work-in-progress and are defined by [MSC3861] and its sub-proposals. And
19//! more documentation is available at [areweoidcyet.com].
20//!
21//! This authentication API is available with [`Client::oauth()`].
22//!
23//! # Homeserver support
24//!
25//! After building the client, you can check that the homeserver supports
26//! logging in via OAuth 2.0 when [`OAuth::server_metadata()`] succeeds.
27//!
28//! # Registration
29//!
30//! Clients must register with the homeserver before being able to interact with
31//! an OAuth 2.0 server.
32//!
33//! The registration consists in providing client metadata to the authorization
34//! server, to declare the interactions that the client supports with the
35//! homeserver. This step is important because the client cannot use a feature
36//! that is not declared during registration. In return, the server assigns an
37//! ID and eventually credentials to the client, which will allow to identify
38//! the client when authorization requests are made.
39//!
40//! Note that only public clients are supported by this API, i.e. clients
41//! without credentials.
42//!
43//! The registration step can be done automatically by providing a
44//! [`ClientRegistrationData`] to the login method.
45//!
46//! If the server supports dynamic registration, registration can be performed
47//! manually by using [`OAuth::register_client()`]. If dynamic registration is
48//! not available, the homeserver should document how to obtain a client ID. The
49//! client ID can then be provided with [`OAuth::restore_registered_client()`].
50//!
51//! # Login
52//!
53//! Currently, two login methods are supported by this API.
54//!
55//! ## Login with the Authorization Code flow
56//!
57//! The use of the Authorization Code flow is defined in [MSC2964] and [RFC
58//! 6749][rfc6749-auth-code].
59//!
60//! This method requires to open a URL in the end-user's browser where
61//! they will be able to log into their account in the server's web UI and grant
62//! access to their Matrix account.
63//!
64//! [`OAuth::login()`] constructs an [`OAuthAuthCodeUrlBuilder`] that can be
65//! configured, and then calling [`OAuthAuthCodeUrlBuilder::build()`] will
66//! provide the URL to present to the user in a web browser.
67//!
68//! After authenticating with the server, the user will be redirected to the
69//! provided redirect URI, with a code in the query that will allow to finish
70//! the login process by calling [`OAuth::finish_login()`].
71//!
72//! If the login needs to be cancelled before its completion,
73//! [`OAuth::abort_login()`] should be called to clean up the local data.
74//!
75//! ## Login by scanning a QR Code
76//!
77//! Logging in via a QR code is defined in [MSC4108]. It uses the Device
78//! authorization flow specified in [RFC 8628].
79//!
80//! This method requires to have another logged-in Matrix device that can
81//! display a QR Code.
82//!
83//! This login method is only available if the `e2e-encryption` cargo feature is
84//! enabled. It is not available on WASM.
85//!
86//! After scanning the QR Code, [`OAuth::login_with_qr_code()`] can be called
87//! with the QR Code's data. Then the different steps of the process need to be
88//! followed with [`LoginWithQrCode::subscribe_to_progress()`].
89//!
90//! A successful login using this method will automatically mark the device as
91//! verified and transfer all end-to-end encryption related secrets, like the
92//! private cross-signing keys and the backup key from the existing device to
93//! the new device.
94//!
95//! # Persisting/restoring a session
96//!
97//! The full session to persist can be obtained with [`OAuth::full_session()`].
98//! The different parts can also be retrieved with [`Client::session_meta()`],
99//! [`Client::session_tokens()`] and [`OAuth::client_id()`].
100//!
101//! To restore a previous session, use [`OAuth::restore_session()`].
102//!
103//! # Refresh tokens
104//!
105//! The use of refresh tokens with OAuth 2.0 servers is more common than in the
106//! Matrix specification. For this reason, it is recommended to configure the
107//! client with [`ClientBuilder::handle_refresh_tokens()`], to handle refreshing
108//! tokens automatically.
109//!
110//! Applications should then listen to session tokens changes after logging in
111//! with [`Client::subscribe_to_session_changes()`] to persist them on every
112//! change. If they are not persisted properly, the end-user will need to login
113//! again.
114//!
115//! # Unknown token error
116//!
117//! A request to the Matrix API can return an [`Error`] with an
118//! [`ErrorKind::UnknownToken`].
119//!
120//! The first step is to try to refresh the token with
121//! [`OAuth::refresh_access_token()`]. This step is done automatically if the
122//! client was built with [`ClientBuilder::handle_refresh_tokens()`].
123//!
124//! If refreshing the access token fails, the next step is to try to request a
125//! new login authorization with [`OAuth::login()`], using the device ID from
126//! the session.
127//!
128//! If this fails again, the client should assume to be logged out, and all
129//! local data should be erased.
130//!
131//! # Account management.
132//!
133//! The server might advertise a URL that allows the user to manage their
134//! account. It can be used to replace most of the Matrix APIs requiring
135//! User-Interactive Authentication.
136//!
137//! The account management URL is available as `account_management_uri` on
138//! [`AuthorizationServerMetadata`]. To build a full account management URL that
139//! includes the action that the user wants to perform, use
140//! [`AuthorizationServerMetadata::account_management_url_with_action()`].
141//!
142//! # Logout
143//!
144//! To log the [`Client`] out of the session, simply call [`OAuth::logout()`].
145//!
146//! # Examples
147//!
148//! Most methods have examples, there is also an example CLI application that
149//! supports all the actions described here, in [`examples/oauth_cli`].
150//!
151//! [MSC3861]: https://github.com/matrix-org/matrix-spec-proposals/pull/3861
152//! [areweoidcyet.com]: https://areweoidcyet.com/
153//! [MSC2964]: https://github.com/matrix-org/matrix-spec-proposals/pull/2964
154//! [rfc6749-auth-code]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
155//! [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
156//! [RFC 8628]: https://datatracker.ietf.org/doc/html/rfc8628
157//! [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
158//! [`Error`]: ruma::api::client::error::Error
159//! [`ErrorKind::UnknownToken`]: ruma::api::client::error::ErrorKind::UnknownToken
160//! [`examples/oauth_cli`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/examples/oauth_cli
161
162#[cfg(feature = "e2e-encryption")]
163use std::sync::OnceLock;
164#[cfg(feature = "e2e-encryption")]
165use std::time::Duration;
166use std::{borrow::Cow, collections::HashMap, fmt, sync::Arc};
167
168use as_variant::as_variant;
169#[cfg(feature = "e2e-encryption")]
170use error::CrossProcessRefreshLockError;
171use error::{
172    OAuthAuthorizationCodeError, OAuthClientRegistrationError, OAuthDiscoveryError,
173    OAuthTokenRevocationError, RedirectUriQueryParseError,
174};
175#[cfg(feature = "e2e-encryption")]
176use matrix_sdk_base::crypto::types::qr_login::QrCodeData;
177use matrix_sdk_base::{SessionMeta, store::RoomLoadSettings};
178#[cfg(feature = "e2e-encryption")]
179use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
180use oauth2::{
181    AccessToken, PkceCodeVerifier, RedirectUrl, RefreshToken, RevocationUrl, Scope,
182    StandardErrorResponse, StandardRevocableToken, TokenResponse, TokenUrl,
183    basic::BasicClient as OAuthClient,
184};
185pub use oauth2::{ClientId, CsrfToken};
186use ruma::{
187    DeviceId, OwnedDeviceId,
188    api::client::discovery::get_authorization_server_metadata::{
189        self, v1::AuthorizationServerMetadata,
190    },
191    serde::Raw,
192};
193use serde::{Deserialize, Serialize};
194use sha2::Digest as _;
195use tokio::sync::Mutex;
196use tracing::{debug, error, instrument, trace, warn};
197use url::Url;
198
199mod auth_code_builder;
200#[cfg(feature = "e2e-encryption")]
201mod cross_process;
202pub mod error;
203mod http_client;
204#[cfg(feature = "e2e-encryption")]
205pub mod qrcode;
206pub mod registration;
207#[cfg(all(test, not(target_family = "wasm")))]
208mod tests;
209
210#[cfg(feature = "e2e-encryption")]
211use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
212#[cfg(feature = "e2e-encryption")]
213use self::qrcode::{
214    GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode, LoginWithGeneratedQrCode,
215    LoginWithQrCode,
216};
217pub use self::{
218    auth_code_builder::{OAuthAuthCodeUrlBuilder, OAuthAuthorizationData},
219    error::OAuthError,
220};
221use self::{
222    http_client::OAuthHttpClient,
223    registration::{ClientMetadata, ClientRegistrationResponse, register_client},
224};
225use super::{AuthData, SessionTokens};
226use crate::{Client, HttpError, RefreshTokenError, Result, client::SessionChange, executor::spawn};
227
228pub(crate) struct OAuthCtx {
229    /// Lock and state when multiple processes may refresh an OAuth 2.0 session.
230    #[cfg(feature = "e2e-encryption")]
231    cross_process_token_refresh_manager: OnceLock<CrossProcessRefreshManager>,
232
233    /// Deferred cross-process lock initializer.
234    ///
235    /// Note: only required because we're using the crypto store that might not
236    /// be present before reloading a session.
237    #[cfg(feature = "e2e-encryption")]
238    deferred_cross_process_lock_init: Mutex<Option<String>>,
239
240    /// Whether to allow HTTP issuer URLs.
241    insecure_discover: bool,
242}
243
244impl OAuthCtx {
245    pub(crate) fn new(insecure_discover: bool) -> Self {
246        Self {
247            insecure_discover,
248            #[cfg(feature = "e2e-encryption")]
249            cross_process_token_refresh_manager: Default::default(),
250            #[cfg(feature = "e2e-encryption")]
251            deferred_cross_process_lock_init: Default::default(),
252        }
253    }
254}
255
256pub(crate) struct OAuthAuthData {
257    pub(crate) client_id: ClientId,
258    /// The data necessary to validate authorization responses.
259    authorization_data: Mutex<HashMap<CsrfToken, AuthorizationValidationData>>,
260}
261
262#[cfg(not(tarpaulin_include))]
263impl fmt::Debug for OAuthAuthData {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        f.debug_struct("OAuthAuthData").finish_non_exhaustive()
266    }
267}
268
269/// A high-level authentication API to interact with an OAuth 2.0 authorization
270/// server.
271#[derive(Debug, Clone)]
272pub struct OAuth {
273    /// The underlying Matrix API client.
274    client: Client,
275    /// The HTTP client used for making OAuth 2.0 request.
276    http_client: OAuthHttpClient,
277}
278
279impl OAuth {
280    pub(crate) fn new(client: Client) -> Self {
281        let http_client = OAuthHttpClient {
282            inner: client.inner.http_client.inner.clone(),
283            #[cfg(test)]
284            insecure_rewrite_https_to_http: false,
285        };
286        Self { client, http_client }
287    }
288
289    /// Rewrite HTTPS requests to use HTTP instead.
290    ///
291    /// This is a workaround to bypass some checks that require an HTTPS URL,
292    /// but we can only mock HTTP URLs.
293    #[cfg(test)]
294    pub(crate) fn insecure_rewrite_https_to_http(mut self) -> Self {
295        self.http_client.insecure_rewrite_https_to_http = true;
296        self
297    }
298
299    fn ctx(&self) -> &OAuthCtx {
300        &self.client.auth_ctx().oauth
301    }
302
303    fn http_client(&self) -> &OAuthHttpClient {
304        &self.http_client
305    }
306
307    /// Enable a cross-process store lock on the state store, to coordinate
308    /// refreshes across different processes.
309    #[cfg(feature = "e2e-encryption")]
310    pub async fn enable_cross_process_refresh_lock(
311        &self,
312        lock_value: String,
313    ) -> Result<(), OAuthError> {
314        // FIXME: it must be deferred only because we're using the crypto store and it's
315        // initialized only in `set_or_reload_session`, not if we use a dedicated store.
316        let mut lock = self.ctx().deferred_cross_process_lock_init.lock().await;
317        if lock.is_some() {
318            return Err(CrossProcessRefreshLockError::DuplicatedLock.into());
319        }
320        *lock = Some(lock_value);
321
322        Ok(())
323    }
324
325    /// Performs a deferred cross-process refresh-lock, if needs be, after an
326    /// olm machine has been initialized.
327    ///
328    /// Must be called after [`BaseClient::set_or_reload_session`].
329    #[cfg(feature = "e2e-encryption")]
330    async fn deferred_enable_cross_process_refresh_lock(&self) {
331        let deferred_init_lock = self.ctx().deferred_cross_process_lock_init.lock().await;
332
333        // Don't `take()` the value, so that subsequent calls to
334        // `enable_cross_process_refresh_lock` will keep on failing if we've enabled the
335        // lock at least once.
336        let Some(lock_value) = deferred_init_lock.as_ref() else {
337            return;
338        };
339
340        // FIXME: We shouldn't be using the crypto store for that! see also https://github.com/matrix-org/matrix-rust-sdk/issues/2472
341        let olm_machine_lock = self.client.olm_machine().await;
342        let olm_machine =
343            olm_machine_lock.as_ref().expect("there has to be an olm machine, hopefully?");
344        let store = olm_machine.store();
345        let lock = store.create_store_lock(
346            "oidc_session_refresh_lock".to_owned(),
347            CrossProcessLockConfig::multi_process(lock_value.to_owned()),
348        );
349
350        let manager = CrossProcessRefreshManager::new(store.clone(), lock);
351
352        // This method is guarded with the `deferred_cross_process_lock_init` lock held,
353        // so this `set` can't be an error.
354        let _ = self.ctx().cross_process_token_refresh_manager.set(manager);
355    }
356
357    /// The OAuth 2.0 authentication data.
358    ///
359    /// Returns `None` if the client was not registered or if the registration
360    /// was not restored with [`OAuth::restore_registered_client()`] or
361    /// [`OAuth::restore_session()`].
362    fn data(&self) -> Option<&OAuthAuthData> {
363        let data = self.client.auth_ctx().auth_data.get()?;
364        as_variant!(data, AuthData::OAuth)
365    }
366
367    /// Log in this device using a QR code.
368    ///
369    /// # Arguments
370    ///
371    /// * `registration_data` - The data to restore or register the client with
372    ///   the server. If this is not provided, an error will occur unless
373    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
374    ///   was called previously.
375    #[cfg(feature = "e2e-encryption")]
376    pub fn login_with_qr_code<'a>(
377        &'a self,
378        registration_data: Option<&'a ClientRegistrationData>,
379    ) -> LoginWithQrCodeBuilder<'a> {
380        LoginWithQrCodeBuilder { client: &self.client, registration_data }
381    }
382
383    /// Grant login to a new device using a QR code.
384    #[cfg(feature = "e2e-encryption")]
385    pub fn grant_login_with_qr_code<'a>(&'a self) -> GrantLoginWithQrCodeBuilder<'a> {
386        GrantLoginWithQrCodeBuilder::new(&self.client)
387    }
388
389    /// Restore or register the OAuth 2.0 client for the server with the given
390    /// metadata, with the given optional [`ClientRegistrationData`].
391    ///
392    /// If we already have a client ID, this is a noop.
393    ///
394    /// Returns an error if there was a problem using the registration method.
395    async fn use_registration_data(
396        &self,
397        server_metadata: &AuthorizationServerMetadata,
398        data: Option<&ClientRegistrationData>,
399    ) -> std::result::Result<(), OAuthError> {
400        if self.client_id().is_some() {
401            tracing::info!("OAuth 2.0 is already configured.");
402            return Ok(());
403        }
404
405        let Some(data) = data else {
406            return Err(OAuthError::NotRegistered);
407        };
408
409        if let Some(static_registrations) = &data.static_registrations {
410            let client_id = static_registrations
411                .get(&self.client.homeserver())
412                .or_else(|| static_registrations.get(&server_metadata.issuer));
413
414            if let Some(client_id) = client_id {
415                self.restore_registered_client(client_id.clone());
416                return Ok(());
417            }
418        }
419
420        self.register_client_inner(server_metadata, &data.metadata).await?;
421
422        Ok(())
423    }
424
425    /// Get the cached OAuth 2.0 authorization server metadata of the
426    /// homeserver.
427    ///
428    /// This method will cache the metadata for a while. If the cache is not
429    /// populated it will request the server metadata, like a call to
430    /// [`OAuth::server_metadata()`], and cache the response before returning
431    /// it.
432    ///
433    /// In most cases during the authentication process, it is better to always
434    /// fetch the metadata from the server. This is provided for convenience for
435    /// cases where the client doesn't want to incur the extra time necessary to
436    /// make the request.
437    ///
438    /// Returns an error if a problem occurred when fetching or validating the
439    /// metadata.
440    pub async fn cached_server_metadata(
441        &self,
442    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
443        const CACHE_KEY: &str = "SERVER_METADATA";
444
445        let mut cache = self.client.inner.caches.server_metadata.lock().await;
446
447        let metadata = if let Some(metadata) = cache.get(CACHE_KEY) {
448            metadata
449        } else {
450            let server_metadata = self.server_metadata().await?;
451            cache.insert(CACHE_KEY.to_owned(), server_metadata.clone());
452            server_metadata
453        };
454
455        Ok(metadata)
456    }
457
458    /// Fetch the OAuth 2.0 authorization server metadata of the homeserver.
459    ///
460    /// This will always request the latest server metadata from the homeserver.
461    ///
462    /// To avoid making a request each time, you can use
463    /// [`OAuth::cached_server_metadata()`].
464    ///
465    /// Returns an error if a problem occurred when fetching or validating the
466    /// metadata.
467    pub async fn server_metadata(
468        &self,
469    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
470        let is_endpoint_unsupported = |error: &HttpError| {
471            error
472                .as_client_api_error()
473                .is_some_and(|err| err.status_code == http::StatusCode::NOT_FOUND)
474        };
475
476        let response =
477            self.client.send(get_authorization_server_metadata::v1::Request::new()).await.map_err(
478                |error| {
479                    // If the endpoint returns a 404, i.e. the server doesn't support the endpoint.
480                    if is_endpoint_unsupported(&error) {
481                        OAuthDiscoveryError::NotSupported
482                    } else {
483                        error.into()
484                    }
485                },
486            )?;
487
488        let metadata = response.metadata.deserialize()?;
489
490        if self.ctx().insecure_discover {
491            metadata.insecure_validate_urls()?;
492        } else {
493            metadata.validate_urls()?;
494        }
495
496        Ok(metadata)
497    }
498
499    /// The OAuth 2.0 unique identifier of this client obtained after
500    /// registration.
501    ///
502    /// Returns `None` if the client was not registered or if the registration
503    /// was not restored with [`OAuth::restore_registered_client()`] or
504    /// [`OAuth::restore_session()`].
505    pub fn client_id(&self) -> Option<&ClientId> {
506        self.data().map(|data| &data.client_id)
507    }
508
509    /// The OAuth 2.0 user session of this client.
510    ///
511    /// Returns `None` if the client was not logged in.
512    pub fn user_session(&self) -> Option<UserSession> {
513        let meta = self.client.session_meta()?.to_owned();
514        let tokens = self.client.session_tokens()?;
515        Some(UserSession { meta, tokens })
516    }
517
518    /// The full OAuth 2.0 session of this client.
519    ///
520    /// Returns `None` if the client was not logged in with the OAuth 2.0 API.
521    pub fn full_session(&self) -> Option<OAuthSession> {
522        let user = self.user_session()?;
523        let data = self.data()?;
524        Some(OAuthSession { client_id: data.client_id.clone(), user })
525    }
526
527    /// Register a client with the OAuth 2.0 server.
528    ///
529    /// This should be called before any authorization request with an
530    /// authorization server that supports dynamic client registration. If the
531    /// client registered with the server manually, it should use
532    /// [`OAuth::restore_registered_client()`].
533    ///
534    /// Note that this method only supports public clients, i.e. clients without
535    /// a secret.
536    ///
537    /// # Arguments
538    ///
539    /// * `client_metadata` - The serialized client metadata to register.
540    ///
541    /// # Panic
542    ///
543    /// Panics if the authentication data was already set.
544    ///
545    /// # Example
546    ///
547    /// ```no_run
548    /// use matrix_sdk::{Client, ServerName};
549    /// # use matrix_sdk::authentication::oauth::ClientId;
550    /// # use matrix_sdk::authentication::oauth::registration::ClientMetadata;
551    /// # use ruma::serde::Raw;
552    /// # let client_metadata = unimplemented!();
553    /// # fn persist_client_registration (_: url::Url, _: &ClientId) {}
554    /// # _ = async {
555    /// let server_name = ServerName::parse("myhomeserver.org")?;
556    /// let client = Client::builder().server_name(&server_name).build().await?;
557    /// let oauth = client.oauth();
558    ///
559    /// if let Err(error) = oauth.server_metadata().await {
560    ///     if error.is_not_supported() {
561    ///         println!("OAuth 2.0 is not supported");
562    ///     }
563    ///
564    ///     return Err(error.into());
565    /// }
566    ///
567    /// let response = oauth
568    ///     .register_client(&client_metadata)
569    ///     .await?;
570    ///
571    /// println!(
572    ///     "Registered with client_id: {}",
573    ///     response.client_id.as_str()
574    /// );
575    ///
576    /// // The API only supports clients without secrets.
577    /// let client_id = response.client_id;
578    ///
579    /// persist_client_registration(client.homeserver(), &client_id);
580    /// # anyhow::Ok(()) };
581    /// ```
582    pub async fn register_client(
583        &self,
584        client_metadata: &Raw<ClientMetadata>,
585    ) -> Result<ClientRegistrationResponse, OAuthError> {
586        let server_metadata = self.server_metadata().await?;
587        Ok(self.register_client_inner(&server_metadata, client_metadata).await?)
588    }
589
590    async fn register_client_inner(
591        &self,
592        server_metadata: &AuthorizationServerMetadata,
593        client_metadata: &Raw<ClientMetadata>,
594    ) -> Result<ClientRegistrationResponse, OAuthClientRegistrationError> {
595        let registration_endpoint = server_metadata
596            .registration_endpoint
597            .as_ref()
598            .ok_or(OAuthClientRegistrationError::NotSupported)?;
599
600        let registration_response =
601            register_client(self.http_client(), registration_endpoint, client_metadata).await?;
602
603        // The format of the credentials changes according to the client metadata that
604        // was sent. Public clients only get a client ID.
605        self.restore_registered_client(registration_response.client_id.clone());
606
607        Ok(registration_response)
608    }
609
610    /// Set the data of a client that is registered with an OAuth 2.0
611    /// authorization server.
612    ///
613    /// This should be called when logging in with a server that is already
614    /// known by the client.
615    ///
616    /// Note that this method only supports public clients, i.e. clients with
617    /// no credentials.
618    ///
619    /// # Arguments
620    ///
621    /// * `client_id` - The unique identifier to authenticate the client with
622    ///   the server, obtained after registration.
623    ///
624    /// # Panic
625    ///
626    /// Panics if authentication data was already set.
627    pub fn restore_registered_client(&self, client_id: ClientId) {
628        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
629
630        self.client
631            .auth_ctx()
632            .auth_data
633            .set(AuthData::OAuth(data))
634            .expect("Client authentication data was already set");
635    }
636
637    /// Restore a previously logged in session.
638    ///
639    /// This can be used to restore the client to a logged in state, including
640    /// loading the sync state and the encryption keys from the store, if
641    /// one was set up.
642    ///
643    /// # Arguments
644    ///
645    /// * `session` - The session to restore.
646    /// * `room_load_settings` — Specify how many rooms must be restored; use
647    ///   `::default()` if you don't know which value to pick.
648    ///
649    /// # Panic
650    ///
651    /// Panics if authentication data was already set.
652    pub async fn restore_session(
653        &self,
654        session: OAuthSession,
655        room_load_settings: RoomLoadSettings,
656    ) -> Result<()> {
657        let OAuthSession { client_id, user: UserSession { meta, tokens } } = session;
658
659        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
660
661        self.client.auth_ctx().set_session_tokens(tokens.clone());
662        self.client
663            .base_client()
664            .activate(
665                meta,
666                room_load_settings,
667                #[cfg(feature = "e2e-encryption")]
668                None,
669            )
670            .await?;
671        #[cfg(feature = "e2e-encryption")]
672        self.deferred_enable_cross_process_refresh_lock().await;
673
674        self.client
675            .inner
676            .auth_ctx
677            .auth_data
678            .set(AuthData::OAuth(data))
679            .expect("Client authentication data was already set");
680
681        // Initialize the cross-process locking by saving our tokens' hash into the
682        // database, if we've enabled the cross-process lock.
683
684        #[cfg(feature = "e2e-encryption")]
685        if let Some(cross_process_lock) = self.ctx().cross_process_token_refresh_manager.get() {
686            cross_process_lock.restore_session(&tokens).await;
687
688            let mut guard = cross_process_lock
689                .spin_lock()
690                .await
691                .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
692
693            // After we got the lock, it's possible that our session doesn't match the one
694            // read from the database, because of a race: another process has
695            // refreshed the tokens while we were waiting for the lock.
696            //
697            // In that case, if there's a mismatch, we reload the session and update the
698            // hash. Otherwise, we save our hash into the database.
699
700            if guard.hash_mismatch {
701                Box::pin(self.handle_session_hash_mismatch(&mut guard))
702                    .await
703                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
704            } else {
705                guard
706                    .save_in_memory_and_db(&tokens)
707                    .await
708                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
709                // No need to call the save_session_callback here; it was the
710                // source of the session, so it's already in
711                // sync with what we had.
712            }
713        }
714
715        #[cfg(feature = "e2e-encryption")]
716        self.client.encryption().spawn_initialization_task(None).await;
717
718        Ok(())
719    }
720
721    #[cfg(feature = "e2e-encryption")]
722    async fn handle_session_hash_mismatch(
723        &self,
724        guard: &mut CrossProcessRefreshLockGuard,
725    ) -> Result<(), CrossProcessRefreshLockError> {
726        trace!("Handling hash mismatch.");
727
728        let callback = self
729            .client
730            .auth_ctx()
731            .reload_session_callback
732            .get()
733            .ok_or(CrossProcessRefreshLockError::MissingReloadSession)?;
734
735        match callback(self.client.clone()) {
736            Ok(tokens) => {
737                guard.handle_mismatch(&tokens).await?;
738
739                self.client.auth_ctx().set_session_tokens(tokens.clone());
740                // The app's callback acted as authoritative here, so we're not
741                // saving the data back into the app, as that would have no
742                // effect.
743            }
744            Err(err) => {
745                error!("when reloading OAuth 2.0 session tokens from callback: {err}");
746            }
747        }
748
749        Ok(())
750    }
751
752    /// The scopes to request for logging in and the corresponding device ID.
753    fn login_scopes(
754        device_id: Option<OwnedDeviceId>,
755        additional_scopes: Option<Vec<Scope>>,
756    ) -> (Vec<Scope>, OwnedDeviceId) {
757        /// Scope to grand full access to the client-server API.
758        const SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS: &str =
759            "urn:matrix:org.matrix.msc2967.client:api:*";
760        /// Prefix of the scope to bind a device ID to an access token.
761        const SCOPE_MATRIX_DEVICE_ID_PREFIX: &str = "urn:matrix:org.matrix.msc2967.client:device:";
762
763        // Generate the device ID if it is not provided.
764        let device_id = device_id.unwrap_or_else(DeviceId::new);
765
766        let mut scopes = vec![
767            Scope::new(SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS.to_owned()),
768            Scope::new(format!("{SCOPE_MATRIX_DEVICE_ID_PREFIX}{device_id}")),
769        ];
770
771        if let Some(extra_scopes) = additional_scopes {
772            scopes.extend(extra_scopes);
773        }
774
775        (scopes, device_id)
776    }
777
778    /// Log in via OAuth 2.0 with the Authorization Code flow.
779    ///
780    /// This method requires to open a URL in the end-user's browser where they
781    /// will be able to log into their account in the server's web UI and grant
782    /// access to their Matrix account.
783    ///
784    /// The [`OAuthAuthCodeUrlBuilder`] that is returned allows to customize a
785    /// few settings before calling `.build()` to obtain the URL to open in the
786    /// browser of the end-user.
787    ///
788    /// [`OAuth::finish_login()`] must be called once the user has been
789    /// redirected to the `redirect_uri`. [`OAuth::abort_login()`] should be
790    /// called instead if the authorization should be aborted before completion.
791    ///
792    /// # Arguments
793    ///
794    /// * `redirect_uri` - The URI where the end user will be redirected after
795    ///   authorizing the login. It must be one of the redirect URIs sent in the
796    ///   client metadata during registration.
797    ///
798    /// * `device_id` - The unique ID that will be associated with the session.
799    ///   If not set, a random one will be generated. It can be an existing
800    ///   device ID from a previous login call. Note that this should be done
801    ///   only if the client also holds the corresponding encryption keys.
802    ///
803    /// * `registration_data` - The data to restore or register the client with
804    ///   the server. If this is not provided, an error will occur unless
805    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
806    ///   was called previously.
807    ///
808    /// * `additional_scopes` - Additional scopes to request from the
809    ///   authorization server, e.g. "urn:matrix:client:com.example.msc9999.foo".
810    ///   The scopes for API access and the device ID according to the
811    ///   [specification](https://spec.matrix.org/v1.15/client-server-api/#allocated-scope-tokens)
812    ///   are always requested.
813    ///
814    /// # Example
815    ///
816    /// ```no_run
817    /// use matrix_sdk::{
818    ///     authentication::oauth::registration::ClientMetadata,
819    ///     ruma::serde::Raw,
820    /// };
821    /// use url::Url;
822    /// # use matrix_sdk::Client;
823    /// # let client: Client = unimplemented!();
824    /// # let redirect_uri = unimplemented!();
825    /// # async fn open_uri_and_wait_for_redirect(uri: Url) -> Url { unimplemented!() };
826    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() };
827    /// # _ = async {
828    /// let oauth = client.oauth();
829    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
830    /// let registration_data = client_metadata.into();
831    ///
832    /// let auth_data = oauth.login(redirect_uri, None, Some(registration_data), None)
833    ///                      .build()
834    ///                      .await?;
835    ///
836    /// // Open auth_data.url and wait for response at the redirect URI.
837    /// let redirected_to_uri: Url = open_uri_and_wait_for_redirect(auth_data.url).await;
838    ///
839    /// oauth.finish_login(redirected_to_uri.into()).await?;
840    ///
841    /// // The session tokens can be persisted from the
842    /// // `OAuth::full_session()` method.
843    ///
844    /// // You can now make requests to the Matrix API.
845    /// let _me = client.whoami().await?;
846    /// # anyhow::Ok(()) }
847    /// ```
848    pub fn login(
849        &self,
850        redirect_uri: Url,
851        device_id: Option<OwnedDeviceId>,
852        registration_data: Option<ClientRegistrationData>,
853        additional_scopes: Option<Vec<Scope>>,
854    ) -> OAuthAuthCodeUrlBuilder {
855        let (scopes, device_id) = Self::login_scopes(device_id, additional_scopes);
856
857        OAuthAuthCodeUrlBuilder::new(
858            self.clone(),
859            scopes.to_vec(),
860            device_id,
861            redirect_uri,
862            registration_data,
863        )
864    }
865
866    /// Finish the login process.
867    ///
868    /// This method should be called after the URL returned by
869    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
870    /// been redirected to the redirect URI after completing the authorization.
871    ///
872    /// If the authorization needs to be cancelled before its completion,
873    /// [`OAuth::abort_login()`] should be used instead to clean up the local
874    /// data.
875    ///
876    /// # Arguments
877    ///
878    /// * `url_or_query` - The URI where the user was redirected, or just its
879    ///   query part.
880    ///
881    /// Returns an error if the authorization failed, if a request fails, or if
882    /// the client was already logged in with a different session.
883    pub async fn finish_login(&self, url_or_query: UrlOrQuery) -> Result<()> {
884        let response = AuthorizationResponse::parse_url_or_query(&url_or_query)
885            .map_err(|error| OAuthError::from(OAuthAuthorizationCodeError::from(error)))?;
886
887        let auth_code = match response {
888            AuthorizationResponse::Success(code) => code,
889            AuthorizationResponse::Error(error) => {
890                self.abort_login(&error.state).await;
891                return Err(OAuthError::from(OAuthAuthorizationCodeError::from(error.error)).into());
892            }
893        };
894
895        let device_id = self.finish_authorization(auth_code).await?;
896        self.load_session(device_id).await
897    }
898
899    /// Load the session after login.
900    ///
901    /// Returns an error if the request to get the user ID fails, or if the
902    /// client was already logged in with a different session.
903    pub(crate) async fn load_session(&self, device_id: OwnedDeviceId) -> Result<()> {
904        // Get the user ID.
905        let whoami_res = self.client.whoami().await.map_err(crate::Error::from)?;
906
907        let new_session = SessionMeta { user_id: whoami_res.user_id, device_id };
908
909        if let Some(current_session) = self.client.session_meta() {
910            if new_session != *current_session {
911                return Err(OAuthError::SessionMismatch.into());
912            }
913        } else {
914            self.client
915                .base_client()
916                .activate(
917                    new_session,
918                    RoomLoadSettings::default(),
919                    #[cfg(feature = "e2e-encryption")]
920                    None,
921                )
922                .await?;
923            // At this point the Olm machine has been set up.
924
925            // Enable the cross-process lock for refreshes, if needs be.
926            #[cfg(feature = "e2e-encryption")]
927            self.enable_cross_process_lock().await.map_err(OAuthError::from)?;
928
929            #[cfg(feature = "e2e-encryption")]
930            self.client.encryption().spawn_initialization_task(None).await;
931        }
932
933        Ok(())
934    }
935
936    #[cfg(feature = "e2e-encryption")]
937    pub(crate) async fn enable_cross_process_lock(
938        &self,
939    ) -> Result<(), CrossProcessRefreshLockError> {
940        // Enable the cross-process lock for refreshes, if needs be.
941        self.deferred_enable_cross_process_refresh_lock().await;
942
943        if let Some(cross_process_manager) = self.ctx().cross_process_token_refresh_manager.get()
944            && let Some(tokens) = self.client.session_tokens()
945        {
946            let mut cross_process_guard = cross_process_manager.spin_lock().await?;
947
948            if cross_process_guard.hash_mismatch {
949                // At this point, we're finishing a login while another process had written
950                // something in the database. It's likely the information in the database is
951                // just outdated and wasn't properly updated, but display a warning, just in
952                // case this happens frequently.
953                warn!("unexpected cross-process hash mismatch when finishing login (see comment)");
954            }
955
956            cross_process_guard.save_in_memory_and_db(&tokens).await?;
957        }
958
959        Ok(())
960    }
961
962    /// Finish the authorization process.
963    ///
964    /// This method should be called after the URL returned by
965    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
966    /// been redirected to the redirect URI after a successful authorization.
967    ///
968    /// # Arguments
969    ///
970    /// * `auth_code` - The response received as part of the redirect URI when
971    ///   the authorization was successful.
972    ///
973    /// Returns the device ID used in the authorized scope if it succeeds.
974    /// Returns an error if a request fails.
975    async fn finish_authorization(
976        &self,
977        auth_code: AuthorizationCode,
978    ) -> Result<OwnedDeviceId, OAuthError> {
979        let data = self.data().ok_or(OAuthError::NotAuthenticated)?;
980        let client_id = data.client_id.clone();
981
982        let validation_data = data
983            .authorization_data
984            .lock()
985            .await
986            .remove(&auth_code.state)
987            .ok_or(OAuthAuthorizationCodeError::InvalidState)?;
988
989        let token_uri = TokenUrl::from_url(validation_data.server_metadata.token_endpoint.clone());
990
991        let response = OAuthClient::new(client_id)
992            .set_token_uri(token_uri)
993            .exchange_code(oauth2::AuthorizationCode::new(auth_code.code))
994            .set_pkce_verifier(validation_data.pkce_verifier)
995            .set_redirect_uri(Cow::Owned(validation_data.redirect_uri))
996            .request_async(self.http_client())
997            .await
998            .map_err(OAuthAuthorizationCodeError::RequestToken)?;
999
1000        self.client.auth_ctx().set_session_tokens(SessionTokens {
1001            access_token: response.access_token().secret().clone(),
1002            refresh_token: response.refresh_token().map(RefreshToken::secret).cloned(),
1003        });
1004
1005        Ok(validation_data.device_id)
1006    }
1007
1008    /// Abort the login process.
1009    ///
1010    /// This method should be called if a login should be aborted before it is
1011    /// completed.
1012    ///
1013    /// If the login has been completed, [`OAuth::finish_login()`] should be
1014    /// used instead.
1015    ///
1016    /// # Arguments
1017    ///
1018    /// * `state` - The state provided in [`OAuthAuthorizationData`] after
1019    ///   building the authorization URL.
1020    pub async fn abort_login(&self, state: &CsrfToken) {
1021        if let Some(data) = self.data() {
1022            data.authorization_data.lock().await.remove(state);
1023        }
1024    }
1025
1026    /// Request codes from the authorization server for logging in with another
1027    /// device.
1028    #[cfg(feature = "e2e-encryption")]
1029    async fn request_device_authorization(
1030        &self,
1031        server_metadata: &AuthorizationServerMetadata,
1032        device_id: Option<OwnedDeviceId>,
1033    ) -> Result<oauth2::StandardDeviceAuthorizationResponse, qrcode::DeviceAuthorizationOAuthError>
1034    {
1035        let (scopes, _) = Self::login_scopes(device_id, None);
1036
1037        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1038
1039        let device_authorization_url = server_metadata
1040            .device_authorization_endpoint
1041            .clone()
1042            .map(oauth2::DeviceAuthorizationUrl::from_url)
1043            .ok_or(qrcode::DeviceAuthorizationOAuthError::NoDeviceAuthorizationEndpoint)?;
1044
1045        let response = OAuthClient::new(client_id)
1046            .set_device_authorization_url(device_authorization_url)
1047            .exchange_device_code()
1048            .add_scopes(scopes)
1049            .request_async(self.http_client())
1050            .await?;
1051
1052        Ok(response)
1053    }
1054
1055    /// Exchange the device code against an access token.
1056    #[cfg(feature = "e2e-encryption")]
1057    async fn exchange_device_code(
1058        &self,
1059        server_metadata: &AuthorizationServerMetadata,
1060        device_authorization_response: &oauth2::StandardDeviceAuthorizationResponse,
1061    ) -> Result<(), qrcode::DeviceAuthorizationOAuthError> {
1062        use oauth2::TokenResponse;
1063
1064        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1065
1066        let token_uri = TokenUrl::from_url(server_metadata.token_endpoint.clone());
1067
1068        let response = OAuthClient::new(client_id)
1069            .set_token_uri(token_uri)
1070            .exchange_device_access_token(device_authorization_response)
1071            .request_async(self.http_client(), tokio::time::sleep, None)
1072            .await?;
1073
1074        self.client.auth_ctx().set_session_tokens(SessionTokens {
1075            access_token: response.access_token().secret().to_owned(),
1076            refresh_token: response.refresh_token().map(|t| t.secret().to_owned()),
1077        });
1078
1079        Ok(())
1080    }
1081
1082    async fn refresh_access_token_inner(
1083        self,
1084        refresh_token: String,
1085        token_endpoint: Url,
1086        client_id: ClientId,
1087        #[cfg(feature = "e2e-encryption")] cross_process_lock: Option<CrossProcessRefreshLockGuard>,
1088    ) -> Result<(), OAuthError> {
1089        trace!(
1090            "Token refresh: attempting to refresh with refresh_token {:x}",
1091            hash_str(&refresh_token)
1092        );
1093
1094        let token = RefreshToken::new(refresh_token.clone());
1095        let token_uri = TokenUrl::from_url(token_endpoint);
1096
1097        let response = OAuthClient::new(client_id)
1098            .set_token_uri(token_uri)
1099            .exchange_refresh_token(&token)
1100            .request_async(self.http_client())
1101            .await
1102            .map_err(OAuthError::RefreshToken)?;
1103
1104        let new_access_token = response.access_token().secret().clone();
1105        let new_refresh_token = response.refresh_token().map(RefreshToken::secret).cloned();
1106
1107        trace!(
1108            "Token refresh: new refresh_token: {} / access_token: {:x}",
1109            new_refresh_token
1110                .as_deref()
1111                .map(|token| format!("{:x}", hash_str(token)))
1112                .unwrap_or_else(|| "<none>".to_owned()),
1113            hash_str(&new_access_token)
1114        );
1115
1116        let tokens = SessionTokens {
1117            access_token: new_access_token,
1118            refresh_token: new_refresh_token.or(Some(refresh_token)),
1119        };
1120
1121        #[cfg(feature = "e2e-encryption")]
1122        let tokens_clone = tokens.clone();
1123
1124        self.client.auth_ctx().set_session_tokens(tokens);
1125
1126        // Call the save_session_callback if set, while the optional lock is being held.
1127        if let Some(save_session_callback) = self.client.auth_ctx().save_session_callback.get() {
1128            // Satisfies the save_session_callback invariant: set_session_tokens has
1129            // been called just above.
1130            tracing::debug!("call save_session_callback");
1131            if let Err(err) = save_session_callback(self.client.clone()) {
1132                error!("when saving session after refresh: {err}");
1133            }
1134        }
1135
1136        #[cfg(feature = "e2e-encryption")]
1137        if let Some(mut lock) = cross_process_lock {
1138            lock.save_in_memory_and_db(&tokens_clone).await?;
1139        }
1140
1141        tracing::debug!("broadcast session changed");
1142        _ = self.client.auth_ctx().session_change_sender.send(SessionChange::TokensRefreshed);
1143
1144        Ok(())
1145    }
1146
1147    /// Refresh the access token.
1148    ///
1149    /// This should be called when the access token has expired. It should not
1150    /// be needed to call this manually if the [`Client`] was constructed with
1151    /// [`ClientBuilder::handle_refresh_tokens()`].
1152    ///
1153    /// This method is protected behind a lock, so calling this method several
1154    /// times at once will only call the endpoint once and all subsequent calls
1155    /// will wait for the result of the first call.
1156    ///
1157    /// [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
1158    #[instrument(skip_all)]
1159    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1160        macro_rules! fail {
1161            ($lock:expr, $err:expr) => {
1162                let error = $err;
1163                *$lock = Err(error.clone());
1164                return Err(error);
1165            };
1166        }
1167
1168        let client = &self.client;
1169
1170        let refresh_status_lock = client.auth_ctx().refresh_token_lock.clone().try_lock_owned();
1171
1172        let Ok(mut refresh_status_guard) = refresh_status_lock else {
1173            debug!("another refresh is happening, waiting for result.");
1174            // There's already a request to refresh happening in the same process. Wait for
1175            // it to finish.
1176            let res = client.auth_ctx().refresh_token_lock.lock().await.clone();
1177            debug!("other refresh is a {}", if res.is_ok() { "success" } else { "failure " });
1178            return res;
1179        };
1180
1181        debug!("no other refresh happening in background, starting.");
1182
1183        #[cfg(feature = "e2e-encryption")]
1184        let cross_process_guard =
1185            if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1186                let mut cross_process_guard = match manager
1187                    .spin_lock()
1188                    .await
1189                    .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))
1190                {
1191                    Ok(guard) => guard,
1192                    Err(err) => {
1193                        warn!("couldn't acquire cross-process lock (timeout)");
1194                        fail!(refresh_status_guard, err);
1195                    }
1196                };
1197
1198                if cross_process_guard.hash_mismatch {
1199                    Box::pin(self.handle_session_hash_mismatch(&mut cross_process_guard))
1200                        .await
1201                        .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))?;
1202                    // Optimistic exit: assume that the underlying process did update fast enough.
1203                    // In the worst case, we'll do another refresh Soon™.
1204                    tracing::info!("other process handled refresh for us, assuming success");
1205                    *refresh_status_guard = Ok(());
1206                    return Ok(());
1207                }
1208
1209                Some(cross_process_guard)
1210            } else {
1211                None
1212            };
1213
1214        let Some(session_tokens) = self.client.session_tokens() else {
1215            warn!("invalid state: missing session tokens");
1216            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1217        };
1218
1219        let Some(refresh_token) = session_tokens.refresh_token else {
1220            warn!("invalid state: missing session tokens");
1221            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1222        };
1223
1224        let server_metadata = match self.server_metadata().await {
1225            Ok(metadata) => metadata,
1226            Err(err) => {
1227                warn!("couldn't get authorization server metadata: {err:?}");
1228                fail!(refresh_status_guard, RefreshTokenError::OAuth(Arc::new(err.into())));
1229            }
1230        };
1231
1232        let Some(client_id) = self.client_id().cloned() else {
1233            warn!("invalid state: missing client ID");
1234            fail!(
1235                refresh_status_guard,
1236                RefreshTokenError::OAuth(Arc::new(OAuthError::NotAuthenticated))
1237            );
1238        };
1239
1240        // Do not interrupt refresh access token requests and processing, by detaching
1241        // the request sending and response processing.
1242        // Make sure to keep the `refresh_status_guard` during the entire processing.
1243
1244        let this = self.clone();
1245
1246        spawn(async move {
1247            match this
1248                .refresh_access_token_inner(
1249                    refresh_token,
1250                    server_metadata.token_endpoint,
1251                    client_id,
1252                    #[cfg(feature = "e2e-encryption")]
1253                    cross_process_guard,
1254                )
1255                .await
1256            {
1257                Ok(()) => {
1258                    debug!("success refreshing a token");
1259                    *refresh_status_guard = Ok(());
1260                    Ok(())
1261                }
1262
1263                Err(err) => {
1264                    let err = RefreshTokenError::OAuth(Arc::new(err));
1265                    warn!("error refreshing an OAuth 2.0 token: {err}");
1266                    fail!(refresh_status_guard, err);
1267                }
1268            }
1269        })
1270        .await
1271        .expect("joining")
1272    }
1273
1274    /// Log out from the currently authenticated session.
1275    pub async fn logout(&self) -> Result<(), OAuthError> {
1276        let client_id = self.client_id().ok_or(OAuthError::NotAuthenticated)?.clone();
1277
1278        let server_metadata = self.server_metadata().await?;
1279        let revocation_url = RevocationUrl::from_url(server_metadata.revocation_endpoint);
1280
1281        let tokens = self.client.session_tokens().ok_or(OAuthError::NotAuthenticated)?;
1282
1283        // Revoke the access token, it should revoke both tokens.
1284        OAuthClient::new(client_id)
1285            .set_revocation_url(revocation_url)
1286            .revoke_token(StandardRevocableToken::AccessToken(AccessToken::new(
1287                tokens.access_token,
1288            )))
1289            .map_err(OAuthTokenRevocationError::Url)?
1290            .request_async(self.http_client())
1291            .await
1292            .map_err(OAuthTokenRevocationError::Revoke)?;
1293
1294        #[cfg(feature = "e2e-encryption")]
1295        if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1296            manager.on_logout().await?;
1297        }
1298
1299        Ok(())
1300    }
1301}
1302
1303/// Builder for QR login futures.
1304#[cfg(feature = "e2e-encryption")]
1305#[derive(Debug)]
1306pub struct LoginWithQrCodeBuilder<'a> {
1307    /// The underlying Matrix API client.
1308    client: &'a Client,
1309
1310    /// The data to restore or register the client with the server.
1311    registration_data: Option<&'a ClientRegistrationData>,
1312}
1313
1314#[cfg(feature = "e2e-encryption")]
1315impl<'a> LoginWithQrCodeBuilder<'a> {
1316    /// This method allows you to log in with a scanned QR code.
1317    ///
1318    /// The existing device needs to display the QR code which this device can
1319    /// scan and call this method to log in.
1320    ///
1321    /// A successful login using this method will automatically mark the device
1322    /// as verified and transfer all end-to-end encryption related secrets, like
1323    /// the private cross-signing keys and the backup key from the existing
1324    /// device to the new device.
1325    ///
1326    /// For the reverse flow where this device generates the QR code for the
1327    /// existing device to scan, use [`LoginWithQrCodeBuilder::generate`].
1328    ///
1329    /// # Arguments
1330    ///
1331    /// * `data` - The data scanned from a QR code.
1332    ///
1333    /// # Example
1334    ///
1335    /// ```no_run
1336    /// use anyhow::bail;
1337    /// use futures_util::StreamExt;
1338    /// use matrix_sdk::{
1339    ///     authentication::oauth::{
1340    ///         registration::ClientMetadata,
1341    ///         qrcode::{LoginProgress, Msc4108IntentData, QrCodeData, QrCodeIntentData, QrProgress},
1342    ///     },
1343    ///     ruma::serde::Raw,
1344    ///     Client,
1345    /// };
1346    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
1347    /// # _ = async {
1348    /// # let bytes = unimplemented!();
1349    /// // You'll need to use a different library to scan and extract the raw bytes from the QR
1350    /// // code.
1351    /// let qr_code_data = QrCodeData::from_bytes(bytes)?;
1352    ///
1353    /// // Fetch the homeserver out of the parsed QR code data.
1354    /// let QrCodeIntentData::Msc4108 { data: Msc4108IntentData::Reciprocate { server_name }, ..} = qr_code_data.intent_data() else {
1355    ///     bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
1356    /// };
1357    ///
1358    /// // Build the client as usual.
1359    /// let client = Client::builder()
1360    ///     .server_name_or_homeserver_url(server_name)
1361    ///     .handle_refresh_tokens()
1362    ///     .build()
1363    ///     .await?;
1364    ///
1365    /// let oauth = client.oauth();
1366    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
1367    /// let registration_data = client_metadata.into();
1368    ///
1369    /// // Subscribing to the progress is necessary since we need to input the check
1370    /// // code on the existing device.
1371    /// let login = oauth.login_with_qr_code(Some(&registration_data)).scan(&qr_code_data);
1372    /// let mut progress = login.subscribe_to_progress();
1373    ///
1374    /// // Create a task which will show us the progress and tell us the check
1375    /// // code to input in the existing device.
1376    /// let task = tokio::spawn(async move {
1377    ///     while let Some(state) = progress.next().await {
1378    ///         match state {
1379    ///             LoginProgress::Starting | LoginProgress::SyncingSecrets => (),
1380    ///             LoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1381    ///                 let code = check_code.to_digit();
1382    ///                 println!("Please enter the following code into the other device {code:02}");
1383    ///             },
1384    ///             LoginProgress::WaitingForToken { user_code } => {
1385    ///                 println!("Please use your other device to confirm the log in {user_code}")
1386    ///             },
1387    ///             LoginProgress::Done => break,
1388    ///         }
1389    ///     }
1390    /// });
1391    ///
1392    /// // Now run the future to complete the login.
1393    /// login.await?;
1394    /// task.abort();
1395    ///
1396    /// println!("Successfully logged in: {:?} {:?}", client.user_id(), client.device_id());
1397    /// # anyhow::Ok(()) };
1398    /// ```
1399    pub fn scan(self, data: &'a QrCodeData) -> LoginWithQrCode<'a> {
1400        LoginWithQrCode::new(self.client, data, self.registration_data)
1401    }
1402
1403    /// This method allows you to log in by generating a QR code.
1404    ///
1405    /// This device needs to call this method to generate and display the
1406    /// QR code which the existing device can scan and grant the log in.
1407    ///
1408    /// A successful login using this method will automatically mark the device
1409    /// as verified and transfer all end-to-end encryption related secrets, like
1410    /// the private cross-signing keys and the backup key from the existing
1411    /// device to the new device.
1412    ///
1413    /// For the reverse flow where the existing device generates the QR code
1414    /// for this device to scan, use [`LoginWithQrCodeBuilder::scan`].
1415    ///
1416    /// # Example
1417    ///
1418    /// ```no_run
1419    /// use anyhow::bail;
1420    /// use futures_util::StreamExt;
1421    /// use matrix_sdk::{
1422    ///     authentication::oauth::{
1423    ///         registration::ClientMetadata,
1424    ///         qrcode::{GeneratedQrProgress, LoginProgress, QrCodeData},
1425    ///     },
1426    ///     ruma::serde::Raw,
1427    ///     Client,
1428    /// };
1429    /// use std::{error::Error, io::stdin};
1430    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
1431    /// # _ = async {
1432    /// // Build the client as usual.
1433    /// let client = Client::builder()
1434    ///     .server_name_or_homeserver_url("matrix.org")
1435    ///     .handle_refresh_tokens()
1436    ///     .build()
1437    ///     .await?;
1438    ///
1439    /// let oauth = client.oauth();
1440    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
1441    /// let registration_data = client_metadata.into();
1442    ///
1443    /// // Subscribing to the progress is necessary since we need to display the
1444    /// // QR code and prompt for the check code.
1445    /// let login = oauth.login_with_qr_code(Some(&registration_data)).generate();
1446    /// let mut progress = login.subscribe_to_progress();
1447    ///
1448    /// // Create a task which will show us the progress and allows us to display
1449    /// // the QR code and prompt for the check code.
1450    /// let task = tokio::spawn(async move {
1451    ///     while let Some(state) = progress.next().await {
1452    ///         match state {
1453    ///             LoginProgress::Starting | LoginProgress::SyncingSecrets => (),
1454    ///             LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(qr)) => {
1455    ///                 println!("Please use your other device to scan the QR code {:?}", qr)
1456    ///             }
1457    ///             LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(cctx)) => {
1458    ///                 println!("Please enter the code displayed on your other device");
1459    ///                 let mut s = String::new();
1460    ///                 stdin().read_line(&mut s)?;
1461    ///                 let check_code = s.trim().parse::<u8>()?;
1462    ///                 cctx.send(check_code).await?
1463    ///             }
1464    ///             LoginProgress::WaitingForToken { user_code } => {
1465    ///                 println!("Please use your other device to confirm the log in {user_code}")
1466    ///             },
1467    ///             LoginProgress::Done => break,
1468    ///         }
1469    ///     }
1470    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1471    /// });
1472    ///
1473    /// // Now run the future to complete the login.
1474    /// login.await?;
1475    /// task.abort();
1476    ///
1477    /// println!("Successfully logged in: {:?} {:?}", client.user_id(), client.device_id());
1478    /// # anyhow::Ok(()) };
1479    /// ```
1480    pub fn generate(self) -> LoginWithGeneratedQrCode<'a> {
1481        LoginWithGeneratedQrCode::new(self.client, self.registration_data)
1482    }
1483}
1484
1485/// Builder for QR login grant handlers.
1486#[cfg(feature = "e2e-encryption")]
1487#[derive(Debug)]
1488pub struct GrantLoginWithQrCodeBuilder<'a> {
1489    /// The underlying Matrix API client.
1490    client: &'a Client,
1491    /// The duration to wait for the homeserver to create the new device after
1492    /// consenting the login before giving up.
1493    device_creation_timeout: Duration,
1494}
1495
1496#[cfg(feature = "e2e-encryption")]
1497impl<'a> GrantLoginWithQrCodeBuilder<'a> {
1498    /// Create a new builder with the default device creation timeout.
1499    fn new(client: &'a Client) -> Self {
1500        Self { client, device_creation_timeout: Duration::from_secs(10) }
1501    }
1502
1503    /// Set the device creation timeout.
1504    ///
1505    /// # Arguments
1506    ///
1507    /// * `device_creation_timeout` - The duration to wait for the homeserver to
1508    ///   create the new device after consenting the login before giving up.
1509    pub fn device_creation_timeout(mut self, device_creation_timeout: Duration) -> Self {
1510        self.device_creation_timeout = device_creation_timeout;
1511        self
1512    }
1513
1514    /// This method allows you to grant login to a new device by scanning a
1515    /// QR code generated by the new device.
1516    ///
1517    /// The new device needs to display the QR code which this device can
1518    /// scan and call this method to grant the login.
1519    ///
1520    /// A successful login grant using this method will automatically mark the
1521    /// new device as verified and transfer all end-to-end encryption
1522    /// related secrets, like the private cross-signing keys and the backup
1523    /// key from this device device to the new device.
1524    ///
1525    /// For the reverse flow where this device generates the QR code
1526    /// for the new device to scan, use
1527    /// [`GrantLoginWithQrCodeBuilder::generate`].
1528    ///
1529    /// # Arguments
1530    ///
1531    /// * `data` - The data scanned from a QR code.
1532    ///
1533    /// # Example
1534    ///
1535    /// ```no_run
1536    /// use anyhow::bail;
1537    /// use futures_util::StreamExt;
1538    /// use matrix_sdk::{
1539    ///     Client, authentication::oauth::{
1540    ///         qrcode::{GrantLoginProgress, QrCodeData, QrProgress},
1541    ///     }
1542    /// };
1543    /// use std::{error::Error, io::stdin};
1544    /// # _ = async {
1545    /// # let bytes = unimplemented!();
1546    /// // You'll need to use a different library to scan and extract the raw bytes from the QR
1547    /// // code.
1548    /// let qr_code_data = QrCodeData::from_bytes(bytes)?;
1549    ///
1550    /// // Build the client as usual.
1551    /// let client = Client::builder()
1552    ///     .server_name_or_homeserver_url("matrix.org")
1553    ///     .handle_refresh_tokens()
1554    ///     .build()
1555    ///     .await?;
1556    ///
1557    /// let oauth = client.oauth();
1558    ///
1559    /// // Subscribing to the progress is necessary to capture
1560    /// // the checkcode in order to display it to the other device and to obtain the verification URL to
1561    /// // open it in a browser so the user can consent to the new login.
1562    /// let mut grant = oauth.grant_login_with_qr_code().scan(&qr_code_data);
1563    /// let mut progress = grant.subscribe_to_progress();
1564    ///
1565    /// // Create a task which will show us the progress and allows us to receive
1566    /// // and feed back data.
1567    /// let task = tokio::spawn(async move {
1568    ///     while let Some(state) = progress.next().await {
1569    ///         match state {
1570    ///             GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
1571    ///             GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1572    ///                 println!("Please enter the checkcode on your other device: {:?}", check_code);
1573    ///             }
1574    ///             GrantLoginProgress::WaitingForAuth { verification_uri } => {
1575    ///                 println!("Please open {verification_uri} to confirm the new login")
1576    ///             },
1577    ///             GrantLoginProgress::Done => break,
1578    ///         }
1579    ///     }
1580    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1581    /// });
1582    ///
1583    /// // Now run the future to grant the login.
1584    /// grant.await?;
1585    /// task.abort();
1586    ///
1587    /// println!("Successfully granted login");
1588    /// # anyhow::Ok(()) };
1589    /// ```
1590    pub fn scan(self, data: &'a QrCodeData) -> GrantLoginWithScannedQrCode<'a> {
1591        GrantLoginWithScannedQrCode::new(self.client, data, self.device_creation_timeout)
1592    }
1593
1594    /// This method allows you to grant login to a new device by generating a QR
1595    /// code on this device to be scanned by the new device.
1596    ///
1597    /// This device needs to call this method to generate and display the
1598    /// QR code which the new device can scan to initiate the grant process.
1599    ///
1600    /// A successful login grant using this method will automatically mark the
1601    /// new device as verified and transfer all end-to-end encryption
1602    /// related secrets, like the private cross-signing keys and the backup
1603    /// key from this device device to the new device.
1604    ///
1605    /// For the reverse flow where the new device generates the QR code
1606    /// for this device to scan, use [`GrantLoginWithQrCodeBuilder::scan`].
1607    ///
1608    /// # Example
1609    ///
1610    /// ```no_run
1611    /// use anyhow::bail;
1612    /// use futures_util::StreamExt;
1613    /// use matrix_sdk::{
1614    ///     Client, authentication::oauth::{
1615    ///         qrcode::{GeneratedQrProgress, GrantLoginProgress}
1616    ///     }
1617    /// };
1618    /// use std::{error::Error, io::stdin};
1619    /// # _ = async {
1620    /// // Build the client as usual.
1621    /// let client = Client::builder()
1622    ///     .server_name_or_homeserver_url("matrix.org")
1623    ///     .handle_refresh_tokens()
1624    ///     .build()
1625    ///     .await?;
1626    ///
1627    /// let oauth = client.oauth();
1628    ///
1629    /// // Subscribing to the progress is necessary since we need to capture the
1630    /// // QR code, feed the checkcode back in and obtain the verification URL to
1631    /// // open it in a browser so the user can consent to the new login.
1632    /// let mut grant = oauth.grant_login_with_qr_code().generate();
1633    /// let mut progress = grant.subscribe_to_progress();
1634    ///
1635    /// // Create a task which will show us the progress and allows us to receive
1636    /// // and feed back data.
1637    /// let task = tokio::spawn(async move {
1638    ///     while let Some(state) = progress.next().await {
1639    ///         match state {
1640    ///             GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
1641    ///             GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(qr_code_data)) => {
1642    ///                 println!("Please scan the QR code on your other device: {:?}", qr_code_data);
1643    ///             }
1644    ///             GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(checkcode_sender)) => {
1645    ///                 println!("Please enter the code displayed on your other device");
1646    ///                 let mut s = String::new();
1647    ///                 stdin().read_line(&mut s)?;
1648    ///                 let check_code = s.trim().parse::<u8>()?;
1649    ///                 checkcode_sender.send(check_code).await?;
1650    ///             }
1651    ///             GrantLoginProgress::WaitingForAuth { verification_uri } => {
1652    ///                 println!("Please open {verification_uri} to confirm the new login")
1653    ///             },
1654    ///             GrantLoginProgress::Done => break,
1655    ///         }
1656    ///     }
1657    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1658    /// });
1659    ///
1660    /// // Now run the future to grant the login.
1661    /// grant.await?;
1662    /// task.abort();
1663    ///
1664    /// println!("Successfully granted login");
1665    /// # anyhow::Ok(()) };
1666    /// ```
1667    pub fn generate(self) -> GrantLoginWithGeneratedQrCode<'a> {
1668        GrantLoginWithGeneratedQrCode::new(self.client, self.device_creation_timeout)
1669    }
1670}
1671/// A full session for the OAuth 2.0 API.
1672#[derive(Debug, Clone)]
1673pub struct OAuthSession {
1674    /// The client ID obtained after registration.
1675    pub client_id: ClientId,
1676
1677    /// The user session.
1678    pub user: UserSession,
1679}
1680
1681/// A user session for the OAuth 2.0 API.
1682#[derive(Debug, Clone, Serialize, Deserialize)]
1683pub struct UserSession {
1684    /// The Matrix user session info.
1685    #[serde(flatten)]
1686    pub meta: SessionMeta,
1687
1688    /// The tokens used for authentication.
1689    #[serde(flatten)]
1690    pub tokens: SessionTokens,
1691}
1692
1693/// The data necessary to validate a response from the Token endpoint in the
1694/// Authorization Code flow.
1695#[derive(Debug)]
1696struct AuthorizationValidationData {
1697    /// The metadata of the server,
1698    server_metadata: AuthorizationServerMetadata,
1699
1700    /// The device ID used in the scope.
1701    device_id: OwnedDeviceId,
1702
1703    /// The URI where the end-user will be redirected after authorization.
1704    redirect_uri: RedirectUrl,
1705
1706    /// A string to correlate the authorization request to the token request.
1707    pkce_verifier: PkceCodeVerifier,
1708}
1709
1710/// The data returned by the server in the redirect URI after a successful
1711/// authorization.
1712#[derive(Debug, Clone)]
1713enum AuthorizationResponse {
1714    /// A successful response.
1715    Success(AuthorizationCode),
1716
1717    /// An error response.
1718    Error(AuthorizationError),
1719}
1720
1721impl AuthorizationResponse {
1722    /// Deserialize an `AuthorizationResponse` from a [`UrlOrQuery`].
1723    ///
1724    /// Returns an error if the URL or query doesn't have the expected format.
1725    fn parse_url_or_query(url_or_query: &UrlOrQuery) -> Result<Self, RedirectUriQueryParseError> {
1726        let query = url_or_query.query().ok_or(RedirectUriQueryParseError::MissingQuery)?;
1727        Self::parse_query(query)
1728    }
1729
1730    /// Deserialize an `AuthorizationResponse` from the query part of a URI.
1731    ///
1732    /// Returns an error if the query doesn't have the expected format.
1733    fn parse_query(query: &str) -> Result<Self, RedirectUriQueryParseError> {
1734        // For some reason deserializing the enum with `serde(untagged)` doesn't work,
1735        // so let's try both variants separately.
1736        if let Ok(code) = serde_html_form::from_str(query) {
1737            return Ok(AuthorizationResponse::Success(code));
1738        }
1739        if let Ok(error) = serde_html_form::from_str(query) {
1740            return Ok(AuthorizationResponse::Error(error));
1741        }
1742
1743        Err(RedirectUriQueryParseError::UnknownFormat)
1744    }
1745}
1746
1747/// The data returned by the server in the redirect URI after a successful
1748/// authorization.
1749#[derive(Debug, Clone, Deserialize)]
1750struct AuthorizationCode {
1751    /// The code to use to retrieve the access token.
1752    code: String,
1753    /// The unique identifier for this transaction.
1754    state: CsrfToken,
1755}
1756
1757/// The data returned by the server in the redirect URI after an authorization
1758/// error.
1759#[derive(Debug, Clone, Deserialize)]
1760struct AuthorizationError {
1761    /// The error.
1762    #[serde(flatten)]
1763    error: StandardErrorResponse<error::AuthorizationCodeErrorResponseType>,
1764    /// The unique identifier for this transaction.
1765    state: CsrfToken,
1766}
1767
1768fn hash_str(x: &str) -> impl fmt::LowerHex {
1769    sha2::Sha256::new().chain_update(x).finalize()
1770}
1771
1772/// Data to register or restore a client.
1773#[derive(Debug, Clone)]
1774pub struct ClientRegistrationData {
1775    /// The metadata to use to register the client when using dynamic client
1776    /// registration.
1777    pub metadata: Raw<ClientMetadata>,
1778
1779    /// Static registrations for servers that don't support dynamic registration
1780    /// but provide a client ID out-of-band.
1781    ///
1782    /// The keys of the map should be the URLs of the homeservers, but keys
1783    /// using `issuer` URLs are also supported.
1784    pub static_registrations: Option<HashMap<Url, ClientId>>,
1785}
1786
1787impl ClientRegistrationData {
1788    /// Construct a [`ClientRegistrationData`] with the given metadata and no
1789    /// static registrations.
1790    pub fn new(metadata: Raw<ClientMetadata>) -> Self {
1791        Self { metadata, static_registrations: None }
1792    }
1793}
1794
1795impl From<Raw<ClientMetadata>> for ClientRegistrationData {
1796    fn from(value: Raw<ClientMetadata>) -> Self {
1797        Self::new(value)
1798    }
1799}
1800
1801/// A full URL or just the query part of a URL.
1802#[derive(Debug, Clone, PartialEq, Eq)]
1803pub enum UrlOrQuery {
1804    /// A full URL.
1805    Url(Url),
1806
1807    /// The query part of a URL.
1808    Query(String),
1809}
1810
1811impl UrlOrQuery {
1812    /// Get the query part of this [`UrlOrQuery`].
1813    ///
1814    /// If this is a [`Url`], this extracts the query.
1815    pub fn query(&self) -> Option<&str> {
1816        match self {
1817            Self::Url(url) => url.query(),
1818            Self::Query(query) => Some(query),
1819        }
1820    }
1821}
1822
1823impl From<Url> for UrlOrQuery {
1824    fn from(value: Url) -> Self {
1825        Self::Url(value)
1826    }
1827}