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