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//! An [`AccountManagementUrlBuilder`] can be obtained with
138//! [`OAuth::account_management_url()`]. Then the action that the user wants to
139//! perform can be customized with [`AccountManagementUrlBuilder::action()`].
140//! Finally you can obtain the final URL to present to the user with
141//! [`AccountManagementUrlBuilder::build()`].
142//!
143//! # Logout
144//!
145//! To log the [`Client`] out of the session, simply call [`OAuth::logout()`].
146//!
147//! # Examples
148//!
149//! Most methods have examples, there is also an example CLI application that
150//! supports all the actions described here, in [`examples/oauth_cli`].
151//!
152//! [MSC3861]: https://github.com/matrix-org/matrix-spec-proposals/pull/3861
153//! [areweoidcyet.com]: https://areweoidcyet.com/
154//! [MSC2964]: https://github.com/matrix-org/matrix-spec-proposals/pull/2964
155//! [rfc6749-auth-code]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
156//! [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
157//! [RFC 8628]: https://datatracker.ietf.org/doc/html/rfc8628
158//! [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
159//! [`Error`]: ruma::api::client::error::Error
160//! [`ErrorKind::UnknownToken`]: ruma::api::client::error::ErrorKind::UnknownToken
161//! [`examples/oauth_cli`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/examples/oauth_cli
162
163use std::{
164    borrow::Cow,
165    collections::{BTreeSet, HashMap},
166    fmt,
167    sync::Arc,
168};
169
170use as_variant::as_variant;
171#[cfg(feature = "e2e-encryption")]
172use error::CrossProcessRefreshLockError;
173use error::{
174    OAuthAuthorizationCodeError, OAuthClientRegistrationError, OAuthDiscoveryError,
175    OAuthTokenRevocationError, RedirectUriQueryParseError,
176};
177#[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
178use matrix_sdk_base::crypto::types::qr_login::QrCodeData;
179#[cfg(feature = "e2e-encryption")]
180use matrix_sdk_base::once_cell::sync::OnceCell;
181use matrix_sdk_base::{store::RoomLoadSettings, SessionMeta};
182use oauth2::{
183    basic::BasicClient as OAuthClient, AccessToken, PkceCodeVerifier, RedirectUrl, RefreshToken,
184    RevocationUrl, Scope, StandardErrorResponse, StandardRevocableToken, TokenResponse, TokenUrl,
185};
186pub use oauth2::{ClientId, CsrfToken};
187use ruma::{
188    api::client::discovery::{
189        get_authentication_issuer,
190        get_authorization_server_metadata::{
191            self,
192            msc2965::{AccountManagementAction, AuthorizationServerMetadata},
193        },
194    },
195    serde::Raw,
196    DeviceId, OwnedDeviceId,
197};
198use serde::{Deserialize, Serialize};
199use sha2::Digest as _;
200use tokio::sync::Mutex;
201use tracing::{debug, error, instrument, trace, warn};
202use url::Url;
203
204mod account_management_url;
205mod auth_code_builder;
206#[cfg(feature = "e2e-encryption")]
207mod cross_process;
208pub mod error;
209mod http_client;
210mod oidc_discovery;
211#[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
212pub mod qrcode;
213pub mod registration;
214#[cfg(all(test, not(target_arch = "wasm32")))]
215mod tests;
216
217#[cfg(feature = "e2e-encryption")]
218use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
219#[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
220use self::qrcode::LoginWithQrCode;
221pub use self::{
222    account_management_url::{AccountManagementActionFull, AccountManagementUrlBuilder},
223    auth_code_builder::{OAuthAuthCodeUrlBuilder, OAuthAuthorizationData},
224    error::OAuthError,
225};
226use self::{
227    http_client::OAuthHttpClient,
228    oidc_discovery::discover,
229    registration::{register_client, ClientMetadata, ClientRegistrationResponse},
230};
231use super::{AuthData, SessionTokens};
232use crate::{client::SessionChange, executor::spawn, Client, HttpError, RefreshTokenError, Result};
233
234pub(crate) struct OAuthCtx {
235    /// Lock and state when multiple processes may refresh an OAuth 2.0 session.
236    #[cfg(feature = "e2e-encryption")]
237    cross_process_token_refresh_manager: OnceCell<CrossProcessRefreshManager>,
238
239    /// Deferred cross-process lock initializer.
240    ///
241    /// Note: only required because we're using the crypto store that might not
242    /// be present before reloading a session.
243    #[cfg(feature = "e2e-encryption")]
244    deferred_cross_process_lock_init: Mutex<Option<String>>,
245
246    /// Whether to allow HTTP issuer URLs.
247    insecure_discover: bool,
248}
249
250impl OAuthCtx {
251    pub(crate) fn new(insecure_discover: bool) -> Self {
252        Self {
253            insecure_discover,
254            #[cfg(feature = "e2e-encryption")]
255            cross_process_token_refresh_manager: Default::default(),
256            #[cfg(feature = "e2e-encryption")]
257            deferred_cross_process_lock_init: Default::default(),
258        }
259    }
260}
261
262pub(crate) struct OAuthAuthData {
263    pub(crate) client_id: ClientId,
264    /// The data necessary to validate authorization responses.
265    authorization_data: Mutex<HashMap<CsrfToken, AuthorizationValidationData>>,
266}
267
268#[cfg(not(tarpaulin_include))]
269impl fmt::Debug for OAuthAuthData {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        f.debug_struct("OAuthAuthData").finish_non_exhaustive()
272    }
273}
274
275/// A high-level authentication API to interact with an OAuth 2.0 authorization
276/// server.
277#[derive(Debug, Clone)]
278pub struct OAuth {
279    /// The underlying Matrix API client.
280    client: Client,
281    /// The HTTP client used for making OAuth 2.0 request.
282    http_client: OAuthHttpClient,
283}
284
285impl OAuth {
286    pub(crate) fn new(client: Client) -> Self {
287        let http_client = OAuthHttpClient {
288            inner: client.inner.http_client.inner.clone(),
289            #[cfg(test)]
290            insecure_rewrite_https_to_http: false,
291        };
292        Self { client, http_client }
293    }
294
295    /// Rewrite HTTPS requests to use HTTP instead.
296    ///
297    /// This is a workaround to bypass some checks that require an HTTPS URL,
298    /// but we can only mock HTTP URLs.
299    #[cfg(test)]
300    pub(crate) fn insecure_rewrite_https_to_http(mut self) -> Self {
301        self.http_client.insecure_rewrite_https_to_http = true;
302        self
303    }
304
305    fn ctx(&self) -> &OAuthCtx {
306        &self.client.auth_ctx().oauth
307    }
308
309    fn http_client(&self) -> &OAuthHttpClient {
310        &self.http_client
311    }
312
313    /// Enable a cross-process store lock on the state store, to coordinate
314    /// refreshes across different processes.
315    #[cfg(feature = "e2e-encryption")]
316    pub async fn enable_cross_process_refresh_lock(
317        &self,
318        lock_value: String,
319    ) -> Result<(), OAuthError> {
320        // FIXME: it must be deferred only because we're using the crypto store and it's
321        // initialized only in `set_or_reload_session`, not if we use a dedicated store.
322        let mut lock = self.ctx().deferred_cross_process_lock_init.lock().await;
323        if lock.is_some() {
324            return Err(CrossProcessRefreshLockError::DuplicatedLock.into());
325        }
326        *lock = Some(lock_value);
327
328        Ok(())
329    }
330
331    /// Performs a deferred cross-process refresh-lock, if needs be, after an
332    /// olm machine has been initialized.
333    ///
334    /// Must be called after [`BaseClient::set_or_reload_session`].
335    #[cfg(feature = "e2e-encryption")]
336    async fn deferred_enable_cross_process_refresh_lock(&self) {
337        let deferred_init_lock = self.ctx().deferred_cross_process_lock_init.lock().await;
338
339        // Don't `take()` the value, so that subsequent calls to
340        // `enable_cross_process_refresh_lock` will keep on failing if we've enabled the
341        // lock at least once.
342        let Some(lock_value) = deferred_init_lock.as_ref() else {
343            return;
344        };
345
346        // FIXME: We shouldn't be using the crypto store for that! see also https://github.com/matrix-org/matrix-rust-sdk/issues/2472
347        let olm_machine_lock = self.client.olm_machine().await;
348        let olm_machine =
349            olm_machine_lock.as_ref().expect("there has to be an olm machine, hopefully?");
350        let store = olm_machine.store();
351        let lock =
352            store.create_store_lock("oidc_session_refresh_lock".to_owned(), lock_value.clone());
353
354        let manager = CrossProcessRefreshManager::new(store.clone(), lock);
355
356        // This method is guarded with the `deferred_cross_process_lock_init` lock held,
357        // so this `set` can't be an error.
358        let _ = self.ctx().cross_process_token_refresh_manager.set(manager);
359    }
360
361    /// The OAuth 2.0 authentication data.
362    ///
363    /// Returns `None` if the client was not registered or if the registration
364    /// was not restored with [`OAuth::restore_registered_client()`] or
365    /// [`OAuth::restore_session()`].
366    fn data(&self) -> Option<&OAuthAuthData> {
367        let data = self.client.auth_ctx().auth_data.get()?;
368        as_variant!(data, AuthData::OAuth)
369    }
370
371    /// Log in using a QR code.
372    ///
373    /// This method allows you to log in with a QR code, the existing device
374    /// needs to display the QR code which this device can scan and call
375    /// this method to log in.
376    ///
377    /// A successful login using this method will automatically mark the device
378    /// as verified and transfer all end-to-end encryption related secrets, like
379    /// the private cross-signing keys and the backup key from the existing
380    /// device to the new device.
381    ///
382    /// # Arguments
383    ///
384    /// * `data` - The data scanned from a QR code.
385    ///
386    /// * `registration_data` - The data to restore or register the client with
387    ///   the server. If this is not provided, an error will occur unless
388    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
389    ///   was called previously.
390    ///
391    /// # Example
392    ///
393    /// ```no_run
394    /// use anyhow::bail;
395    /// use futures_util::StreamExt;
396    /// use matrix_sdk::{
397    ///     authentication::oauth::{
398    ///         registration::ClientMetadata,
399    ///         qrcode::{LoginProgress, QrCodeData, QrCodeModeData},
400    ///     },
401    ///     ruma::serde::Raw,
402    ///     Client,
403    /// };
404    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
405    /// # _ = async {
406    /// # let bytes = unimplemented!();
407    /// // You'll need to use a different library to scan and extract the raw bytes from the QR
408    /// // code.
409    /// let qr_code_data = QrCodeData::from_bytes(bytes)?;
410    ///
411    /// // Fetch the homeserver out of the parsed QR code data.
412    /// let QrCodeModeData::Reciprocate{ server_name } = qr_code_data.mode_data else {
413    ///     bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
414    /// };
415    ///
416    /// // Build the client as usual.
417    /// let client = Client::builder()
418    ///     .server_name_or_homeserver_url(server_name)
419    ///     .handle_refresh_tokens()
420    ///     .build()
421    ///     .await?;
422    ///
423    /// let oauth = client.oauth();
424    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
425    /// let registration_data = client_metadata.into();
426    ///
427    /// // Subscribing to the progress is necessary since we need to input the check
428    /// // code on the existing device.
429    /// let login = oauth.login_with_qr_code(&qr_code_data, Some(&registration_data));
430    /// let mut progress = login.subscribe_to_progress();
431    ///
432    /// // Create a task which will show us the progress and tell us the check
433    /// // code to input in the existing device.
434    /// let task = tokio::spawn(async move {
435    ///     while let Some(state) = progress.next().await {
436    ///         match state {
437    ///             LoginProgress::Starting => (),
438    ///             LoginProgress::EstablishingSecureChannel { check_code } => {
439    ///                 let code = check_code.to_digit();
440    ///                 println!("Please enter the following code into the other device {code:02}");
441    ///             },
442    ///             LoginProgress::WaitingForToken { user_code } => {
443    ///                 println!("Please use your other device to confirm the log in {user_code}")
444    ///             },
445    ///             LoginProgress::Done => break,
446    ///         }
447    ///     }
448    /// });
449    ///
450    /// // Now run the future to complete the login.
451    /// login.await?;
452    /// task.abort();
453    ///
454    /// println!("Successfully logged in: {:?} {:?}", client.user_id(), client.device_id());
455    /// # anyhow::Ok(()) };
456    /// ```
457    #[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
458    pub fn login_with_qr_code<'a>(
459        &'a self,
460        data: &'a QrCodeData,
461        registration_data: Option<&'a ClientRegistrationData>,
462    ) -> LoginWithQrCode<'a> {
463        LoginWithQrCode::new(&self.client, data, registration_data)
464    }
465
466    /// Restore or register the OAuth 2.0 client for the server with the given
467    /// metadata, with the given optional [`ClientRegistrationData`].
468    ///
469    /// If we already have a client ID, this is a noop.
470    ///
471    /// Returns an error if there was a problem using the registration method.
472    async fn use_registration_data(
473        &self,
474        server_metadata: &AuthorizationServerMetadata,
475        data: Option<&ClientRegistrationData>,
476    ) -> std::result::Result<(), OAuthError> {
477        if self.client_id().is_some() {
478            tracing::info!("OAuth 2.0 is already configured.");
479            return Ok(());
480        };
481
482        let Some(data) = data else {
483            return Err(OAuthError::NotRegistered);
484        };
485
486        if let Some(static_registrations) = &data.static_registrations {
487            let client_id = static_registrations
488                .get(&self.client.homeserver())
489                .or_else(|| static_registrations.get(&server_metadata.issuer));
490
491            if let Some(client_id) = client_id {
492                self.restore_registered_client(client_id.clone());
493                return Ok(());
494            }
495        }
496
497        self.register_client_inner(server_metadata, &data.metadata).await?;
498
499        Ok(())
500    }
501
502    /// The account management actions supported by the authorization server's
503    /// account management URL.
504    ///
505    /// Returns an error if the request to get the server metadata fails.
506    pub async fn account_management_actions_supported(
507        &self,
508    ) -> Result<BTreeSet<AccountManagementAction>, OAuthError> {
509        let server_metadata = self.server_metadata().await?;
510
511        Ok(server_metadata.account_management_actions_supported)
512    }
513
514    /// Get the account management URL where the user can manage their
515    /// identity-related settings.
516    ///
517    /// This will always request the latest server metadata to get the account
518    /// management URL.
519    ///
520    /// To avoid making a request each time, you can use
521    /// [`OAuth::account_management_url()`].
522    ///
523    /// Returns an [`AccountManagementUrlBuilder`] if the URL was found. An
524    /// optional action to perform can be added with `.action()`, and the final
525    /// URL is obtained with `.build()`.
526    ///
527    /// Returns `Ok(None)` if the URL was not found.
528    ///
529    /// Returns an error if the request to get the server metadata fails or the
530    /// URL could not be parsed.
531    pub async fn fetch_account_management_url(
532        &self,
533    ) -> Result<Option<AccountManagementUrlBuilder>, OAuthError> {
534        let server_metadata = self.server_metadata().await?;
535        Ok(server_metadata.account_management_uri.map(AccountManagementUrlBuilder::new))
536    }
537
538    /// Get the account management URL where the user can manage their
539    /// identity-related settings.
540    ///
541    /// This method will cache the URL for a while, if the cache is not
542    /// populated it will request the server metadata, like a call to
543    /// [`OAuth::fetch_account_management_url()`], and cache the resulting URL
544    /// before returning it.
545    ///
546    /// Returns an [`AccountManagementUrlBuilder`] if the URL was found. An
547    /// optional action to perform can be added with `.action()`, and the final
548    /// URL is obtained with `.build()`.
549    ///
550    /// Returns `Ok(None)` if the URL was not found.
551    ///
552    /// Returns an error if the request to get the server metadata fails or the
553    /// URL could not be parsed.
554    pub async fn account_management_url(
555        &self,
556    ) -> Result<Option<AccountManagementUrlBuilder>, OAuthError> {
557        const CACHE_KEY: &str = "SERVER_METADATA";
558
559        let mut cache = self.client.inner.caches.server_metadata.lock().await;
560
561        let metadata = if let Some(metadata) = cache.get(CACHE_KEY) {
562            metadata
563        } else {
564            let server_metadata = self.server_metadata().await?;
565            cache.insert(CACHE_KEY.to_owned(), server_metadata.clone());
566            server_metadata
567        };
568
569        Ok(metadata.account_management_uri.map(AccountManagementUrlBuilder::new))
570    }
571
572    /// Discover the authentication issuer and retrieve the
573    /// [`AuthorizationServerMetadata`] using the GET `/auth_issuer` endpoint
574    /// previously defined in [MSC2965].
575    ///
576    /// **Note**: This endpoint is deprecated.
577    ///
578    /// MSC2956: https://github.com/matrix-org/matrix-spec-proposals/pull/2965
579    async fn fallback_discover(
580        &self,
581    ) -> Result<Raw<AuthorizationServerMetadata>, OAuthDiscoveryError> {
582        #[allow(deprecated)]
583        let issuer =
584            match self.client.send(get_authentication_issuer::msc2965::Request::new()).await {
585                Ok(response) => response.issuer,
586                Err(error)
587                    if error
588                        .as_client_api_error()
589                        .is_some_and(|err| err.status_code == http::StatusCode::NOT_FOUND) =>
590                {
591                    return Err(OAuthDiscoveryError::NotSupported);
592                }
593                Err(error) => return Err(error.into()),
594            };
595
596        discover(self.http_client(), &issuer).await
597    }
598
599    /// Fetch the OAuth 2.0 authorization server metadata of the homeserver.
600    ///
601    /// Returns an error if a problem occurred when fetching or validating the
602    /// metadata.
603    pub async fn server_metadata(
604        &self,
605    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
606        let is_endpoint_unsupported = |error: &HttpError| {
607            error
608                .as_client_api_error()
609                .is_some_and(|err| err.status_code == http::StatusCode::NOT_FOUND)
610        };
611
612        let raw_metadata = match self
613            .client
614            .send(get_authorization_server_metadata::msc2965::Request::new())
615            .await
616        {
617            Ok(response) => response.metadata,
618            // If the endpoint returns a 404, i.e. the server doesn't support the endpoint, attempt
619            // to use the equivalent, but deprecated, endpoint.
620            Err(error) if is_endpoint_unsupported(&error) => {
621                // TODO: remove this fallback behavior when the metadata endpoint has wider
622                // support.
623                self.fallback_discover().await?
624            }
625            Err(error) => return Err(error.into()),
626        };
627
628        let metadata = raw_metadata.deserialize()?;
629
630        if self.ctx().insecure_discover {
631            metadata.insecure_validate_urls()?;
632        } else {
633            metadata.validate_urls()?;
634        }
635
636        Ok(metadata)
637    }
638
639    /// The OAuth 2.0 unique identifier of this client obtained after
640    /// registration.
641    ///
642    /// Returns `None` if the client was not registered or if the registration
643    /// was not restored with [`OAuth::restore_registered_client()`] or
644    /// [`OAuth::restore_session()`].
645    pub fn client_id(&self) -> Option<&ClientId> {
646        self.data().map(|data| &data.client_id)
647    }
648
649    /// The OAuth 2.0 user session of this client.
650    ///
651    /// Returns `None` if the client was not logged in.
652    pub fn user_session(&self) -> Option<UserSession> {
653        let meta = self.client.session_meta()?.to_owned();
654        let tokens = self.client.session_tokens()?;
655        Some(UserSession { meta, tokens })
656    }
657
658    /// The full OAuth 2.0 session of this client.
659    ///
660    /// Returns `None` if the client was not logged in with the OAuth 2.0 API.
661    pub fn full_session(&self) -> Option<OAuthSession> {
662        let user = self.user_session()?;
663        let data = self.data()?;
664        Some(OAuthSession { client_id: data.client_id.clone(), user })
665    }
666
667    /// Register a client with the OAuth 2.0 server.
668    ///
669    /// This should be called before any authorization request with an
670    /// authorization server that supports dynamic client registration. If the
671    /// client registered with the server manually, it should use
672    /// [`OAuth::restore_registered_client()`].
673    ///
674    /// Note that this method only supports public clients, i.e. clients without
675    /// a secret.
676    ///
677    /// # Arguments
678    ///
679    /// * `client_metadata` - The serialized client metadata to register.
680    ///
681    /// # Panic
682    ///
683    /// Panics if the authentication data was already set.
684    ///
685    /// # Example
686    ///
687    /// ```no_run
688    /// use matrix_sdk::{Client, ServerName};
689    /// # use matrix_sdk::authentication::oauth::ClientId;
690    /// # use matrix_sdk::authentication::oauth::registration::ClientMetadata;
691    /// # use ruma::serde::Raw;
692    /// # let client_metadata = unimplemented!();
693    /// # fn persist_client_registration (_: url::Url, _: &ClientId) {}
694    /// # _ = async {
695    /// let server_name = ServerName::parse("myhomeserver.org")?;
696    /// let client = Client::builder().server_name(&server_name).build().await?;
697    /// let oauth = client.oauth();
698    ///
699    /// if let Err(error) = oauth.server_metadata().await {
700    ///     if error.is_not_supported() {
701    ///         println!("OAuth 2.0 is not supported");
702    ///     }
703    ///
704    ///     return Err(error.into());
705    /// }
706    ///
707    /// let response = oauth
708    ///     .register_client(&client_metadata)
709    ///     .await?;
710    ///
711    /// println!(
712    ///     "Registered with client_id: {}",
713    ///     response.client_id.as_str()
714    /// );
715    ///
716    /// // The API only supports clients without secrets.
717    /// let client_id = response.client_id;
718    ///
719    /// persist_client_registration(client.homeserver(), &client_id);
720    /// # anyhow::Ok(()) };
721    /// ```
722    pub async fn register_client(
723        &self,
724        client_metadata: &Raw<ClientMetadata>,
725    ) -> Result<ClientRegistrationResponse, OAuthError> {
726        let server_metadata = self.server_metadata().await?;
727        Ok(self.register_client_inner(&server_metadata, client_metadata).await?)
728    }
729
730    async fn register_client_inner(
731        &self,
732        server_metadata: &AuthorizationServerMetadata,
733        client_metadata: &Raw<ClientMetadata>,
734    ) -> Result<ClientRegistrationResponse, OAuthClientRegistrationError> {
735        let registration_endpoint = server_metadata
736            .registration_endpoint
737            .as_ref()
738            .ok_or(OAuthClientRegistrationError::NotSupported)?;
739
740        let registration_response =
741            register_client(self.http_client(), registration_endpoint, client_metadata).await?;
742
743        // The format of the credentials changes according to the client metadata that
744        // was sent. Public clients only get a client ID.
745        self.restore_registered_client(registration_response.client_id.clone());
746
747        Ok(registration_response)
748    }
749
750    /// Set the data of a client that is registered with an OAuth 2.0
751    /// authorization server.
752    ///
753    /// This should be called when logging in with a server that is already
754    /// known by the client.
755    ///
756    /// Note that this method only supports public clients, i.e. clients with
757    /// no credentials.
758    ///
759    /// # Arguments
760    ///
761    /// * `client_id` - The unique identifier to authenticate the client with
762    ///   the server, obtained after registration.
763    ///
764    /// # Panic
765    ///
766    /// Panics if authentication data was already set.
767    pub fn restore_registered_client(&self, client_id: ClientId) {
768        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
769
770        self.client
771            .auth_ctx()
772            .auth_data
773            .set(AuthData::OAuth(data))
774            .expect("Client authentication data was already set");
775    }
776
777    /// Restore a previously logged in session.
778    ///
779    /// This can be used to restore the client to a logged in state, including
780    /// loading the sync state and the encryption keys from the store, if
781    /// one was set up.
782    ///
783    /// # Arguments
784    ///
785    /// * `session` - The session to restore.
786    /// * `room_load_settings` — Specify how many rooms must be restored; use
787    ///   `::default()` if you don't know which value to pick.
788    ///
789    /// # Panic
790    ///
791    /// Panics if authentication data was already set.
792    pub async fn restore_session(
793        &self,
794        session: OAuthSession,
795        room_load_settings: RoomLoadSettings,
796    ) -> Result<()> {
797        let OAuthSession { client_id, user: UserSession { meta, tokens } } = session;
798
799        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
800
801        self.client.auth_ctx().set_session_tokens(tokens.clone());
802        self.client
803            .base_client()
804            .activate(
805                meta,
806                room_load_settings,
807                #[cfg(feature = "e2e-encryption")]
808                None,
809            )
810            .await?;
811        #[cfg(feature = "e2e-encryption")]
812        self.deferred_enable_cross_process_refresh_lock().await;
813
814        self.client
815            .inner
816            .auth_ctx
817            .auth_data
818            .set(AuthData::OAuth(data))
819            .expect("Client authentication data was already set");
820
821        // Initialize the cross-process locking by saving our tokens' hash into the
822        // database, if we've enabled the cross-process lock.
823
824        #[cfg(feature = "e2e-encryption")]
825        if let Some(cross_process_lock) = self.ctx().cross_process_token_refresh_manager.get() {
826            cross_process_lock.restore_session(&tokens).await;
827
828            let mut guard = cross_process_lock
829                .spin_lock()
830                .await
831                .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
832
833            // After we got the lock, it's possible that our session doesn't match the one
834            // read from the database, because of a race: another process has
835            // refreshed the tokens while we were waiting for the lock.
836            //
837            // In that case, if there's a mismatch, we reload the session and update the
838            // hash. Otherwise, we save our hash into the database.
839
840            if guard.hash_mismatch {
841                Box::pin(self.handle_session_hash_mismatch(&mut guard))
842                    .await
843                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
844            } else {
845                guard
846                    .save_in_memory_and_db(&tokens)
847                    .await
848                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
849                // No need to call the save_session_callback here; it was the
850                // source of the session, so it's already in
851                // sync with what we had.
852            }
853        }
854
855        #[cfg(feature = "e2e-encryption")]
856        self.client.encryption().spawn_initialization_task(None);
857
858        Ok(())
859    }
860
861    #[cfg(feature = "e2e-encryption")]
862    async fn handle_session_hash_mismatch(
863        &self,
864        guard: &mut CrossProcessRefreshLockGuard,
865    ) -> Result<(), CrossProcessRefreshLockError> {
866        trace!("Handling hash mismatch.");
867
868        let callback = self
869            .client
870            .auth_ctx()
871            .reload_session_callback
872            .get()
873            .ok_or(CrossProcessRefreshLockError::MissingReloadSession)?;
874
875        match callback(self.client.clone()) {
876            Ok(tokens) => {
877                guard.handle_mismatch(&tokens).await?;
878
879                self.client.auth_ctx().set_session_tokens(tokens.clone());
880                // The app's callback acted as authoritative here, so we're not
881                // saving the data back into the app, as that would have no
882                // effect.
883            }
884            Err(err) => {
885                error!("when reloading OAuth 2.0 session tokens from callback: {err}");
886            }
887        }
888
889        Ok(())
890    }
891
892    /// The scopes to request for logging in and the corresponding device ID.
893    fn login_scopes(device_id: Option<OwnedDeviceId>) -> ([Scope; 2], OwnedDeviceId) {
894        /// Scope to grand full access to the client-server API.
895        const SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS: &str =
896            "urn:matrix:org.matrix.msc2967.client:api:*";
897        /// Prefix of the scope to bind a device ID to an access token.
898        const SCOPE_MATRIX_DEVICE_ID_PREFIX: &str = "urn:matrix:org.matrix.msc2967.client:device:";
899
900        // Generate the device ID if it is not provided.
901        let device_id = device_id.unwrap_or_else(DeviceId::new);
902
903        (
904            [
905                Scope::new(SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS.to_owned()),
906                Scope::new(format!("{SCOPE_MATRIX_DEVICE_ID_PREFIX}{device_id}")),
907            ],
908            device_id,
909        )
910    }
911
912    /// Log in via OAuth 2.0 with the Authorization Code flow.
913    ///
914    /// This method requires to open a URL in the end-user's browser where they
915    /// will be able to log into their account in the server's web UI and grant
916    /// access to their Matrix account.
917    ///
918    /// The [`OAuthAuthCodeUrlBuilder`] that is returned allows to customize a
919    /// few settings before calling `.build()` to obtain the URL to open in the
920    /// browser of the end-user.
921    ///
922    /// [`OAuth::finish_login()`] must be called once the user has been
923    /// redirected to the `redirect_uri`. [`OAuth::abort_login()`] should be
924    /// called instead if the authorization should be aborted before completion.
925    ///
926    /// # Arguments
927    ///
928    /// * `redirect_uri` - The URI where the end user will be redirected after
929    ///   authorizing the login. It must be one of the redirect URIs sent in the
930    ///   client metadata during registration.
931    ///
932    /// * `device_id` - The unique ID that will be associated with the session.
933    ///   If not set, a random one will be generated. It can be an existing
934    ///   device ID from a previous login call. Note that this should be done
935    ///   only if the client also holds the corresponding encryption keys.
936    ///
937    /// * `registration_data` - The data to restore or register the client with
938    ///   the server. If this is not provided, an error will occur unless
939    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
940    ///   was called previously.
941    ///
942    /// # Example
943    ///
944    /// ```no_run
945    /// use matrix_sdk::{
946    ///     authentication::oauth::registration::ClientMetadata,
947    ///     ruma::serde::Raw,
948    /// };
949    /// use url::Url;
950    /// # use matrix_sdk::Client;
951    /// # let client: Client = unimplemented!();
952    /// # let redirect_uri = unimplemented!();
953    /// # async fn open_uri_and_wait_for_redirect(uri: Url) -> Url { unimplemented!() };
954    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() };
955    /// # _ = async {
956    /// let oauth = client.oauth();
957    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
958    /// let registration_data = client_metadata.into();
959    ///
960    /// let auth_data = oauth.login(redirect_uri, None, Some(registration_data))
961    ///                      .build()
962    ///                      .await?;
963    ///
964    /// // Open auth_data.url and wait for response at the redirect URI.
965    /// let redirected_to_uri: Url = open_uri_and_wait_for_redirect(auth_data.url).await;
966    ///
967    /// oauth.finish_login(redirected_to_uri.into()).await?;
968    ///
969    /// // The session tokens can be persisted from the
970    /// // `OAuth::full_session()` method.
971    ///
972    /// // You can now make requests to the Matrix API.
973    /// let _me = client.whoami().await?;
974    /// # anyhow::Ok(()) }
975    /// ```
976    pub fn login(
977        &self,
978        redirect_uri: Url,
979        device_id: Option<OwnedDeviceId>,
980        registration_data: Option<ClientRegistrationData>,
981    ) -> OAuthAuthCodeUrlBuilder {
982        let (scopes, device_id) = Self::login_scopes(device_id);
983
984        OAuthAuthCodeUrlBuilder::new(
985            self.clone(),
986            scopes.to_vec(),
987            device_id,
988            redirect_uri,
989            registration_data,
990        )
991    }
992
993    /// Finish the login process.
994    ///
995    /// This method should be called after the URL returned by
996    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
997    /// been redirected to the redirect URI after completing the authorization.
998    ///
999    /// If the authorization needs to be cancelled before its completion,
1000    /// [`OAuth::abort_login()`] should be used instead to clean up the local
1001    /// data.
1002    ///
1003    /// # Arguments
1004    ///
1005    /// * `url_or_query` - The URI where the user was redirected, or just its
1006    ///   query part.
1007    ///
1008    /// Returns an error if the authorization failed, if a request fails, or if
1009    /// the client was already logged in with a different session.
1010    pub async fn finish_login(&self, url_or_query: UrlOrQuery) -> Result<()> {
1011        let response = AuthorizationResponse::parse_url_or_query(&url_or_query)
1012            .map_err(|error| OAuthError::from(OAuthAuthorizationCodeError::from(error)))?;
1013
1014        let auth_code = match response {
1015            AuthorizationResponse::Success(code) => code,
1016            AuthorizationResponse::Error(error) => {
1017                self.abort_login(&error.state).await;
1018                return Err(OAuthError::from(OAuthAuthorizationCodeError::from(error.error)).into());
1019            }
1020        };
1021
1022        let device_id = self.finish_authorization(auth_code).await?;
1023        self.load_session(device_id).await
1024    }
1025
1026    /// Load the session after login.
1027    ///
1028    /// Returns an error if the request to get the user ID fails, or if the
1029    /// client was already logged in with a different session.
1030    pub(crate) async fn load_session(&self, device_id: OwnedDeviceId) -> Result<()> {
1031        // Get the user ID.
1032        let whoami_res = self.client.whoami().await.map_err(crate::Error::from)?;
1033
1034        let new_session = SessionMeta { user_id: whoami_res.user_id, device_id };
1035
1036        if let Some(current_session) = self.client.session_meta() {
1037            if new_session != *current_session {
1038                return Err(OAuthError::SessionMismatch.into());
1039            }
1040        } else {
1041            self.client
1042                .base_client()
1043                .activate(
1044                    new_session,
1045                    RoomLoadSettings::default(),
1046                    #[cfg(feature = "e2e-encryption")]
1047                    None,
1048                )
1049                .await?;
1050            // At this point the Olm machine has been set up.
1051
1052            // Enable the cross-process lock for refreshes, if needs be.
1053            #[cfg(feature = "e2e-encryption")]
1054            self.enable_cross_process_lock().await.map_err(OAuthError::from)?;
1055
1056            #[cfg(feature = "e2e-encryption")]
1057            self.client.encryption().spawn_initialization_task(None);
1058        }
1059
1060        Ok(())
1061    }
1062
1063    #[cfg(feature = "e2e-encryption")]
1064    pub(crate) async fn enable_cross_process_lock(
1065        &self,
1066    ) -> Result<(), CrossProcessRefreshLockError> {
1067        // Enable the cross-process lock for refreshes, if needs be.
1068        self.deferred_enable_cross_process_refresh_lock().await;
1069
1070        if let Some(cross_process_manager) = self.ctx().cross_process_token_refresh_manager.get() {
1071            if let Some(tokens) = self.client.session_tokens() {
1072                let mut cross_process_guard = cross_process_manager.spin_lock().await?;
1073
1074                if cross_process_guard.hash_mismatch {
1075                    // At this point, we're finishing a login while another process had written
1076                    // something in the database. It's likely the information in the database is
1077                    // just outdated and wasn't properly updated, but display a warning, just in
1078                    // case this happens frequently.
1079                    warn!(
1080                        "unexpected cross-process hash mismatch when finishing login (see comment)"
1081                    );
1082                }
1083
1084                cross_process_guard.save_in_memory_and_db(&tokens).await?;
1085            }
1086        }
1087
1088        Ok(())
1089    }
1090
1091    /// Finish the authorization process.
1092    ///
1093    /// This method should be called after the URL returned by
1094    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
1095    /// been redirected to the redirect URI after a successful authorization.
1096    ///
1097    /// # Arguments
1098    ///
1099    /// * `auth_code` - The response received as part of the redirect URI when
1100    ///   the authorization was successful.
1101    ///
1102    /// Returns the device ID used in the authorized scope if it succeeds.
1103    /// Returns an error if a request fails.
1104    async fn finish_authorization(
1105        &self,
1106        auth_code: AuthorizationCode,
1107    ) -> Result<OwnedDeviceId, OAuthError> {
1108        let data = self.data().ok_or(OAuthError::NotAuthenticated)?;
1109        let client_id = data.client_id.clone();
1110
1111        let validation_data = data
1112            .authorization_data
1113            .lock()
1114            .await
1115            .remove(&auth_code.state)
1116            .ok_or(OAuthAuthorizationCodeError::InvalidState)?;
1117
1118        let token_uri = TokenUrl::from_url(validation_data.server_metadata.token_endpoint.clone());
1119
1120        let response = OAuthClient::new(client_id)
1121            .set_token_uri(token_uri)
1122            .exchange_code(oauth2::AuthorizationCode::new(auth_code.code))
1123            .set_pkce_verifier(validation_data.pkce_verifier)
1124            .set_redirect_uri(Cow::Owned(validation_data.redirect_uri))
1125            .request_async(self.http_client())
1126            .await
1127            .map_err(OAuthAuthorizationCodeError::RequestToken)?;
1128
1129        self.client.auth_ctx().set_session_tokens(SessionTokens {
1130            access_token: response.access_token().secret().clone(),
1131            refresh_token: response.refresh_token().map(RefreshToken::secret).cloned(),
1132        });
1133
1134        Ok(validation_data.device_id)
1135    }
1136
1137    /// Abort the login process.
1138    ///
1139    /// This method should be called if a login should be aborted before it is
1140    /// completed.
1141    ///
1142    /// If the login has been completed, [`OAuth::finish_login()`] should be
1143    /// used instead.
1144    ///
1145    /// # Arguments
1146    ///
1147    /// * `state` - The state provided in [`OAuthAuthorizationData`] after
1148    ///   building the authorization URL.
1149    pub async fn abort_login(&self, state: &CsrfToken) {
1150        if let Some(data) = self.data() {
1151            data.authorization_data.lock().await.remove(state);
1152        }
1153    }
1154
1155    /// Request codes from the authorization server for logging in with another
1156    /// device.
1157    #[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
1158    async fn request_device_authorization(
1159        &self,
1160        server_metadata: &AuthorizationServerMetadata,
1161        device_id: Option<OwnedDeviceId>,
1162    ) -> Result<oauth2::StandardDeviceAuthorizationResponse, qrcode::DeviceAuthorizationOAuthError>
1163    {
1164        let (scopes, _) = Self::login_scopes(device_id);
1165
1166        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1167
1168        let device_authorization_url = server_metadata
1169            .device_authorization_endpoint
1170            .clone()
1171            .map(oauth2::DeviceAuthorizationUrl::from_url)
1172            .ok_or(qrcode::DeviceAuthorizationOAuthError::NoDeviceAuthorizationEndpoint)?;
1173
1174        let response = OAuthClient::new(client_id)
1175            .set_device_authorization_url(device_authorization_url)
1176            .exchange_device_code()
1177            .add_scopes(scopes)
1178            .request_async(self.http_client())
1179            .await?;
1180
1181        Ok(response)
1182    }
1183
1184    /// Exchange the device code against an access token.
1185    #[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
1186    async fn exchange_device_code(
1187        &self,
1188        server_metadata: &AuthorizationServerMetadata,
1189        device_authorization_response: &oauth2::StandardDeviceAuthorizationResponse,
1190    ) -> Result<(), qrcode::DeviceAuthorizationOAuthError> {
1191        use oauth2::TokenResponse;
1192
1193        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1194
1195        let token_uri = TokenUrl::from_url(server_metadata.token_endpoint.clone());
1196
1197        let response = OAuthClient::new(client_id)
1198            .set_token_uri(token_uri)
1199            .exchange_device_access_token(device_authorization_response)
1200            .request_async(self.http_client(), tokio::time::sleep, None)
1201            .await?;
1202
1203        self.client.auth_ctx().set_session_tokens(SessionTokens {
1204            access_token: response.access_token().secret().to_owned(),
1205            refresh_token: response.refresh_token().map(|t| t.secret().to_owned()),
1206        });
1207
1208        Ok(())
1209    }
1210
1211    async fn refresh_access_token_inner(
1212        self,
1213        refresh_token: String,
1214        token_endpoint: Url,
1215        client_id: ClientId,
1216        #[cfg(feature = "e2e-encryption")] cross_process_lock: Option<CrossProcessRefreshLockGuard>,
1217    ) -> Result<(), OAuthError> {
1218        trace!(
1219            "Token refresh: attempting to refresh with refresh_token {:x}",
1220            hash_str(&refresh_token)
1221        );
1222
1223        let token = RefreshToken::new(refresh_token.clone());
1224        let token_uri = TokenUrl::from_url(token_endpoint);
1225
1226        let response = OAuthClient::new(client_id)
1227            .set_token_uri(token_uri)
1228            .exchange_refresh_token(&token)
1229            .request_async(self.http_client())
1230            .await
1231            .map_err(OAuthError::RefreshToken)?;
1232
1233        let new_access_token = response.access_token().secret().clone();
1234        let new_refresh_token = response.refresh_token().map(RefreshToken::secret).cloned();
1235
1236        trace!(
1237            "Token refresh: new refresh_token: {} / access_token: {:x}",
1238            new_refresh_token
1239                .as_deref()
1240                .map(|token| format!("{:x}", hash_str(token)))
1241                .unwrap_or_else(|| "<none>".to_owned()),
1242            hash_str(&new_access_token)
1243        );
1244
1245        let tokens = SessionTokens {
1246            access_token: new_access_token,
1247            refresh_token: new_refresh_token.or(Some(refresh_token)),
1248        };
1249
1250        #[cfg(feature = "e2e-encryption")]
1251        let tokens_clone = tokens.clone();
1252
1253        self.client.auth_ctx().set_session_tokens(tokens);
1254
1255        // Call the save_session_callback if set, while the optional lock is being held.
1256        if let Some(save_session_callback) = self.client.auth_ctx().save_session_callback.get() {
1257            // Satisfies the save_session_callback invariant: set_session_tokens has
1258            // been called just above.
1259            if let Err(err) = save_session_callback(self.client.clone()) {
1260                error!("when saving session after refresh: {err}");
1261            }
1262        }
1263
1264        #[cfg(feature = "e2e-encryption")]
1265        if let Some(mut lock) = cross_process_lock {
1266            lock.save_in_memory_and_db(&tokens_clone).await?;
1267        }
1268
1269        _ = self.client.auth_ctx().session_change_sender.send(SessionChange::TokensRefreshed);
1270
1271        Ok(())
1272    }
1273
1274    /// Refresh the access token.
1275    ///
1276    /// This should be called when the access token has expired. It should not
1277    /// be needed to call this manually if the [`Client`] was constructed with
1278    /// [`ClientBuilder::handle_refresh_tokens()`].
1279    ///
1280    /// This method is protected behind a lock, so calling this method several
1281    /// times at once will only call the endpoint once and all subsequent calls
1282    /// will wait for the result of the first call.
1283    ///
1284    /// [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
1285    #[instrument(skip_all)]
1286    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1287        macro_rules! fail {
1288            ($lock:expr, $err:expr) => {
1289                let error = $err;
1290                *$lock = Err(error.clone());
1291                return Err(error);
1292            };
1293        }
1294
1295        let client = &self.client;
1296
1297        let refresh_status_lock = client.auth_ctx().refresh_token_lock.clone().try_lock_owned();
1298
1299        let Ok(mut refresh_status_guard) = refresh_status_lock else {
1300            debug!("another refresh is happening, waiting for result.");
1301            // There's already a request to refresh happening in the same process. Wait for
1302            // it to finish.
1303            let res = client.auth_ctx().refresh_token_lock.lock().await.clone();
1304            debug!("other refresh is a {}", if res.is_ok() { "success" } else { "failure " });
1305            return res;
1306        };
1307
1308        debug!("no other refresh happening in background, starting.");
1309
1310        #[cfg(feature = "e2e-encryption")]
1311        let cross_process_guard =
1312            if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1313                let mut cross_process_guard = match manager
1314                    .spin_lock()
1315                    .await
1316                    .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))
1317                {
1318                    Ok(guard) => guard,
1319                    Err(err) => {
1320                        warn!("couldn't acquire cross-process lock (timeout)");
1321                        fail!(refresh_status_guard, err);
1322                    }
1323                };
1324
1325                if cross_process_guard.hash_mismatch {
1326                    Box::pin(self.handle_session_hash_mismatch(&mut cross_process_guard))
1327                        .await
1328                        .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))?;
1329                    // Optimistic exit: assume that the underlying process did update fast enough.
1330                    // In the worst case, we'll do another refresh Soon™.
1331                    tracing::info!("other process handled refresh for us, assuming success");
1332                    *refresh_status_guard = Ok(());
1333                    return Ok(());
1334                }
1335
1336                Some(cross_process_guard)
1337            } else {
1338                None
1339            };
1340
1341        let Some(session_tokens) = self.client.session_tokens() else {
1342            warn!("invalid state: missing session tokens");
1343            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1344        };
1345
1346        let Some(refresh_token) = session_tokens.refresh_token else {
1347            warn!("invalid state: missing session tokens");
1348            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1349        };
1350
1351        let server_metadata = match self.server_metadata().await {
1352            Ok(metadata) => metadata,
1353            Err(err) => {
1354                warn!("couldn't get authorization server metadata: {err:?}");
1355                fail!(refresh_status_guard, RefreshTokenError::OAuth(Arc::new(err.into())));
1356            }
1357        };
1358
1359        let Some(client_id) = self.client_id().cloned() else {
1360            warn!("invalid state: missing client ID");
1361            fail!(
1362                refresh_status_guard,
1363                RefreshTokenError::OAuth(Arc::new(OAuthError::NotAuthenticated))
1364            );
1365        };
1366
1367        // Do not interrupt refresh access token requests and processing, by detaching
1368        // the request sending and response processing.
1369        // Make sure to keep the `refresh_status_guard` during the entire processing.
1370
1371        let this = self.clone();
1372
1373        spawn(async move {
1374            match this
1375                .refresh_access_token_inner(
1376                    refresh_token,
1377                    server_metadata.token_endpoint,
1378                    client_id,
1379                    #[cfg(feature = "e2e-encryption")]
1380                    cross_process_guard,
1381                )
1382                .await
1383            {
1384                Ok(()) => {
1385                    debug!("success refreshing a token");
1386                    *refresh_status_guard = Ok(());
1387                    Ok(())
1388                }
1389
1390                Err(err) => {
1391                    let err = RefreshTokenError::OAuth(Arc::new(err));
1392                    warn!("error refreshing an OAuth 2.0 token: {err}");
1393                    fail!(refresh_status_guard, err);
1394                }
1395            }
1396        })
1397        .await
1398        .expect("joining")
1399    }
1400
1401    /// Log out from the currently authenticated session.
1402    pub async fn logout(&self) -> Result<(), OAuthError> {
1403        let client_id = self.client_id().ok_or(OAuthError::NotAuthenticated)?.clone();
1404
1405        let server_metadata = self.server_metadata().await?;
1406        let revocation_url = RevocationUrl::from_url(server_metadata.revocation_endpoint);
1407
1408        let tokens = self.client.session_tokens().ok_or(OAuthError::NotAuthenticated)?;
1409
1410        // Revoke the access token, it should revoke both tokens.
1411        OAuthClient::new(client_id)
1412            .set_revocation_url(revocation_url)
1413            .revoke_token(StandardRevocableToken::AccessToken(AccessToken::new(
1414                tokens.access_token,
1415            )))
1416            .map_err(OAuthTokenRevocationError::Url)?
1417            .request_async(self.http_client())
1418            .await
1419            .map_err(OAuthTokenRevocationError::Revoke)?;
1420
1421        #[cfg(feature = "e2e-encryption")]
1422        if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1423            manager.on_logout().await?;
1424        }
1425
1426        Ok(())
1427    }
1428}
1429
1430/// A full session for the OAuth 2.0 API.
1431#[derive(Debug, Clone)]
1432pub struct OAuthSession {
1433    /// The client ID obtained after registration.
1434    pub client_id: ClientId,
1435
1436    /// The user session.
1437    pub user: UserSession,
1438}
1439
1440/// A user session for the OAuth 2.0 API.
1441#[derive(Debug, Clone, Serialize, Deserialize)]
1442pub struct UserSession {
1443    /// The Matrix user session info.
1444    #[serde(flatten)]
1445    pub meta: SessionMeta,
1446
1447    /// The tokens used for authentication.
1448    #[serde(flatten)]
1449    pub tokens: SessionTokens,
1450}
1451
1452/// The data necessary to validate a response from the Token endpoint in the
1453/// Authorization Code flow.
1454#[derive(Debug)]
1455struct AuthorizationValidationData {
1456    /// The metadata of the server,
1457    server_metadata: AuthorizationServerMetadata,
1458
1459    /// The device ID used in the scope.
1460    device_id: OwnedDeviceId,
1461
1462    /// The URI where the end-user will be redirected after authorization.
1463    redirect_uri: RedirectUrl,
1464
1465    /// A string to correlate the authorization request to the token request.
1466    pkce_verifier: PkceCodeVerifier,
1467}
1468
1469/// The data returned by the server in the redirect URI after a successful
1470/// authorization.
1471#[derive(Debug, Clone)]
1472enum AuthorizationResponse {
1473    /// A successful response.
1474    Success(AuthorizationCode),
1475
1476    /// An error response.
1477    Error(AuthorizationError),
1478}
1479
1480impl AuthorizationResponse {
1481    /// Deserialize an `AuthorizationResponse` from a [`UrlOrQuery`].
1482    ///
1483    /// Returns an error if the URL or query doesn't have the expected format.
1484    fn parse_url_or_query(url_or_query: &UrlOrQuery) -> Result<Self, RedirectUriQueryParseError> {
1485        let query = url_or_query.query().ok_or(RedirectUriQueryParseError::MissingQuery)?;
1486        Self::parse_query(query)
1487    }
1488
1489    /// Deserialize an `AuthorizationResponse` from the query part of a URI.
1490    ///
1491    /// Returns an error if the query doesn't have the expected format.
1492    fn parse_query(query: &str) -> Result<Self, RedirectUriQueryParseError> {
1493        // For some reason deserializing the enum with `serde(untagged)` doesn't work,
1494        // so let's try both variants separately.
1495        if let Ok(code) = serde_html_form::from_str(query) {
1496            return Ok(AuthorizationResponse::Success(code));
1497        }
1498        if let Ok(error) = serde_html_form::from_str(query) {
1499            return Ok(AuthorizationResponse::Error(error));
1500        }
1501
1502        Err(RedirectUriQueryParseError::UnknownFormat)
1503    }
1504}
1505
1506/// The data returned by the server in the redirect URI after a successful
1507/// authorization.
1508#[derive(Debug, Clone, Deserialize)]
1509struct AuthorizationCode {
1510    /// The code to use to retrieve the access token.
1511    code: String,
1512    /// The unique identifier for this transaction.
1513    state: CsrfToken,
1514}
1515
1516/// The data returned by the server in the redirect URI after an authorization
1517/// error.
1518#[derive(Debug, Clone, Deserialize)]
1519struct AuthorizationError {
1520    /// The error.
1521    #[serde(flatten)]
1522    error: StandardErrorResponse<error::AuthorizationCodeErrorResponseType>,
1523    /// The unique identifier for this transaction.
1524    state: CsrfToken,
1525}
1526
1527fn hash_str(x: &str) -> impl fmt::LowerHex {
1528    sha2::Sha256::new().chain_update(x).finalize()
1529}
1530
1531/// Data to register or restore a client.
1532#[derive(Debug, Clone)]
1533pub struct ClientRegistrationData {
1534    /// The metadata to use to register the client when using dynamic client
1535    /// registration.
1536    pub metadata: Raw<ClientMetadata>,
1537
1538    /// Static registrations for servers that don't support dynamic registration
1539    /// but provide a client ID out-of-band.
1540    ///
1541    /// The keys of the map should be the URLs of the homeservers, but keys
1542    /// using `issuer` URLs are also supported.
1543    pub static_registrations: Option<HashMap<Url, ClientId>>,
1544}
1545
1546impl ClientRegistrationData {
1547    /// Construct a [`ClientRegistrationData`] with the given metadata and no
1548    /// static registrations.
1549    pub fn new(metadata: Raw<ClientMetadata>) -> Self {
1550        Self { metadata, static_registrations: None }
1551    }
1552}
1553
1554impl From<Raw<ClientMetadata>> for ClientRegistrationData {
1555    fn from(value: Raw<ClientMetadata>) -> Self {
1556        Self::new(value)
1557    }
1558}
1559
1560/// A full URL or just the query part of a URL.
1561#[derive(Debug, Clone, PartialEq, Eq)]
1562pub enum UrlOrQuery {
1563    /// A full URL.
1564    Url(Url),
1565
1566    /// The query part of a URL.
1567    Query(String),
1568}
1569
1570impl UrlOrQuery {
1571    /// Get the query part of this [`UrlOrQuery`].
1572    ///
1573    /// If this is a [`Url`], this extracts the query.
1574    pub fn query(&self) -> Option<&str> {
1575        match self {
1576            Self::Url(url) => url.query(),
1577            Self::Query(query) => Some(query),
1578        }
1579    }
1580}
1581
1582impl From<Url> for UrlOrQuery {
1583    fn from(value: Url) -> Self {
1584        Self::Url(value)
1585    }
1586}