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