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