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::error::Error
159//! [`ErrorKind::UnknownToken`]: ruma::api::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, ttl::TtlValue};
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 oauth2_reqwest::ReqwestClient;
187use ruma::{
188    DeviceId, OwnedDeviceId,
189    api::client::discovery::get_authorization_server_metadata::{
190        self, v1::AuthorizationServerMetadata,
191    },
192    serde::Raw,
193};
194use serde::{Deserialize, Serialize};
195use sha2::Digest as _;
196use tokio::sync::Mutex;
197use tracing::{debug, error, instrument, trace, warn};
198use url::Url;
199
200mod auth_code_builder;
201#[cfg(feature = "e2e-encryption")]
202mod cross_process;
203pub mod error;
204mod http_client;
205#[cfg(feature = "e2e-encryption")]
206pub mod qrcode;
207pub mod registration;
208#[cfg(all(test, not(target_family = "wasm")))]
209mod tests;
210
211#[cfg(feature = "e2e-encryption")]
212use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
213#[cfg(feature = "e2e-encryption")]
214use self::qrcode::{
215    GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode, LoginWithGeneratedQrCode,
216    LoginWithQrCode,
217};
218pub use self::{
219    auth_code_builder::{OAuthAuthCodeUrlBuilder, OAuthAuthorizationData},
220    error::OAuthError,
221};
222use self::{
223    http_client::OAuthHttpClient,
224    registration::{ClientMetadata, ClientRegistrationResponse, register_client},
225};
226use super::{AuthData, SessionTokens};
227use crate::{
228    Client, RefreshTokenError, Result,
229    client::{SessionChange, caches::CachedValue},
230    executor::spawn,
231    utils::UrlOrQuery,
232};
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: OnceLock<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: ReqwestClient::from(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
321        // and it's initialized only in `set_or_reload_session`, not if we use a
322        // dedicated store.
323        let mut lock = self.ctx().deferred_cross_process_lock_init.lock().await;
324        if lock.is_some() {
325            return Err(CrossProcessRefreshLockError::DuplicatedLock.into());
326        }
327        *lock = Some(lock_value);
328
329        Ok(())
330    }
331
332    /// Performs a deferred cross-process refresh-lock, if needs be, after an
333    /// olm machine has been initialized.
334    ///
335    /// Must be called after [`BaseClient::set_or_reload_session`].
336    #[cfg(feature = "e2e-encryption")]
337    async fn deferred_enable_cross_process_refresh_lock(&self) {
338        let deferred_init_lock = self.ctx().deferred_cross_process_lock_init.lock().await;
339
340        // Don't `take()` the value, so that subsequent calls to
341        // `enable_cross_process_refresh_lock` will keep on failing if we've
342        // enabled the lock at least once.
343        let Some(lock_value) = deferred_init_lock.as_ref() else {
344            return;
345        };
346
347        // FIXME: We shouldn't be using the crypto store for that! see also https://github.com/matrix-org/matrix-rust-sdk/issues/2472
348        let olm_machine_lock = self.client.olm_machine().await;
349        let olm_machine =
350            olm_machine_lock.as_ref().expect("there has to be an olm machine, hopefully?");
351        let store = olm_machine.store();
352        let lock = store.create_store_lock(
353            "oidc_session_refresh_lock".to_owned(),
354            CrossProcessLockConfig::multi_process(lock_value.to_owned()),
355        );
356
357        let manager = CrossProcessRefreshManager::new(store.clone(), lock);
358
359        // This method is guarded with the `deferred_cross_process_lock_init`
360        // lock held, so this `set` can't be an error.
361        let _ = self.ctx().cross_process_token_refresh_manager.set(manager);
362    }
363
364    /// The OAuth 2.0 authentication data.
365    ///
366    /// Returns `None` if the client was not registered or if the registration
367    /// was not restored with [`OAuth::restore_registered_client()`] or
368    /// [`OAuth::restore_session()`].
369    fn data(&self) -> Option<&OAuthAuthData> {
370        let data = self.client.auth_ctx().auth_data.get()?;
371        as_variant!(data, AuthData::OAuth)
372    }
373
374    /// Check if the homeserver supports the [MSC4388] variant of the rendezvous
375    /// server.
376    ///
377    /// Returns `Ok(true)` if the rendezvous discovery endpoint returns a 200 OK
378    /// HTTP response, `Ok(false)` if the endpoint returns a 404 NOT_FOUND or
379    /// 403 FORBIDDEN HTTP response, otherwise an error is returned.
380    ///
381    /// [MSC4388]: https://github.com/matrix-org/matrix-spec-proposals/pull/4388
382    #[cfg(feature = "e2e-encryption")]
383    pub async fn msc_4388_rendezvous_server_supported(&self) -> Result<bool, crate::HttpError> {
384        use http::StatusCode;
385        use ruma::api::client::rendezvous::discover_rendezvous;
386
387        match self.client.send(discover_rendezvous::unstable::Request::new()).await {
388            Ok(response) => Ok(response.create_available),
389            Err(e) => {
390                if e.as_client_api_error().is_some_and(|err| {
391                    matches!(err.status_code, StatusCode::NOT_FOUND | StatusCode::FORBIDDEN)
392                }) {
393                    Ok(false)
394                } else {
395                    Err(e)
396                }
397            }
398        }
399    }
400
401    /// Log in this device using a QR code.
402    ///
403    /// # Arguments
404    ///
405    /// - `registration_data` - The data to restore or register the client with
406    ///   the server. If this is not provided, an error will occur unless
407    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
408    ///   was called previously.
409    #[cfg(feature = "e2e-encryption")]
410    pub fn login_with_qr_code<'a>(
411        &'a self,
412        registration_data: Option<&'a ClientRegistrationData>,
413    ) -> LoginWithQrCodeBuilder<'a> {
414        LoginWithQrCodeBuilder::new(&self.client, registration_data)
415    }
416
417    /// Grant login to a new device using a QR code.
418    #[cfg(feature = "e2e-encryption")]
419    pub fn grant_login_with_qr_code<'a>(&'a self) -> GrantLoginWithQrCodeBuilder<'a> {
420        GrantLoginWithQrCodeBuilder::new(&self.client)
421    }
422
423    /// Restore or register the OAuth 2.0 client for the server with the given
424    /// metadata, with the given optional [`ClientRegistrationData`].
425    ///
426    /// If we already have a client ID, this is a noop.
427    ///
428    /// Returns an error if there was a problem using the registration method.
429    async fn use_registration_data(
430        &self,
431        server_metadata: &AuthorizationServerMetadata,
432        data: Option<&ClientRegistrationData>,
433    ) -> std::result::Result<(), OAuthError> {
434        if self.client_id().is_some() {
435            tracing::info!("OAuth 2.0 is already configured.");
436            return Ok(());
437        }
438
439        let Some(data) = data else {
440            return Err(OAuthError::NotRegistered);
441        };
442
443        if let Some(static_registrations) = &data.static_registrations {
444            let client_id = static_registrations
445                .get(&self.client.homeserver())
446                .or_else(|| static_registrations.get(&server_metadata.issuer));
447
448            if let Some(client_id) = client_id {
449                self.restore_registered_client(client_id.clone());
450                return Ok(());
451            }
452        }
453
454        self.register_client_inner(server_metadata, &data.metadata).await?;
455
456        Ok(())
457    }
458
459    /// Get the cached OAuth 2.0 authorization server metadata of the
460    /// homeserver.
461    ///
462    /// This method will cache the metadata for a while. If the cache is not
463    /// populated it will request the server metadata, like a call to
464    /// [`OAuth::server_metadata()`], and cache the response before returning
465    /// it.
466    ///
467    /// The cache can be forced to be refreshed by calling
468    /// [`OAuth::server_metadata()`] instead.
469    ///
470    /// In most cases during the authentication process, it is better to always
471    /// fetch the metadata from the server. This is provided for convenience for
472    /// cases where the client doesn't want to incur the extra time necessary to
473    /// make the request.
474    ///
475    /// Returns an error if a problem occurred when fetching or validating the
476    /// metadata.
477    pub async fn cached_server_metadata(
478        &self,
479    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
480        let server_metadata_cache = &self.client.inner.caches.server_metadata;
481
482        if let CachedValue::Cached(metadata) = server_metadata_cache.value() {
483            if metadata.has_expired() {
484                debug!("spawning task to refresh OAuth 2.0 server metadata cache");
485
486                let oauth = self.clone();
487                self.client.task_monitor().spawn_finite_task(
488                    "refresh OAuth 2.0 server metadata cache",
489                    async move {
490                        if let Err(error) = oauth.server_metadata().await {
491                            warn!("failed to refresh OAuth 2.0 server metadata cache: {error}");
492                        }
493                    },
494                );
495            }
496
497            return Ok(metadata.into_data());
498        }
499
500        self.server_metadata().await
501    }
502
503    /// Fetch the OAuth 2.0 authorization server metadata of the homeserver.
504    ///
505    /// This will always request the latest server metadata from the homeserver.
506    ///
507    /// To avoid making a request each time, you can use
508    /// [`OAuth::cached_server_metadata()`].
509    ///
510    /// Returns an error if a problem occurred when fetching or validating the
511    /// metadata.
512    pub async fn server_metadata(
513        &self,
514    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
515        let server_metadata_cache = &self.client.inner.caches.server_metadata;
516
517        let mut server_metadata_guard = match server_metadata_cache.refresh_lock.try_lock() {
518            Ok(guard) => guard,
519            Err(_) => {
520                // There is already a refresh in progress, wait for it to
521                // finish.
522                let guard = server_metadata_cache.refresh_lock.lock().await;
523
524                // Reuse the data if the request was successful.
525                if matches!(*guard, Ok(()))
526                    && let CachedValue::Cached(value) = server_metadata_cache.value()
527                {
528                    return Ok(value.into_data());
529                }
530
531                // The previous request failed, make another request.
532                guard
533            }
534        };
535
536        match self.server_metadata_inner().await {
537            Ok(metadata) => {
538                // Always refresh the cache.
539                self.client.inner.caches.server_metadata.set_value(TtlValue::new(metadata.clone()));
540                *server_metadata_guard = Ok(());
541                Ok(metadata)
542            }
543            Err(error) => {
544                *server_metadata_guard = Err(());
545                Err(error)
546            }
547        }
548    }
549
550    async fn server_metadata_inner(
551        &self,
552    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
553        let response =
554            self.client.send(get_authorization_server_metadata::v1::Request::new()).await.map_err(
555                |error| {
556                    // If the server doesn't support the endpoint.
557                    if error.is_endpoint_not_implemented() {
558                        OAuthDiscoveryError::NotSupported
559                    } else {
560                        error.into()
561                    }
562                },
563            )?;
564
565        let metadata = response.metadata.deserialize()?;
566
567        if self.ctx().insecure_discover {
568            metadata.insecure_validate_urls()?;
569        } else {
570            metadata.validate_urls()?;
571        }
572
573        Ok(metadata)
574    }
575
576    /// The OAuth 2.0 unique identifier of this client obtained after
577    /// registration.
578    ///
579    /// Returns `None` if the client was not registered or if the registration
580    /// was not restored with [`OAuth::restore_registered_client()`] or
581    /// [`OAuth::restore_session()`].
582    pub fn client_id(&self) -> Option<&ClientId> {
583        self.data().map(|data| &data.client_id)
584    }
585
586    /// The OAuth 2.0 user session of this client.
587    ///
588    /// Returns `None` if the client was not logged in.
589    pub fn user_session(&self) -> Option<UserSession> {
590        let meta = self.client.session_meta()?.to_owned();
591        let tokens = self.client.session_tokens()?;
592        Some(UserSession { meta, tokens })
593    }
594
595    /// The full OAuth 2.0 session of this client.
596    ///
597    /// Returns `None` if the client was not logged in with the OAuth 2.0 API.
598    pub fn full_session(&self) -> Option<OAuthSession> {
599        let user = self.user_session()?;
600        let data = self.data()?;
601        Some(OAuthSession { client_id: data.client_id.clone(), user })
602    }
603
604    /// Register a client with the OAuth 2.0 server.
605    ///
606    /// This should be called before any authorization request with an
607    /// authorization server that supports dynamic client registration. If the
608    /// client registered with the server manually, it should use
609    /// [`OAuth::restore_registered_client()`].
610    ///
611    /// Note that this method only supports public clients, i.e. clients without
612    /// a secret.
613    ///
614    /// # Arguments
615    ///
616    /// - `client_metadata` - The serialized client metadata to register.
617    ///
618    /// # Panic
619    ///
620    /// Panics if the authentication data was already set.
621    ///
622    /// # Example
623    ///
624    /// ```no_run
625    /// use matrix_sdk::{Client, ServerName};
626    /// # use matrix_sdk::authentication::oauth::ClientId;
627    /// # use matrix_sdk::authentication::oauth::registration::ClientMetadata;
628    /// # use ruma::serde::Raw;
629    /// # let client_metadata = unimplemented!();
630    /// # fn persist_client_registration (_: url::Url, _: &ClientId) {}
631    /// # _ = async {
632    /// let server_name = ServerName::parse("myhomeserver.org")?;
633    /// let client = Client::builder().server_name(&server_name).build().await?;
634    /// let oauth = client.oauth();
635    ///
636    /// if let Err(error) = oauth.server_metadata().await {
637    ///     if error.is_not_supported() {
638    ///         println!("OAuth 2.0 is not supported");
639    ///     }
640    ///
641    ///     return Err(error.into());
642    /// }
643    ///
644    /// let response = oauth
645    ///     .register_client(&client_metadata)
646    ///     .await?;
647    ///
648    /// println!(
649    ///     "Registered with client_id: {}",
650    ///     response.client_id.as_str()
651    /// );
652    ///
653    /// // The API only supports clients without secrets.
654    /// let client_id = response.client_id;
655    ///
656    /// persist_client_registration(client.homeserver(), &client_id);
657    /// # anyhow::Ok(()) };
658    /// ```
659    pub async fn register_client(
660        &self,
661        client_metadata: &Raw<ClientMetadata>,
662    ) -> Result<ClientRegistrationResponse, OAuthError> {
663        let server_metadata = self.server_metadata().await?;
664        Ok(self.register_client_inner(&server_metadata, client_metadata).await?)
665    }
666
667    async fn register_client_inner(
668        &self,
669        server_metadata: &AuthorizationServerMetadata,
670        client_metadata: &Raw<ClientMetadata>,
671    ) -> Result<ClientRegistrationResponse, OAuthClientRegistrationError> {
672        let registration_endpoint = server_metadata
673            .registration_endpoint
674            .as_ref()
675            .ok_or(OAuthClientRegistrationError::NotSupported)?;
676
677        let registration_response =
678            register_client(self.http_client(), registration_endpoint, client_metadata).await?;
679
680        // The format of the credentials changes according to the client
681        // metadata that was sent. Public clients only get a client ID.
682        self.restore_registered_client(registration_response.client_id.clone());
683
684        Ok(registration_response)
685    }
686
687    /// Set the data of a client that is registered with an OAuth 2.0
688    /// authorization server.
689    ///
690    /// This should be called when logging in with a server that is already
691    /// known by the client.
692    ///
693    /// Note that this method only supports public clients, i.e. clients with no
694    /// credentials.
695    ///
696    /// # Arguments
697    ///
698    /// - `client_id` - The unique identifier to authenticate the client with
699    ///   the server, obtained after registration.
700    ///
701    /// # Panic
702    ///
703    /// Panics if authentication data was already set.
704    pub fn restore_registered_client(&self, client_id: ClientId) {
705        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
706
707        self.client
708            .auth_ctx()
709            .auth_data
710            .set(AuthData::OAuth(data))
711            .expect("Client authentication data was already set");
712    }
713
714    /// Restore a previously logged in session.
715    ///
716    /// This can be used to restore the client to a logged in state, including
717    /// loading the sync state and the encryption keys from the store, if one
718    /// was set up.
719    ///
720    /// # Persisting the store
721    ///
722    /// Restoring only reattaches the client to its stored state; it does not
723    /// recreate that state. The same persistent store used during the original
724    /// login (for example via [`ClientBuilder::sqlite_store()`]) must be
725    /// configured on the [`ClientBuilder`] when the session is restored,
726    /// otherwise the encryption keys and room state will not be available. When
727    /// the `e2e-encryption` feature is enabled, restoring on top of an
728    /// in-memory store will leave the client unable to send or receive
729    /// encrypted messages. See the [`persist_session`] example for a full
730    /// walk-through.
731    ///
732    /// [`ClientBuilder`]: crate::ClientBuilder
733    /// [`ClientBuilder::sqlite_store()`]: crate::ClientBuilder::sqlite_store
734    /// [`persist_session`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/examples/persist_session
735    ///
736    /// # Arguments
737    ///
738    /// - `session` - The session to restore.
739    /// - `room_load_settings` — Specify how many rooms must be restored; use
740    ///   `::default()` if you don't know which value to pick.
741    ///
742    /// # Panic
743    ///
744    /// Panics if authentication data was already set.
745    pub async fn restore_session(
746        &self,
747        session: OAuthSession,
748        room_load_settings: RoomLoadSettings,
749    ) -> Result<()> {
750        let OAuthSession { client_id, user: UserSession { meta, tokens } } = session;
751
752        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
753
754        self.client.auth_ctx().set_session_tokens(tokens.clone());
755        self.client
756            .activate(
757                meta,
758                room_load_settings,
759                #[cfg(feature = "e2e-encryption")]
760                None,
761            )
762            .await?;
763        #[cfg(feature = "e2e-encryption")]
764        self.deferred_enable_cross_process_refresh_lock().await;
765
766        self.client
767            .inner
768            .auth_ctx
769            .auth_data
770            .set(AuthData::OAuth(data))
771            .expect("Client authentication data was already set");
772
773        // Initialize the cross-process locking by saving our tokens' hash into
774        // the database, if we've enabled the cross-process lock.
775
776        #[cfg(feature = "e2e-encryption")]
777        if let Some(cross_process_lock) = self.ctx().cross_process_token_refresh_manager.get() {
778            cross_process_lock.restore_session(&tokens).await;
779
780            let mut guard = cross_process_lock
781                .spin_lock()
782                .await
783                .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
784
785            // After we got the lock, it's possible that our session doesn't
786            // match the one read from the database, because of a race: another
787            // process has refreshed the tokens while we were waiting for the
788            // lock.
789            //
790            // In that case, if there's a mismatch, we reload the session and
791            // update the hash. Otherwise, we save our hash into the database.
792
793            if guard.hash_mismatch {
794                Box::pin(self.handle_session_hash_mismatch(&mut guard))
795                    .await
796                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
797            } else {
798                guard
799                    .save_in_memory_and_db(&tokens)
800                    .await
801                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
802                // No need to call the save_session_callback here; it was the
803                // source of the session, so it's already in sync with what we
804                // had.
805            }
806        }
807
808        #[cfg(feature = "e2e-encryption")]
809        self.client.encryption().spawn_initialization_task(None).await;
810
811        Ok(())
812    }
813
814    #[cfg(feature = "e2e-encryption")]
815    async fn handle_session_hash_mismatch(
816        &self,
817        guard: &mut CrossProcessRefreshLockGuard,
818    ) -> Result<(), CrossProcessRefreshLockError> {
819        trace!("Handling hash mismatch.");
820
821        let callback = self
822            .client
823            .auth_ctx()
824            .reload_session_callback
825            .get()
826            .ok_or(CrossProcessRefreshLockError::MissingReloadSession)?;
827
828        match callback(self.client.clone()) {
829            Ok(tokens) => {
830                guard.handle_mismatch(&tokens).await?;
831
832                self.client.auth_ctx().set_session_tokens(tokens.clone());
833                // The app's callback acted as authoritative here, so we're not
834                // saving the data back into the app, as that would have no
835                // effect.
836            }
837            Err(err) => {
838                error!("when reloading OAuth 2.0 session tokens from callback: {err}");
839            }
840        }
841
842        Ok(())
843    }
844
845    /// The scopes to request for logging in and the corresponding device ID.
846    fn login_scopes(
847        device_id: Option<OwnedDeviceId>,
848        additional_scopes: Option<Vec<Scope>>,
849    ) -> (Vec<Scope>, OwnedDeviceId) {
850        /// Scope to grand full access to the client-server API.
851        const SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS: &str = "urn:matrix:client:api:*";
852        /// Prefix of the scope to bind a device ID to an access token.
853        const SCOPE_MATRIX_DEVICE_ID_PREFIX: &str = "urn:matrix:client:device:";
854
855        // Generate the device ID if it is not provided.
856        let device_id = device_id.unwrap_or_else(DeviceId::new);
857
858        let mut scopes = vec![
859            Scope::new(SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS.to_owned()),
860            Scope::new(format!("{SCOPE_MATRIX_DEVICE_ID_PREFIX}{device_id}")),
861        ];
862
863        if let Some(extra_scopes) = additional_scopes {
864            scopes.extend(extra_scopes);
865        }
866
867        (scopes, device_id)
868    }
869
870    /// Log in via OAuth 2.0 with the Authorization Code flow.
871    ///
872    /// This method requires to open a URL in the end-user's browser where they
873    /// will be able to log into their account in the server's web UI and grant
874    /// access to their Matrix account.
875    ///
876    /// The [`OAuthAuthCodeUrlBuilder`] that is returned allows to customize a
877    /// few settings before calling `.build()` to obtain the URL to open in the
878    /// browser of the end-user.
879    ///
880    /// [`OAuth::finish_login()`] must be called once the user has been
881    /// redirected to the `redirect_uri`. [`OAuth::abort_login()`] should be
882    /// called instead if the authorization should be aborted before completion.
883    ///
884    /// # Arguments
885    ///
886    /// - `redirect_uri` - The URI where the end user will be redirected after
887    ///   authorizing the login. It must be one of the redirect URIs sent in the
888    ///   client metadata during registration.
889    ///
890    /// - `device_id` - The unique ID that will be associated with the session.
891    ///   If not set, a random one will be generated. It can be an existing
892    ///   device ID from a previous login call. Note that this should be done
893    ///   only if the client also holds the corresponding encryption keys.
894    ///
895    /// - `registration_data` - The data to restore or register the client with
896    ///   the server. If this is not provided, an error will occur unless
897    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
898    ///   was called previously.
899    ///
900    /// - `additional_scopes` - Additional scopes to request from the
901    ///   authorization server, e.g.
902    ///   "urn:matrix:client:com.example.msc9999.foo". The scopes for API access
903    ///   and the device ID according to the
904    ///   [specification](https://spec.matrix.org/v1.15/client-server-api/#allocated-scope-tokens)
905    ///   are always requested.
906    ///
907    /// # Example
908    ///
909    /// ```no_run
910    /// use matrix_sdk::{
911    ///     authentication::oauth::registration::ClientMetadata,
912    ///     ruma::serde::Raw,
913    /// };
914    /// use url::Url;
915    /// # use matrix_sdk::Client;
916    /// # let client: Client = unimplemented!();
917    /// # let redirect_uri = unimplemented!();
918    /// # async fn open_uri_and_wait_for_redirect(uri: Url) -> Url { unimplemented!() };
919    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() };
920    /// # _ = async {
921    /// let oauth = client.oauth();
922    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
923    /// let registration_data = client_metadata.into();
924    ///
925    /// let auth_data = oauth.login(redirect_uri, None, Some(registration_data), None)
926    ///                      .build()
927    ///                      .await?;
928    ///
929    /// // Open auth_data.url and wait for response at the redirect URI.
930    /// let redirected_to_uri: Url = open_uri_and_wait_for_redirect(auth_data.url).await;
931    ///
932    /// oauth.finish_login(redirected_to_uri.into()).await?;
933    ///
934    /// // The session tokens can be persisted from the
935    /// // `OAuth::full_session()` method.
936    ///
937    /// // You can now make requests to the Matrix API.
938    /// let _me = client.whoami().await?;
939    /// # anyhow::Ok(()) }
940    /// ```
941    pub fn login(
942        &self,
943        redirect_uri: Url,
944        device_id: Option<OwnedDeviceId>,
945        registration_data: Option<ClientRegistrationData>,
946        additional_scopes: Option<Vec<Scope>>,
947    ) -> OAuthAuthCodeUrlBuilder {
948        let (scopes, device_id) = Self::login_scopes(device_id, additional_scopes);
949
950        OAuthAuthCodeUrlBuilder::new(
951            self.clone(),
952            scopes.to_vec(),
953            device_id,
954            redirect_uri,
955            registration_data,
956        )
957    }
958
959    /// Finish the login process.
960    ///
961    /// This method should be called after the URL returned by
962    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
963    /// been redirected to the redirect URI after completing the authorization.
964    ///
965    /// If the authorization needs to be cancelled before its completion,
966    /// [`OAuth::abort_login()`] should be used instead to clean up the local
967    /// data.
968    ///
969    /// # Arguments
970    ///
971    /// - `url_or_query` - The URI where the user was redirected, or just its
972    ///   query part.
973    ///
974    /// Returns an error if the authorization failed, if a request fails, or if
975    /// the client was already logged in with a different session.
976    pub async fn finish_login(&self, url_or_query: UrlOrQuery) -> Result<()> {
977        let response = AuthorizationResponse::parse_url_or_query(&url_or_query)
978            .map_err(|error| OAuthError::from(OAuthAuthorizationCodeError::from(error)))?;
979
980        let auth_code = match response {
981            AuthorizationResponse::Success(code) => code,
982            AuthorizationResponse::Error(error) => {
983                self.abort_login(&error.state).await;
984                return Err(OAuthError::from(OAuthAuthorizationCodeError::from(error.error)).into());
985            }
986        };
987
988        let device_id = self.finish_authorization(auth_code).await?;
989        self.load_session(device_id).await
990    }
991
992    /// Load the session after login.
993    ///
994    /// Returns an error if the request to get the user ID fails, or if the
995    /// client was already logged in with a different session.
996    pub(crate) async fn load_session(&self, device_id: OwnedDeviceId) -> Result<()> {
997        // Get the user ID.
998        let whoami_res = self.client.whoami().await.map_err(crate::Error::from)?;
999
1000        let new_session = SessionMeta { user_id: whoami_res.user_id, device_id };
1001
1002        if let Some(current_session) = self.client.session_meta() {
1003            if new_session != *current_session {
1004                return Err(OAuthError::SessionMismatch.into());
1005            }
1006        } else {
1007            self.client
1008                .activate(
1009                    new_session,
1010                    RoomLoadSettings::default(),
1011                    #[cfg(feature = "e2e-encryption")]
1012                    None,
1013                )
1014                .await?;
1015            // At this point the Olm machine has been set up.
1016
1017            // Enable the cross-process lock for refreshes, if needs be.
1018            #[cfg(feature = "e2e-encryption")]
1019            self.enable_cross_process_lock().await.map_err(OAuthError::from)?;
1020
1021            #[cfg(feature = "e2e-encryption")]
1022            self.client.encryption().spawn_initialization_task(None).await;
1023        }
1024
1025        Ok(())
1026    }
1027
1028    #[cfg(feature = "e2e-encryption")]
1029    pub(crate) async fn enable_cross_process_lock(
1030        &self,
1031    ) -> Result<(), CrossProcessRefreshLockError> {
1032        // Enable the cross-process lock for refreshes, if needs be.
1033        self.deferred_enable_cross_process_refresh_lock().await;
1034
1035        if let Some(cross_process_manager) = self.ctx().cross_process_token_refresh_manager.get()
1036            && let Some(tokens) = self.client.session_tokens()
1037        {
1038            let mut cross_process_guard = cross_process_manager.spin_lock().await?;
1039
1040            if cross_process_guard.hash_mismatch {
1041                // At this point, we're finishing a login while another process
1042                // had written something in the database. It's likely the
1043                // information in the database is just outdated and wasn't
1044                // properly updated, but display a warning, just in case this
1045                // happens frequently.
1046                warn!("unexpected cross-process hash mismatch when finishing login (see comment)");
1047            }
1048
1049            cross_process_guard.save_in_memory_and_db(&tokens).await?;
1050        }
1051
1052        Ok(())
1053    }
1054
1055    /// Finish the authorization process.
1056    ///
1057    /// This method should be called after the URL returned by
1058    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
1059    /// been redirected to the redirect URI after a successful authorization.
1060    ///
1061    /// # Arguments
1062    ///
1063    /// - `auth_code` - The response received as part of the redirect URI when
1064    ///   the authorization was successful.
1065    ///
1066    /// Returns the device ID used in the authorized scope if it succeeds.
1067    /// Returns an error if a request fails.
1068    async fn finish_authorization(
1069        &self,
1070        auth_code: AuthorizationCode,
1071    ) -> Result<OwnedDeviceId, OAuthError> {
1072        let data = self.data().ok_or(OAuthError::NotAuthenticated)?;
1073        let client_id = data.client_id.clone();
1074
1075        let validation_data = data
1076            .authorization_data
1077            .lock()
1078            .await
1079            .remove(&auth_code.state)
1080            .ok_or(OAuthAuthorizationCodeError::InvalidState)?;
1081
1082        let token_uri = TokenUrl::from_url(validation_data.server_metadata.token_endpoint.clone());
1083
1084        let response = OAuthClient::new(client_id)
1085            .set_token_uri(token_uri)
1086            .exchange_code(oauth2::AuthorizationCode::new(auth_code.code))
1087            .set_pkce_verifier(validation_data.pkce_verifier)
1088            .set_redirect_uri(Cow::Owned(validation_data.redirect_uri))
1089            .request_async(self.http_client())
1090            .await
1091            .map_err(OAuthAuthorizationCodeError::RequestToken)?;
1092
1093        self.client.auth_ctx().set_session_tokens(SessionTokens {
1094            access_token: response.access_token().secret().clone(),
1095            refresh_token: response.refresh_token().map(RefreshToken::secret).cloned(),
1096        });
1097
1098        Ok(validation_data.device_id)
1099    }
1100
1101    /// Abort the login process.
1102    ///
1103    /// This method should be called if a login should be aborted before it is
1104    /// completed.
1105    ///
1106    /// If the login has been completed, [`OAuth::finish_login()`] should be
1107    /// used instead.
1108    ///
1109    /// # Arguments
1110    ///
1111    /// - `state` - The state provided in [`OAuthAuthorizationData`] after
1112    ///   building the authorization URL.
1113    pub async fn abort_login(&self, state: &CsrfToken) {
1114        if let Some(data) = self.data() {
1115            data.authorization_data.lock().await.remove(state);
1116        }
1117    }
1118
1119    /// Request codes from the authorization server for logging in with another
1120    /// device.
1121    #[cfg(feature = "e2e-encryption")]
1122    async fn request_device_authorization(
1123        &self,
1124        server_metadata: &AuthorizationServerMetadata,
1125        device_id: Option<OwnedDeviceId>,
1126    ) -> Result<oauth2::StandardDeviceAuthorizationResponse, qrcode::DeviceAuthorizationOAuthError>
1127    {
1128        let (scopes, _) = Self::login_scopes(device_id, None);
1129
1130        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1131
1132        let device_authorization_url = server_metadata
1133            .device_authorization_endpoint
1134            .clone()
1135            .map(oauth2::DeviceAuthorizationUrl::from_url)
1136            .ok_or(qrcode::DeviceAuthorizationOAuthError::NoDeviceAuthorizationEndpoint)?;
1137
1138        let response = OAuthClient::new(client_id)
1139            .set_device_authorization_url(device_authorization_url)
1140            .exchange_device_code()
1141            .add_scopes(scopes)
1142            .request_async(self.http_client())
1143            .await?;
1144
1145        Ok(response)
1146    }
1147
1148    /// Exchange the device code against an access token.
1149    #[cfg(feature = "e2e-encryption")]
1150    async fn exchange_device_code(
1151        &self,
1152        server_metadata: &AuthorizationServerMetadata,
1153        device_authorization_response: &oauth2::StandardDeviceAuthorizationResponse,
1154    ) -> Result<(), qrcode::DeviceAuthorizationOAuthError> {
1155        use oauth2::TokenResponse;
1156
1157        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1158
1159        let token_uri = TokenUrl::from_url(server_metadata.token_endpoint.clone());
1160
1161        let response = OAuthClient::new(client_id)
1162            .set_token_uri(token_uri)
1163            .exchange_device_access_token(device_authorization_response)
1164            .request_async(self.http_client(), matrix_sdk_common::sleep::sleep, None)
1165            .await?;
1166
1167        self.client.auth_ctx().set_session_tokens(SessionTokens {
1168            access_token: response.access_token().secret().to_owned(),
1169            refresh_token: response.refresh_token().map(|t| t.secret().to_owned()),
1170        });
1171
1172        Ok(())
1173    }
1174
1175    async fn refresh_access_token_inner(
1176        self,
1177        refresh_token: String,
1178        token_endpoint: Url,
1179        client_id: ClientId,
1180        #[cfg(feature = "e2e-encryption")] cross_process_lock: Option<CrossProcessRefreshLockGuard>,
1181    ) -> Result<(), OAuthError> {
1182        trace!(
1183            "Token refresh: attempting to refresh with refresh_token {}",
1184            hash_str(&refresh_token)
1185        );
1186
1187        let token = RefreshToken::new(refresh_token.clone());
1188        let token_uri = TokenUrl::from_url(token_endpoint);
1189
1190        let response = OAuthClient::new(client_id)
1191            .set_token_uri(token_uri)
1192            .exchange_refresh_token(&token)
1193            .request_async(self.http_client())
1194            .await
1195            .map_err(OAuthError::RefreshToken)?;
1196
1197        let new_access_token = response.access_token().secret().clone();
1198        let new_refresh_token = response.refresh_token().map(RefreshToken::secret).cloned();
1199
1200        trace!(
1201            "Token refresh: new refresh_token: {} / access_token: {}",
1202            new_refresh_token.as_deref().map(hash_str).unwrap_or_else(|| "<none>".to_owned()),
1203            hash_str(&new_access_token)
1204        );
1205
1206        let tokens = SessionTokens {
1207            access_token: new_access_token,
1208            refresh_token: new_refresh_token.or(Some(refresh_token)),
1209        };
1210
1211        #[cfg(feature = "e2e-encryption")]
1212        let tokens_clone = tokens.clone();
1213
1214        self.client.auth_ctx().set_session_tokens(tokens);
1215
1216        // Call the save_session_callback if set, while the optional lock is
1217        // being held.
1218        if let Some(save_session_callback) = self.client.auth_ctx().save_session_callback.get() {
1219            // Satisfies the save_session_callback invariant: set_session_tokens
1220            // has been called just above.
1221            tracing::debug!("call save_session_callback");
1222            if let Err(err) = save_session_callback(self.client.clone()) {
1223                error!("when saving session after refresh: {err}");
1224            }
1225        }
1226
1227        #[cfg(feature = "e2e-encryption")]
1228        if let Some(mut lock) = cross_process_lock {
1229            lock.save_in_memory_and_db(&tokens_clone).await?;
1230        }
1231
1232        tracing::debug!("broadcast session changed");
1233        _ = self.client.auth_ctx().session_change_sender.send(SessionChange::TokensRefreshed);
1234
1235        Ok(())
1236    }
1237
1238    /// Refresh the access token.
1239    ///
1240    /// This should be called when the access token has expired. It should not
1241    /// be needed to call this manually if the [`Client`] was constructed with
1242    /// [`ClientBuilder::handle_refresh_tokens()`].
1243    ///
1244    /// This method is protected behind a lock, so calling this method several
1245    /// times at once will only call the endpoint once and all subsequent calls
1246    /// will wait for the result of the first call.
1247    ///
1248    /// [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
1249    #[instrument(skip_all)]
1250    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1251        macro_rules! fail {
1252            ($lock:expr, $err:expr) => {
1253                let error = $err;
1254                *$lock = Err(error.clone());
1255                return Err(error);
1256            };
1257        }
1258
1259        let client = &self.client;
1260
1261        let refresh_status_lock = client.auth_ctx().refresh_token_lock.clone().try_lock_owned();
1262
1263        let Ok(mut refresh_status_guard) = refresh_status_lock else {
1264            debug!("another refresh is happening, waiting for result.");
1265            // There's already a request to refresh happening in the same
1266            // process. Wait for it to finish.
1267            let res = client.auth_ctx().refresh_token_lock.lock().await.clone();
1268            debug!("other refresh is a {}", if res.is_ok() { "success" } else { "failure " });
1269            return res;
1270        };
1271
1272        debug!("no other refresh happening in background, starting.");
1273
1274        // Fetch the authorization server metadata _before_ taking the
1275        // cross-process lock, checking the session hash, or reading the refresh
1276        // token. This request can stall for a long time when the OS suspends
1277        // the process (e.g. iOS background suspension), and while suspended the
1278        // lock lease lapses, which lets another process refresh and rotate the
1279        // token. Doing it first means the lock and the hash check happen after
1280        // the stall, so such a rotation is caught below as a hash mismatch
1281        // instead of being exchanged while stale, which the server rejects with
1282        // `invalid_grant` and signs the user out.
1283        let server_metadata = match self.server_metadata().await {
1284            Ok(metadata) => metadata,
1285            Err(err) => {
1286                warn!("couldn't get authorization server metadata: {err:?}");
1287                fail!(refresh_status_guard, RefreshTokenError::OAuth(Arc::new(err.into())));
1288            }
1289        };
1290
1291        let Some(client_id) = self.client_id().cloned() else {
1292            warn!("invalid state: missing client ID");
1293            fail!(
1294                refresh_status_guard,
1295                RefreshTokenError::OAuth(Arc::new(OAuthError::NotAuthenticated))
1296            );
1297        };
1298
1299        #[cfg(feature = "e2e-encryption")]
1300        let cross_process_guard =
1301            if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1302                let mut cross_process_guard = match manager
1303                    .spin_lock()
1304                    .await
1305                    .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))
1306                {
1307                    Ok(guard) => guard,
1308                    Err(err) => {
1309                        warn!("couldn't acquire cross-process lock (timeout)");
1310                        fail!(refresh_status_guard, err);
1311                    }
1312                };
1313
1314                if cross_process_guard.hash_mismatch {
1315                    Box::pin(self.handle_session_hash_mismatch(&mut cross_process_guard))
1316                        .await
1317                        .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))?;
1318                    // Optimistic exit: assume that the underlying process did
1319                    // update fast enough. In the worst case, we'll do another
1320                    // refresh Soon™.
1321                    tracing::info!("other process handled refresh for us, assuming success");
1322                    *refresh_status_guard = Ok(());
1323                    return Ok(());
1324                }
1325
1326                Some(cross_process_guard)
1327            } else {
1328                None
1329            };
1330
1331        // Read the refresh token only now, after the hash check above, so we
1332        // always exchange the token that is current in the store, never one
1333        // that another process rotated out from under us while we were
1334        // suspended.
1335        let Some(session_tokens) = self.client.session_tokens() else {
1336            warn!("invalid state: missing session tokens");
1337            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1338        };
1339
1340        let Some(refresh_token) = session_tokens.refresh_token else {
1341            warn!("invalid state: missing session tokens");
1342            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1343        };
1344
1345        // Do not interrupt refresh access token requests and processing, by
1346        // detaching the request sending and response processing. Make sure to
1347        // keep the `refresh_status_guard` during the entire processing.
1348
1349        let this = self.clone();
1350
1351        spawn(async move {
1352            match this
1353                .refresh_access_token_inner(
1354                    refresh_token,
1355                    server_metadata.token_endpoint,
1356                    client_id,
1357                    #[cfg(feature = "e2e-encryption")]
1358                    cross_process_guard,
1359                )
1360                .await
1361            {
1362                Ok(()) => {
1363                    debug!("success refreshing a token");
1364                    *refresh_status_guard = Ok(());
1365                    Ok(())
1366                }
1367
1368                Err(err) => {
1369                    let err = RefreshTokenError::OAuth(Arc::new(err));
1370                    warn!("error refreshing an OAuth 2.0 token: {err}");
1371                    fail!(refresh_status_guard, err);
1372                }
1373            }
1374        })
1375        .await
1376        .expect("joining")
1377    }
1378
1379    /// Log out from the currently authenticated session.
1380    pub async fn logout(&self) -> Result<(), OAuthError> {
1381        let client_id = self.client_id().ok_or(OAuthError::NotAuthenticated)?.clone();
1382
1383        let server_metadata = self.server_metadata().await?;
1384        let revocation_url = RevocationUrl::from_url(server_metadata.revocation_endpoint);
1385
1386        let tokens = self.client.session_tokens().ok_or(OAuthError::NotAuthenticated)?;
1387
1388        // Revoke the access token, it should revoke both tokens.
1389        OAuthClient::new(client_id)
1390            .set_revocation_url(revocation_url)
1391            .revoke_token(StandardRevocableToken::AccessToken(AccessToken::new(
1392                tokens.access_token,
1393            )))
1394            .map_err(OAuthTokenRevocationError::Url)?
1395            .request_async(self.http_client())
1396            .await
1397            .map_err(OAuthTokenRevocationError::Revoke)?;
1398
1399        #[cfg(feature = "e2e-encryption")]
1400        if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1401            manager.on_logout().await?;
1402        }
1403
1404        Ok(())
1405    }
1406}
1407
1408/// Builder for QR login futures.
1409#[cfg(feature = "e2e-encryption")]
1410#[derive(Debug)]
1411pub struct LoginWithQrCodeBuilder<'a> {
1412    /// The underlying Matrix API client.
1413    client: &'a Client,
1414
1415    /// The data to restore or register the client with the server.
1416    registration_data: Option<&'a ClientRegistrationData>,
1417}
1418
1419#[cfg(feature = "e2e-encryption")]
1420impl<'a> LoginWithQrCodeBuilder<'a> {
1421    /// Create a new builder.
1422    fn new(client: &'a Client, registration_data: Option<&'a ClientRegistrationData>) -> Self {
1423        Self { client, registration_data }
1424    }
1425
1426    /// This method allows you to log in with a scanned QR code.
1427    ///
1428    /// The existing device needs to display the QR code which this device can
1429    /// scan and call this method to log in.
1430    ///
1431    /// A successful login using this method will automatically mark the device
1432    /// as verified and transfer all end-to-end encryption related secrets, like
1433    /// the private cross-signing keys and the backup key from the existing
1434    /// device to the new device.
1435    ///
1436    /// For the reverse flow where this device generates the QR code for the
1437    /// existing device to scan, use [`LoginWithQrCodeBuilder::generate`].
1438    ///
1439    /// # Arguments
1440    ///
1441    /// - `data` - The data scanned from a QR code.
1442    ///
1443    /// # Example
1444    ///
1445    /// ```no_run
1446    /// use anyhow::bail;
1447    /// use futures_util::StreamExt;
1448    /// use matrix_sdk::{
1449    ///     authentication::oauth::{
1450    ///         registration::ClientMetadata,
1451    ///         qrcode::{LoginProgress, Msc4108IntentData, QrCodeData, QrCodeIntentData, QrProgress},
1452    ///     },
1453    ///     ruma::serde::Raw,
1454    ///     Client,
1455    /// };
1456    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
1457    /// # _ = async {
1458    /// # let bytes = unimplemented!();
1459    /// // You'll need to use a different library to scan and extract the raw bytes from the QR
1460    /// // code.
1461    /// let qr_code_data = QrCodeData::from_bytes(bytes)?;
1462    ///
1463    /// // Fetch the homeserver out of the parsed QR code data.
1464    /// let QrCodeIntentData::Msc4108 { data: Msc4108IntentData::Reciprocate { server_name }, ..} = qr_code_data.intent_data() else {
1465    ///     bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
1466    /// };
1467    ///
1468    /// // Build the client as usual.
1469    /// let client = Client::builder()
1470    ///     .server_name_or_homeserver_url(server_name)
1471    ///     .handle_refresh_tokens()
1472    ///     .build()
1473    ///     .await?;
1474    ///
1475    /// let oauth = client.oauth();
1476    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
1477    /// let registration_data = client_metadata.into();
1478    ///
1479    /// // Subscribing to the progress is necessary since we need to input the check
1480    /// // code on the existing device.
1481    /// let login = oauth.login_with_qr_code(Some(&registration_data)).scan(&qr_code_data);
1482    /// let mut progress = login.subscribe_to_progress();
1483    ///
1484    /// // Create a task which will show us the progress and tell us the check
1485    /// // code to input in the existing device.
1486    /// let task = tokio::spawn(async move {
1487    ///     while let Some(state) = progress.next().await {
1488    ///         match state {
1489    ///             LoginProgress::Starting | LoginProgress::SyncingSecrets => (),
1490    ///             LoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1491    ///                 println!("Please enter the following code into the other device {check_code:02}");
1492    ///             },
1493    ///             LoginProgress::WaitingForToken { user_code } => {
1494    ///                 println!("Please use your other device to confirm the log in {user_code}")
1495    ///             },
1496    ///             LoginProgress::Done => break,
1497    ///         }
1498    ///     }
1499    /// });
1500    ///
1501    /// // Now run the future to complete the login.
1502    /// login.await?;
1503    /// task.abort();
1504    ///
1505    /// println!("Successfully logged in: {:?} {:?}", client.user_id(), client.device_id());
1506    /// # anyhow::Ok(()) };
1507    /// ```
1508    pub fn scan(self, data: &QrCodeData) -> LoginWithQrCode {
1509        LoginWithQrCode::new(self.client, data, self.registration_data)
1510    }
1511
1512    /// This method allows you to log in by generating a QR code.
1513    ///
1514    /// This device needs to call this method to generate and display the QR
1515    /// code which the existing device can scan and grant the log in.
1516    ///
1517    /// A successful login using this method will automatically mark the device
1518    /// as verified and transfer all end-to-end encryption related secrets, like
1519    /// the private cross-signing keys and the backup key from the existing
1520    /// device to the new device.
1521    ///
1522    /// For the reverse flow where the existing device generates the QR code for
1523    /// this device to scan, use [`LoginWithQrCodeBuilder::scan`].
1524    ///
1525    /// # Example
1526    ///
1527    /// ```no_run
1528    /// use anyhow::bail;
1529    /// use futures_util::StreamExt;
1530    /// use matrix_sdk::{
1531    ///     authentication::oauth::{
1532    ///         registration::ClientMetadata,
1533    ///         qrcode::{GeneratedQrProgress, LoginProgress, QrCodeData},
1534    ///     },
1535    ///     ruma::serde::Raw,
1536    ///     Client,
1537    /// };
1538    /// use std::{error::Error, io::stdin};
1539    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
1540    /// # _ = async {
1541    /// // Build the client as usual.
1542    /// let client = Client::builder()
1543    ///     .server_name_or_homeserver_url("matrix.org")
1544    ///     .handle_refresh_tokens()
1545    ///     .build()
1546    ///     .await?;
1547    ///
1548    /// let oauth = client.oauth();
1549    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
1550    /// let registration_data = client_metadata.into();
1551    ///
1552    /// // Subscribing to the progress is necessary since we need to display the
1553    /// // QR code and prompt for the check code.
1554    /// let login = oauth.login_with_qr_code(Some(&registration_data)).generate();
1555    /// let mut progress = login.subscribe_to_progress();
1556    ///
1557    /// // Create a task which will show us the progress and allows us to display
1558    /// // the QR code and prompt for the check code.
1559    /// let task = tokio::spawn(async move {
1560    ///     while let Some(state) = progress.next().await {
1561    ///         match state {
1562    ///             LoginProgress::Starting | LoginProgress::SyncingSecrets => (),
1563    ///             LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(qr)) => {
1564    ///                 println!("Please use your other device to scan the QR code {:?}", qr)
1565    ///             }
1566    ///             LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(cctx)) => {
1567    ///                 println!("Please enter the code displayed on your other device");
1568    ///                 let mut s = String::new();
1569    ///                 stdin().read_line(&mut s)?;
1570    ///                 let check_code = s.trim().parse::<u8>()?;
1571    ///                 cctx.send(check_code).await?
1572    ///             }
1573    ///             LoginProgress::WaitingForToken { user_code } => {
1574    ///                 println!("Please use your other device to confirm the log in {user_code}")
1575    ///             },
1576    ///             LoginProgress::Done => break,
1577    ///         }
1578    ///     }
1579    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1580    /// });
1581    ///
1582    /// // Now run the future to complete the login.
1583    /// login.await?;
1584    /// task.abort();
1585    ///
1586    /// println!("Successfully logged in: {:?} {:?}", client.user_id(), client.device_id());
1587    /// # anyhow::Ok(()) };
1588    /// ```
1589    pub fn generate(self) -> LoginWithGeneratedQrCode {
1590        LoginWithGeneratedQrCode::new(self.client, self.registration_data)
1591    }
1592}
1593
1594/// Builder for QR login grant handlers.
1595#[cfg(feature = "e2e-encryption")]
1596#[derive(Debug)]
1597pub struct GrantLoginWithQrCodeBuilder<'a> {
1598    /// The underlying Matrix API client.
1599    client: &'a Client,
1600    /// The duration to wait for the homeserver to create the new device after
1601    /// consenting the login before giving up.
1602    device_creation_timeout: Duration,
1603}
1604
1605#[cfg(feature = "e2e-encryption")]
1606impl<'a> GrantLoginWithQrCodeBuilder<'a> {
1607    /// Create a new builder with the default device creation timeout.
1608    fn new(client: &'a Client) -> Self {
1609        Self { client, device_creation_timeout: Duration::from_secs(10) }
1610    }
1611
1612    /// Set the device creation timeout.
1613    ///
1614    /// # Arguments
1615    ///
1616    /// - `device_creation_timeout` - The duration to wait for the homeserver to
1617    ///   create the new device after consenting the login before giving up.
1618    pub fn device_creation_timeout(mut self, device_creation_timeout: Duration) -> Self {
1619        self.device_creation_timeout = device_creation_timeout;
1620        self
1621    }
1622
1623    /// This method allows you to grant login to a new device by scanning a QR
1624    /// code generated by the new device.
1625    ///
1626    /// The new device needs to display the QR code which this device can scan
1627    /// and call this method to grant the login.
1628    ///
1629    /// A successful login grant using this method will automatically mark the
1630    /// new device as verified and transfer all end-to-end encryption related
1631    /// secrets, like the private cross-signing keys and the backup key from
1632    /// this device device to the new device.
1633    ///
1634    /// For the reverse flow where this device generates the QR code for the new
1635    /// device to scan, use [`GrantLoginWithQrCodeBuilder::generate`].
1636    ///
1637    /// # Arguments
1638    ///
1639    /// - `data` - The data scanned from a QR code.
1640    ///
1641    /// # Example
1642    ///
1643    /// ```no_run
1644    /// use anyhow::bail;
1645    /// use futures_util::StreamExt;
1646    /// use matrix_sdk::{
1647    ///     Client, authentication::oauth::{
1648    ///         qrcode::{GrantLoginProgress, QrCodeData, QrProgress},
1649    ///     }
1650    /// };
1651    /// use std::{error::Error, io::stdin};
1652    /// # _ = async {
1653    /// # let bytes = unimplemented!();
1654    /// // You'll need to use a different library to scan and extract the raw bytes from the QR
1655    /// // code.
1656    /// let qr_code_data = QrCodeData::from_bytes(bytes)?;
1657    ///
1658    /// // Build the client as usual.
1659    /// let client = Client::builder()
1660    ///     .server_name_or_homeserver_url("matrix.org")
1661    ///     .handle_refresh_tokens()
1662    ///     .build()
1663    ///     .await?;
1664    ///
1665    /// let oauth = client.oauth();
1666    ///
1667    /// // Subscribing to the progress is necessary to capture
1668    /// // the checkcode in order to display it to the other device and to obtain the verification URL to
1669    /// // open it in a browser so the user can consent to the new login.
1670    /// let mut grant = oauth.grant_login_with_qr_code().scan(&qr_code_data);
1671    /// let mut progress = grant.subscribe_to_progress();
1672    ///
1673    /// // Create a task which will show us the progress and allows us to receive
1674    /// // and feed back data.
1675    /// let task = tokio::spawn(async move {
1676    ///     while let Some(state) = progress.next().await {
1677    ///         match state {
1678    ///             GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
1679    ///             GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1680    ///                 println!("Please enter the checkcode on your other device: {:?}", check_code);
1681    ///             }
1682    ///             GrantLoginProgress::WaitingForAuth { verification_uri, continuation_sender } => {
1683    ///                 println!("Please open {verification_uri} to confirm the new login");
1684    ///
1685    ///                 // Once the new login has been confirmed in the browser, we can let the
1686    ///                 // client continue with the process.
1687    ///                 continuation_sender.confirm().await?;
1688    ///             },
1689    ///             GrantLoginProgress::Done => break,
1690    ///         }
1691    ///     }
1692    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1693    /// });
1694    ///
1695    /// // Now run the future to grant the login.
1696    /// grant.await?;
1697    /// task.abort();
1698    ///
1699    /// println!("Successfully granted login");
1700    /// # anyhow::Ok(()) };
1701    /// ```
1702    pub fn scan(self, data: &QrCodeData) -> GrantLoginWithScannedQrCode {
1703        GrantLoginWithScannedQrCode::new(self.client, data, self.device_creation_timeout)
1704    }
1705
1706    /// This method allows you to grant login to a new device by generating a QR
1707    /// code on this device to be scanned by the new device.
1708    ///
1709    /// This device needs to call this method to generate and display the QR
1710    /// code which the new device can scan to initiate the grant process.
1711    ///
1712    /// A successful login grant using this method will automatically mark the
1713    /// new device as verified and transfer all end-to-end encryption related
1714    /// secrets, like the private cross-signing keys and the backup key from
1715    /// this device device to the new device.
1716    ///
1717    /// For the reverse flow where the new device generates the QR code for this
1718    /// device to scan, use [`GrantLoginWithQrCodeBuilder::scan`].
1719    ///
1720    /// # Example
1721    ///
1722    /// ```no_run
1723    /// use anyhow::bail;
1724    /// use futures_util::StreamExt;
1725    /// use matrix_sdk::{
1726    ///     Client, authentication::oauth::{
1727    ///         qrcode::{GeneratedQrProgress, GrantLoginProgress}
1728    ///     }
1729    /// };
1730    /// use std::{error::Error, io::stdin};
1731    /// # _ = async {
1732    /// // Build the client as usual.
1733    /// let client = Client::builder()
1734    ///     .server_name_or_homeserver_url("matrix.org")
1735    ///     .handle_refresh_tokens()
1736    ///     .build()
1737    ///     .await?;
1738    ///
1739    /// let oauth = client.oauth();
1740    ///
1741    /// // Subscribing to the progress is necessary since we need to capture the
1742    /// // QR code, feed the checkcode back in and obtain the verification URL to
1743    /// // open it in a browser so the user can consent to the new login.
1744    /// let mut grant = oauth.grant_login_with_qr_code().generate();
1745    /// let mut progress = grant.subscribe_to_progress();
1746    ///
1747    /// // Create a task which will show us the progress and allows us to receive
1748    /// // and feed back data.
1749    /// let task = tokio::spawn(async move {
1750    ///     while let Some(state) = progress.next().await {
1751    ///         match state {
1752    ///             GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
1753    ///             GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(qr_code_data)) => {
1754    ///                 println!("Please scan the QR code on your other device: {:?}", qr_code_data);
1755    ///             }
1756    ///             GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(checkcode_sender)) => {
1757    ///                 println!("Please enter the code displayed on your other device");
1758    ///                 let mut s = String::new();
1759    ///                 stdin().read_line(&mut s)?;
1760    ///                 let check_code = s.trim().parse::<u8>()?;
1761    ///                 checkcode_sender.send(check_code).await?;
1762    ///             }
1763    ///             GrantLoginProgress::WaitingForAuth { verification_uri, continuation_sender } => {
1764    ///                 println!("Please open {verification_uri} to confirm the new login");
1765    ///
1766    ///                 // Once the new login has been confirmed in the browser, we can let the
1767    ///                 // client continue with the process.
1768    ///                 continuation_sender.confirm().await?;
1769    ///             },
1770    ///             GrantLoginProgress::Done => break,
1771    ///         }
1772    ///     }
1773    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1774    /// });
1775    ///
1776    /// // Now run the future to grant the login.
1777    /// grant.await?;
1778    /// task.abort();
1779    ///
1780    /// println!("Successfully granted login");
1781    /// # anyhow::Ok(()) };
1782    /// ```
1783    pub fn generate(self) -> GrantLoginWithGeneratedQrCode {
1784        GrantLoginWithGeneratedQrCode::new(self.client, self.device_creation_timeout)
1785    }
1786}
1787/// A full session for the OAuth 2.0 API.
1788#[derive(Debug, Clone)]
1789pub struct OAuthSession {
1790    /// The client ID obtained after registration.
1791    pub client_id: ClientId,
1792
1793    /// The user session.
1794    pub user: UserSession,
1795}
1796
1797/// A user session for the OAuth 2.0 API.
1798#[derive(Debug, Clone, Serialize, Deserialize)]
1799pub struct UserSession {
1800    /// The Matrix user session info.
1801    #[serde(flatten)]
1802    pub meta: SessionMeta,
1803
1804    /// The tokens used for authentication.
1805    #[serde(flatten)]
1806    pub tokens: SessionTokens,
1807}
1808
1809/// The data necessary to validate a response from the Token endpoint in the
1810/// Authorization Code flow.
1811#[derive(Debug)]
1812struct AuthorizationValidationData {
1813    /// The metadata of the server,
1814    server_metadata: AuthorizationServerMetadata,
1815
1816    /// The device ID used in the scope.
1817    device_id: OwnedDeviceId,
1818
1819    /// The URI where the end-user will be redirected after authorization.
1820    redirect_uri: RedirectUrl,
1821
1822    /// A string to correlate the authorization request to the token request.
1823    pkce_verifier: PkceCodeVerifier,
1824}
1825
1826/// The data returned by the server in the redirect URI after a successful
1827/// authorization.
1828#[derive(Debug, Clone)]
1829enum AuthorizationResponse {
1830    /// A successful response.
1831    Success(AuthorizationCode),
1832
1833    /// An error response.
1834    Error(AuthorizationError),
1835}
1836
1837impl AuthorizationResponse {
1838    /// Deserialize an `AuthorizationResponse` from a [`UrlOrQuery`].
1839    ///
1840    /// Returns an error if the URL or query doesn't have the expected format.
1841    fn parse_url_or_query(url_or_query: &UrlOrQuery) -> Result<Self, RedirectUriQueryParseError> {
1842        let query = url_or_query.query().ok_or(RedirectUriQueryParseError::MissingQuery)?;
1843        Self::parse_query(query)
1844    }
1845
1846    /// Deserialize an `AuthorizationResponse` from the query part of a URI.
1847    ///
1848    /// Returns an error if the query doesn't have the expected format.
1849    fn parse_query(query: &str) -> Result<Self, RedirectUriQueryParseError> {
1850        // For some reason deserializing the enum with `serde(untagged)` doesn't
1851        // work, so let's try both variants separately.
1852        if let Ok(code) = serde_html_form::from_str(query) {
1853            return Ok(AuthorizationResponse::Success(code));
1854        }
1855        if let Ok(error) = serde_html_form::from_str(query) {
1856            return Ok(AuthorizationResponse::Error(error));
1857        }
1858
1859        Err(RedirectUriQueryParseError::UnknownFormat)
1860    }
1861}
1862
1863/// The data returned by the server in the redirect URI after a successful
1864/// authorization.
1865#[derive(Debug, Clone, Deserialize)]
1866struct AuthorizationCode {
1867    /// The code to use to retrieve the access token.
1868    code: String,
1869    /// The unique identifier for this transaction.
1870    state: CsrfToken,
1871}
1872
1873/// The data returned by the server in the redirect URI after an authorization
1874/// error.
1875#[derive(Debug, Clone, Deserialize)]
1876struct AuthorizationError {
1877    /// The error.
1878    #[serde(flatten)]
1879    error: StandardErrorResponse<error::AuthorizationCodeErrorResponseType>,
1880    /// The unique identifier for this transaction.
1881    state: CsrfToken,
1882}
1883
1884fn hash_str(x: &str) -> String {
1885    hex::encode(sha2::Sha256::new().chain_update(x).finalize())
1886}
1887
1888/// Data to register or restore a client.
1889#[derive(Debug, Clone)]
1890pub struct ClientRegistrationData {
1891    /// The metadata to use to register the client when using dynamic client
1892    /// registration.
1893    pub metadata: Raw<ClientMetadata>,
1894
1895    /// Static registrations for servers that don't support dynamic registration
1896    /// but provide a client ID out-of-band.
1897    ///
1898    /// The keys of the map should be the URLs of the homeservers, but keys
1899    /// using `issuer` URLs are also supported.
1900    pub static_registrations: Option<HashMap<Url, ClientId>>,
1901}
1902
1903impl ClientRegistrationData {
1904    /// Construct a [`ClientRegistrationData`] with the given metadata and no
1905    /// static registrations.
1906    pub fn new(metadata: Raw<ClientMetadata>) -> Self {
1907        Self { metadata, static_registrations: None }
1908    }
1909}
1910
1911impl From<Raw<ClientMetadata>> for ClientRegistrationData {
1912    fn from(value: Raw<ClientMetadata>) -> Self {
1913        Self::new(value)
1914    }
1915}