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