Skip to main content

matrix_sdk/authentication/oauth/
mod.rs

1// Copyright 2022 Kévin Commaille
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! High-level OAuth 2.0 API.
16//!
17//! The OAuth 2.0 interactions with the Matrix API are currently a
18//! work-in-progress and are defined by [MSC3861] and its sub-proposals. And
19//! more documentation is available at [areweoidcyet.com].
20//!
21//! This authentication API is available with [`Client::oauth()`].
22//!
23//! # Homeserver support
24//!
25//! After building the client, you can check that the homeserver supports
26//! logging in via OAuth 2.0 when [`OAuth::server_metadata()`] succeeds.
27//!
28//! # Registration
29//!
30//! Clients must register with the homeserver before being able to interact with
31//! an OAuth 2.0 server.
32//!
33//! The registration consists in providing client metadata to the authorization
34//! server, to declare the interactions that the client supports with the
35//! homeserver. This step is important because the client cannot use a feature
36//! that is not declared during registration. In return, the server assigns an
37//! ID and eventually credentials to the client, which will allow to identify
38//! the client when authorization requests are made.
39//!
40//! Note that only public clients are supported by this API, i.e. clients
41//! without credentials.
42//!
43//! The registration step can be done automatically by providing a
44//! [`ClientRegistrationData`] to the login method.
45//!
46//! If the server supports dynamic registration, registration can be performed
47//! manually by using [`OAuth::register_client()`]. If dynamic registration is
48//! not available, the homeserver should document how to obtain a client ID. The
49//! client ID can then be provided with [`OAuth::restore_registered_client()`].
50//!
51//! # Login
52//!
53//! Currently, two login methods are supported by this API.
54//!
55//! ## Login with the Authorization Code flow
56//!
57//! The use of the Authorization Code flow is defined in [MSC2964] and [RFC
58//! 6749][rfc6749-auth-code].
59//!
60//! This method requires to open a URL in the end-user's browser where
61//! they will be able to log into their account in the server's web UI and grant
62//! access to their Matrix account.
63//!
64//! [`OAuth::login()`] constructs an [`OAuthAuthCodeUrlBuilder`] that can be
65//! configured, and then calling [`OAuthAuthCodeUrlBuilder::build()`] will
66//! provide the URL to present to the user in a web browser.
67//!
68//! After authenticating with the server, the user will be redirected to the
69//! provided redirect URI, with a code in the query that will allow to finish
70//! the login process by calling [`OAuth::finish_login()`].
71//!
72//! If the login needs to be cancelled before its completion,
73//! [`OAuth::abort_login()`] should be called to clean up the local data.
74//!
75//! ## Login by scanning a QR Code
76//!
77//! Logging in via a QR code is defined in [MSC4108]. It uses the Device
78//! authorization flow specified in [RFC 8628].
79//!
80//! This method requires to have another logged-in Matrix device that can
81//! display a QR Code.
82//!
83//! This login method is only available if the `e2e-encryption` cargo feature is
84//! enabled. It is not available on WASM.
85//!
86//! After scanning the QR Code, [`OAuth::login_with_qr_code()`] can be called
87//! with the QR Code's data. Then the different steps of the process need to be
88//! followed with [`LoginWithQrCode::subscribe_to_progress()`].
89//!
90//! A successful login using this method will automatically mark the device as
91//! verified and transfer all end-to-end encryption related secrets, like the
92//! private cross-signing keys and the backup key from the existing device to
93//! the new device.
94//!
95//! # Persisting/restoring a session
96//!
97//! The full session to persist can be obtained with [`OAuth::full_session()`].
98//! The different parts can also be retrieved with [`Client::session_meta()`],
99//! [`Client::session_tokens()`] and [`OAuth::client_id()`].
100//!
101//! To restore a previous session, use [`OAuth::restore_session()`].
102//!
103//! # Refresh tokens
104//!
105//! The use of refresh tokens with OAuth 2.0 servers is more common than in the
106//! Matrix specification. For this reason, it is recommended to configure the
107//! client with [`ClientBuilder::handle_refresh_tokens()`], to handle refreshing
108//! tokens automatically.
109//!
110//! Applications should then listen to session tokens changes after logging in
111//! with [`Client::subscribe_to_session_changes()`] to persist them on every
112//! change. If they are not persisted properly, the end-user will need to login
113//! again.
114//!
115//! # Unknown token error
116//!
117//! A request to the Matrix API can return an [`Error`] with an
118//! [`ErrorKind::UnknownToken`].
119//!
120//! The first step is to try to refresh the token with
121//! [`OAuth::refresh_access_token()`]. This step is done automatically if the
122//! client was built with [`ClientBuilder::handle_refresh_tokens()`].
123//!
124//! If refreshing the access token fails, the next step is to try to request a
125//! new login authorization with [`OAuth::login()`], using the device ID from
126//! the session.
127//!
128//! If this fails again, the client should assume to be logged out, and all
129//! local data should be erased.
130//!
131//! # Account management.
132//!
133//! The server might advertise a URL that allows the user to manage their
134//! account. It can be used to replace most of the Matrix APIs requiring
135//! User-Interactive Authentication.
136//!
137//! The account management URL is available as `account_management_uri` on
138//! [`AuthorizationServerMetadata`]. To build a full account management URL that
139//! includes the action that the user wants to perform, use
140//! [`AuthorizationServerMetadata::account_management_url_with_action()`].
141//!
142//! # Logout
143//!
144//! To log the [`Client`] out of the session, simply call [`OAuth::logout()`].
145//!
146//! # Examples
147//!
148//! Most methods have examples, there is also an example CLI application that
149//! supports all the actions described here, in [`examples/oauth_cli`].
150//!
151//! [MSC3861]: https://github.com/matrix-org/matrix-spec-proposals/pull/3861
152//! [areweoidcyet.com]: https://areweoidcyet.com/
153//! [MSC2964]: https://github.com/matrix-org/matrix-spec-proposals/pull/2964
154//! [rfc6749-auth-code]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
155//! [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
156//! [RFC 8628]: https://datatracker.ietf.org/doc/html/rfc8628
157//! [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
158//! [`Error`]: ruma::api::error::Error
159//! [`ErrorKind::UnknownToken`]: ruma::api::error::ErrorKind::UnknownToken
160//! [`examples/oauth_cli`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/examples/oauth_cli
161
162#[cfg(feature = "e2e-encryption")]
163use std::sync::OnceLock;
164#[cfg(feature = "e2e-encryption")]
165use std::time::Duration;
166use std::{borrow::Cow, collections::HashMap, fmt, sync::Arc};
167
168use as_variant::as_variant;
169#[cfg(feature = "e2e-encryption")]
170use error::CrossProcessRefreshLockError;
171use error::{
172    OAuthAuthorizationCodeError, OAuthClientRegistrationError, OAuthDiscoveryError,
173    OAuthTokenRevocationError, RedirectUriQueryParseError,
174};
175#[cfg(feature = "e2e-encryption")]
176use matrix_sdk_base::crypto::types::qr_login::QrCodeData;
177use matrix_sdk_base::{SessionMeta, store::RoomLoadSettings, ttl::TtlValue};
178#[cfg(feature = "e2e-encryption")]
179use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
180use oauth2::{
181    AccessToken, PkceCodeVerifier, RedirectUrl, RefreshToken, RevocationUrl, Scope,
182    StandardErrorResponse, StandardRevocableToken, TokenResponse, TokenUrl,
183    basic::BasicClient as OAuthClient,
184};
185pub use oauth2::{ClientId, CsrfToken};
186use oauth2_reqwest::ReqwestClient;
187use ruma::{
188    DeviceId, OwnedDeviceId,
189    api::client::discovery::get_authorization_server_metadata::{
190        self, v1::AuthorizationServerMetadata,
191    },
192    serde::Raw,
193};
194use serde::{Deserialize, Serialize};
195use sha2::Digest as _;
196use tokio::sync::Mutex;
197use tracing::{debug, error, instrument, trace, warn};
198use url::Url;
199
200mod auth_code_builder;
201#[cfg(feature = "e2e-encryption")]
202mod cross_process;
203pub mod error;
204mod http_client;
205#[cfg(feature = "e2e-encryption")]
206pub mod qrcode;
207pub mod registration;
208#[cfg(all(test, not(target_family = "wasm")))]
209mod tests;
210
211#[cfg(feature = "e2e-encryption")]
212use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
213#[cfg(feature = "e2e-encryption")]
214use self::qrcode::{
215    GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode, LoginWithGeneratedQrCode,
216    LoginWithQrCode,
217};
218pub use self::{
219    auth_code_builder::{OAuthAuthCodeUrlBuilder, OAuthAuthorizationData},
220    error::OAuthError,
221};
222use self::{
223    http_client::OAuthHttpClient,
224    registration::{ClientMetadata, ClientRegistrationResponse, register_client},
225};
226use super::{AuthData, SessionTokens};
227use crate::{
228    Client, RefreshTokenError, Result,
229    client::{SessionChange, caches::CachedValue},
230    executor::spawn,
231    utils::UrlOrQuery,
232};
233
234pub(crate) struct OAuthCtx {
235    /// Lock and state when multiple processes may refresh an OAuth 2.0 session.
236    #[cfg(feature = "e2e-encryption")]
237    cross_process_token_refresh_manager: OnceLock<CrossProcessRefreshManager>,
238
239    /// Deferred cross-process lock initializer.
240    ///
241    /// Note: only required because we're using the crypto store that might not
242    /// be present before reloading a session.
243    #[cfg(feature = "e2e-encryption")]
244    deferred_cross_process_lock_init: Mutex<Option<String>>,
245
246    /// Whether to allow HTTP issuer URLs.
247    insecure_discover: bool,
248}
249
250impl OAuthCtx {
251    pub(crate) fn new(insecure_discover: bool) -> Self {
252        Self {
253            insecure_discover,
254            #[cfg(feature = "e2e-encryption")]
255            cross_process_token_refresh_manager: Default::default(),
256            #[cfg(feature = "e2e-encryption")]
257            deferred_cross_process_lock_init: Default::default(),
258        }
259    }
260}
261
262pub(crate) struct OAuthAuthData {
263    pub(crate) client_id: ClientId,
264    /// The data necessary to validate authorization responses.
265    authorization_data: Mutex<HashMap<CsrfToken, AuthorizationValidationData>>,
266}
267
268#[cfg(not(tarpaulin_include))]
269impl fmt::Debug for OAuthAuthData {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        f.debug_struct("OAuthAuthData").finish_non_exhaustive()
272    }
273}
274
275/// A high-level authentication API to interact with an OAuth 2.0 authorization
276/// server.
277#[derive(Debug, Clone)]
278pub struct OAuth {
279    /// The underlying Matrix API client.
280    client: Client,
281    /// The HTTP client used for making OAuth 2.0 request.
282    http_client: OAuthHttpClient,
283}
284
285impl OAuth {
286    pub(crate) fn new(client: Client) -> Self {
287        let http_client = OAuthHttpClient {
288            inner: ReqwestClient::from(client.inner.http_client.inner.clone()),
289            #[cfg(test)]
290            insecure_rewrite_https_to_http: false,
291        };
292        Self { client, http_client }
293    }
294
295    /// Rewrite HTTPS requests to use HTTP instead.
296    ///
297    /// This is a workaround to bypass some checks that require an HTTPS URL,
298    /// but we can only mock HTTP URLs.
299    #[cfg(test)]
300    pub(crate) fn insecure_rewrite_https_to_http(mut self) -> Self {
301        self.http_client.insecure_rewrite_https_to_http = true;
302        self
303    }
304
305    fn ctx(&self) -> &OAuthCtx {
306        &self.client.auth_ctx().oauth
307    }
308
309    fn http_client(&self) -> &OAuthHttpClient {
310        &self.http_client
311    }
312
313    /// Enable a cross-process store lock on the state store, to coordinate
314    /// refreshes across different processes.
315    #[cfg(feature = "e2e-encryption")]
316    pub async fn enable_cross_process_refresh_lock(
317        &self,
318        lock_value: String,
319    ) -> Result<(), OAuthError> {
320        // FIXME: it must be deferred only because we're using the crypto store and it's
321        // initialized only in `set_or_reload_session`, not if we use a dedicated store.
322        let mut lock = self.ctx().deferred_cross_process_lock_init.lock().await;
323        if lock.is_some() {
324            return Err(CrossProcessRefreshLockError::DuplicatedLock.into());
325        }
326        *lock = Some(lock_value);
327
328        Ok(())
329    }
330
331    /// Performs a deferred cross-process refresh-lock, if needs be, after an
332    /// olm machine has been initialized.
333    ///
334    /// Must be called after [`BaseClient::set_or_reload_session`].
335    #[cfg(feature = "e2e-encryption")]
336    async fn deferred_enable_cross_process_refresh_lock(&self) {
337        let deferred_init_lock = self.ctx().deferred_cross_process_lock_init.lock().await;
338
339        // Don't `take()` the value, so that subsequent calls to
340        // `enable_cross_process_refresh_lock` will keep on failing if we've enabled the
341        // lock at least once.
342        let Some(lock_value) = deferred_init_lock.as_ref() else {
343            return;
344        };
345
346        // FIXME: We shouldn't be using the crypto store for that! see also https://github.com/matrix-org/matrix-rust-sdk/issues/2472
347        let olm_machine_lock = self.client.olm_machine().await;
348        let olm_machine =
349            olm_machine_lock.as_ref().expect("there has to be an olm machine, hopefully?");
350        let store = olm_machine.store();
351        let lock = store.create_store_lock(
352            "oidc_session_refresh_lock".to_owned(),
353            CrossProcessLockConfig::multi_process(lock_value.to_owned()),
354        );
355
356        let manager = CrossProcessRefreshManager::new(store.clone(), lock);
357
358        // This method is guarded with the `deferred_cross_process_lock_init` lock held,
359        // so this `set` can't be an error.
360        let _ = self.ctx().cross_process_token_refresh_manager.set(manager);
361    }
362
363    /// The OAuth 2.0 authentication data.
364    ///
365    /// Returns `None` if the client was not registered or if the registration
366    /// was not restored with [`OAuth::restore_registered_client()`] or
367    /// [`OAuth::restore_session()`].
368    fn data(&self) -> Option<&OAuthAuthData> {
369        let data = self.client.auth_ctx().auth_data.get()?;
370        as_variant!(data, AuthData::OAuth)
371    }
372
373    /// Log in this device using a QR code.
374    ///
375    /// # Arguments
376    ///
377    /// * `registration_data` - The data to restore or register the client with
378    ///   the server. If this is not provided, an error will occur unless
379    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
380    ///   was called previously.
381    #[cfg(feature = "e2e-encryption")]
382    pub fn login_with_qr_code<'a>(
383        &'a self,
384        registration_data: Option<&'a ClientRegistrationData>,
385    ) -> LoginWithQrCodeBuilder<'a> {
386        LoginWithQrCodeBuilder { client: &self.client, registration_data }
387    }
388
389    /// Grant login to a new device using a QR code.
390    #[cfg(feature = "e2e-encryption")]
391    pub fn grant_login_with_qr_code<'a>(&'a self) -> GrantLoginWithQrCodeBuilder<'a> {
392        GrantLoginWithQrCodeBuilder::new(&self.client)
393    }
394
395    /// Restore or register the OAuth 2.0 client for the server with the given
396    /// metadata, with the given optional [`ClientRegistrationData`].
397    ///
398    /// If we already have a client ID, this is a noop.
399    ///
400    /// Returns an error if there was a problem using the registration method.
401    async fn use_registration_data(
402        &self,
403        server_metadata: &AuthorizationServerMetadata,
404        data: Option<&ClientRegistrationData>,
405    ) -> std::result::Result<(), OAuthError> {
406        if self.client_id().is_some() {
407            tracing::info!("OAuth 2.0 is already configured.");
408            return Ok(());
409        }
410
411        let Some(data) = data else {
412            return Err(OAuthError::NotRegistered);
413        };
414
415        if let Some(static_registrations) = &data.static_registrations {
416            let client_id = static_registrations
417                .get(&self.client.homeserver())
418                .or_else(|| static_registrations.get(&server_metadata.issuer));
419
420            if let Some(client_id) = client_id {
421                self.restore_registered_client(client_id.clone());
422                return Ok(());
423            }
424        }
425
426        self.register_client_inner(server_metadata, &data.metadata).await?;
427
428        Ok(())
429    }
430
431    /// Get the cached OAuth 2.0 authorization server metadata of the
432    /// homeserver.
433    ///
434    /// This method will cache the metadata for a while. If the cache is not
435    /// populated it will request the server metadata, like a call to
436    /// [`OAuth::server_metadata()`], and cache the response before returning
437    /// it.
438    ///
439    /// The cache can be forced to be refreshed by calling
440    /// [`OAuth::server_metadata()`] instead.
441    ///
442    /// In most cases during the authentication process, it is better to always
443    /// fetch the metadata from the server. This is provided for convenience for
444    /// cases where the client doesn't want to incur the extra time necessary to
445    /// make the request.
446    ///
447    /// Returns an error if a problem occurred when fetching or validating the
448    /// metadata.
449    pub async fn cached_server_metadata(
450        &self,
451    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
452        let server_metadata_cache = &self.client.inner.caches.server_metadata;
453
454        if let CachedValue::Cached(metadata) = server_metadata_cache.value() {
455            if metadata.has_expired() {
456                debug!("spawning task to refresh OAuth 2.0 server metadata cache");
457
458                let oauth = self.clone();
459                self.client.task_monitor().spawn_finite_task(
460                    "refresh OAuth 2.0 server metadata cache",
461                    async move {
462                        if let Err(error) = oauth.server_metadata().await {
463                            warn!("failed to refresh OAuth 2.0 server metadata cache: {error}");
464                        }
465                    },
466                );
467            }
468
469            return Ok(metadata.into_data());
470        }
471
472        self.server_metadata().await
473    }
474
475    /// Fetch the OAuth 2.0 authorization server metadata of the homeserver.
476    ///
477    /// This will always request the latest server metadata from the homeserver.
478    ///
479    /// To avoid making a request each time, you can use
480    /// [`OAuth::cached_server_metadata()`].
481    ///
482    /// Returns an error if a problem occurred when fetching or validating the
483    /// metadata.
484    pub async fn server_metadata(
485        &self,
486    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
487        let server_metadata_cache = &self.client.inner.caches.server_metadata;
488
489        let mut server_metadata_guard = match server_metadata_cache.refresh_lock.try_lock() {
490            Ok(guard) => guard,
491            Err(_) => {
492                // There is already a refresh in progress, wait for it to finish.
493                let guard = server_metadata_cache.refresh_lock.lock().await;
494
495                // Reuse the data if the request was successful.
496                if matches!(*guard, Ok(()))
497                    && let CachedValue::Cached(value) = server_metadata_cache.value()
498                {
499                    return Ok(value.into_data());
500                }
501
502                // The previous request failed, make another request.
503                guard
504            }
505        };
506
507        match self.server_metadata_inner().await {
508            Ok(metadata) => {
509                // Always refresh the cache.
510                self.client.inner.caches.server_metadata.set_value(TtlValue::new(metadata.clone()));
511                *server_metadata_guard = Ok(());
512                Ok(metadata)
513            }
514            Err(error) => {
515                *server_metadata_guard = Err(());
516                Err(error)
517            }
518        }
519    }
520
521    async fn server_metadata_inner(
522        &self,
523    ) -> Result<AuthorizationServerMetadata, OAuthDiscoveryError> {
524        let response =
525            self.client.send(get_authorization_server_metadata::v1::Request::new()).await.map_err(
526                |error| {
527                    // If the server doesn't support the endpoint.
528                    if error.is_endpoint_not_implemented() {
529                        OAuthDiscoveryError::NotSupported
530                    } else {
531                        error.into()
532                    }
533                },
534            )?;
535
536        let metadata = response.metadata.deserialize()?;
537
538        if self.ctx().insecure_discover {
539            metadata.insecure_validate_urls()?;
540        } else {
541            metadata.validate_urls()?;
542        }
543
544        Ok(metadata)
545    }
546
547    /// The OAuth 2.0 unique identifier of this client obtained after
548    /// registration.
549    ///
550    /// Returns `None` if the client was not registered or if the registration
551    /// was not restored with [`OAuth::restore_registered_client()`] or
552    /// [`OAuth::restore_session()`].
553    pub fn client_id(&self) -> Option<&ClientId> {
554        self.data().map(|data| &data.client_id)
555    }
556
557    /// The OAuth 2.0 user session of this client.
558    ///
559    /// Returns `None` if the client was not logged in.
560    pub fn user_session(&self) -> Option<UserSession> {
561        let meta = self.client.session_meta()?.to_owned();
562        let tokens = self.client.session_tokens()?;
563        Some(UserSession { meta, tokens })
564    }
565
566    /// The full OAuth 2.0 session of this client.
567    ///
568    /// Returns `None` if the client was not logged in with the OAuth 2.0 API.
569    pub fn full_session(&self) -> Option<OAuthSession> {
570        let user = self.user_session()?;
571        let data = self.data()?;
572        Some(OAuthSession { client_id: data.client_id.clone(), user })
573    }
574
575    /// Register a client with the OAuth 2.0 server.
576    ///
577    /// This should be called before any authorization request with an
578    /// authorization server that supports dynamic client registration. If the
579    /// client registered with the server manually, it should use
580    /// [`OAuth::restore_registered_client()`].
581    ///
582    /// Note that this method only supports public clients, i.e. clients without
583    /// a secret.
584    ///
585    /// # Arguments
586    ///
587    /// * `client_metadata` - The serialized client metadata to register.
588    ///
589    /// # Panic
590    ///
591    /// Panics if the authentication data was already set.
592    ///
593    /// # Example
594    ///
595    /// ```no_run
596    /// use matrix_sdk::{Client, ServerName};
597    /// # use matrix_sdk::authentication::oauth::ClientId;
598    /// # use matrix_sdk::authentication::oauth::registration::ClientMetadata;
599    /// # use ruma::serde::Raw;
600    /// # let client_metadata = unimplemented!();
601    /// # fn persist_client_registration (_: url::Url, _: &ClientId) {}
602    /// # _ = async {
603    /// let server_name = ServerName::parse("myhomeserver.org")?;
604    /// let client = Client::builder().server_name(&server_name).build().await?;
605    /// let oauth = client.oauth();
606    ///
607    /// if let Err(error) = oauth.server_metadata().await {
608    ///     if error.is_not_supported() {
609    ///         println!("OAuth 2.0 is not supported");
610    ///     }
611    ///
612    ///     return Err(error.into());
613    /// }
614    ///
615    /// let response = oauth
616    ///     .register_client(&client_metadata)
617    ///     .await?;
618    ///
619    /// println!(
620    ///     "Registered with client_id: {}",
621    ///     response.client_id.as_str()
622    /// );
623    ///
624    /// // The API only supports clients without secrets.
625    /// let client_id = response.client_id;
626    ///
627    /// persist_client_registration(client.homeserver(), &client_id);
628    /// # anyhow::Ok(()) };
629    /// ```
630    pub async fn register_client(
631        &self,
632        client_metadata: &Raw<ClientMetadata>,
633    ) -> Result<ClientRegistrationResponse, OAuthError> {
634        let server_metadata = self.server_metadata().await?;
635        Ok(self.register_client_inner(&server_metadata, client_metadata).await?)
636    }
637
638    async fn register_client_inner(
639        &self,
640        server_metadata: &AuthorizationServerMetadata,
641        client_metadata: &Raw<ClientMetadata>,
642    ) -> Result<ClientRegistrationResponse, OAuthClientRegistrationError> {
643        let registration_endpoint = server_metadata
644            .registration_endpoint
645            .as_ref()
646            .ok_or(OAuthClientRegistrationError::NotSupported)?;
647
648        let registration_response =
649            register_client(self.http_client(), registration_endpoint, client_metadata).await?;
650
651        // The format of the credentials changes according to the client metadata that
652        // was sent. Public clients only get a client ID.
653        self.restore_registered_client(registration_response.client_id.clone());
654
655        Ok(registration_response)
656    }
657
658    /// Set the data of a client that is registered with an OAuth 2.0
659    /// authorization server.
660    ///
661    /// This should be called when logging in with a server that is already
662    /// known by the client.
663    ///
664    /// Note that this method only supports public clients, i.e. clients with
665    /// no credentials.
666    ///
667    /// # Arguments
668    ///
669    /// * `client_id` - The unique identifier to authenticate the client with
670    ///   the server, obtained after registration.
671    ///
672    /// # Panic
673    ///
674    /// Panics if authentication data was already set.
675    pub fn restore_registered_client(&self, client_id: ClientId) {
676        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
677
678        self.client
679            .auth_ctx()
680            .auth_data
681            .set(AuthData::OAuth(data))
682            .expect("Client authentication data was already set");
683    }
684
685    /// Restore a previously logged in session.
686    ///
687    /// This can be used to restore the client to a logged in state, including
688    /// loading the sync state and the encryption keys from the store, if
689    /// one was set up.
690    ///
691    /// # Arguments
692    ///
693    /// * `session` - The session to restore.
694    /// * `room_load_settings` — Specify how many rooms must be restored; use
695    ///   `::default()` if you don't know which value to pick.
696    ///
697    /// # Panic
698    ///
699    /// Panics if authentication data was already set.
700    pub async fn restore_session(
701        &self,
702        session: OAuthSession,
703        room_load_settings: RoomLoadSettings,
704    ) -> Result<()> {
705        let OAuthSession { client_id, user: UserSession { meta, tokens } } = session;
706
707        let data = OAuthAuthData { client_id, authorization_data: Default::default() };
708
709        self.client.auth_ctx().set_session_tokens(tokens.clone());
710        self.client
711            .base_client()
712            .activate(
713                meta,
714                room_load_settings,
715                #[cfg(feature = "e2e-encryption")]
716                None,
717            )
718            .await?;
719        #[cfg(feature = "e2e-encryption")]
720        self.deferred_enable_cross_process_refresh_lock().await;
721
722        self.client
723            .inner
724            .auth_ctx
725            .auth_data
726            .set(AuthData::OAuth(data))
727            .expect("Client authentication data was already set");
728
729        // Initialize the cross-process locking by saving our tokens' hash into the
730        // database, if we've enabled the cross-process lock.
731
732        #[cfg(feature = "e2e-encryption")]
733        if let Some(cross_process_lock) = self.ctx().cross_process_token_refresh_manager.get() {
734            cross_process_lock.restore_session(&tokens).await;
735
736            let mut guard = cross_process_lock
737                .spin_lock()
738                .await
739                .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
740
741            // After we got the lock, it's possible that our session doesn't match the one
742            // read from the database, because of a race: another process has
743            // refreshed the tokens while we were waiting for the lock.
744            //
745            // In that case, if there's a mismatch, we reload the session and update the
746            // hash. Otherwise, we save our hash into the database.
747
748            if guard.hash_mismatch {
749                Box::pin(self.handle_session_hash_mismatch(&mut guard))
750                    .await
751                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
752            } else {
753                guard
754                    .save_in_memory_and_db(&tokens)
755                    .await
756                    .map_err(|err| crate::Error::OAuth(Box::new(err.into())))?;
757                // No need to call the save_session_callback here; it was the
758                // source of the session, so it's already in
759                // sync with what we had.
760            }
761        }
762
763        #[cfg(feature = "e2e-encryption")]
764        self.client.encryption().spawn_initialization_task(None).await;
765
766        Ok(())
767    }
768
769    #[cfg(feature = "e2e-encryption")]
770    async fn handle_session_hash_mismatch(
771        &self,
772        guard: &mut CrossProcessRefreshLockGuard,
773    ) -> Result<(), CrossProcessRefreshLockError> {
774        trace!("Handling hash mismatch.");
775
776        let callback = self
777            .client
778            .auth_ctx()
779            .reload_session_callback
780            .get()
781            .ok_or(CrossProcessRefreshLockError::MissingReloadSession)?;
782
783        match callback(self.client.clone()) {
784            Ok(tokens) => {
785                guard.handle_mismatch(&tokens).await?;
786
787                self.client.auth_ctx().set_session_tokens(tokens.clone());
788                // The app's callback acted as authoritative here, so we're not
789                // saving the data back into the app, as that would have no
790                // effect.
791            }
792            Err(err) => {
793                error!("when reloading OAuth 2.0 session tokens from callback: {err}");
794            }
795        }
796
797        Ok(())
798    }
799
800    /// The scopes to request for logging in and the corresponding device ID.
801    fn login_scopes(
802        device_id: Option<OwnedDeviceId>,
803        additional_scopes: Option<Vec<Scope>>,
804    ) -> (Vec<Scope>, OwnedDeviceId) {
805        /// Scope to grand full access to the client-server API.
806        const SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS: &str =
807            "urn:matrix:org.matrix.msc2967.client:api:*";
808        /// Prefix of the scope to bind a device ID to an access token.
809        const SCOPE_MATRIX_DEVICE_ID_PREFIX: &str = "urn:matrix:org.matrix.msc2967.client:device:";
810
811        // Generate the device ID if it is not provided.
812        let device_id = device_id.unwrap_or_else(DeviceId::new);
813
814        let mut scopes = vec![
815            Scope::new(SCOPE_MATRIX_CLIENT_SERVER_API_FULL_ACCESS.to_owned()),
816            Scope::new(format!("{SCOPE_MATRIX_DEVICE_ID_PREFIX}{device_id}")),
817        ];
818
819        if let Some(extra_scopes) = additional_scopes {
820            scopes.extend(extra_scopes);
821        }
822
823        (scopes, device_id)
824    }
825
826    /// Log in via OAuth 2.0 with the Authorization Code flow.
827    ///
828    /// This method requires to open a URL in the end-user's browser where they
829    /// will be able to log into their account in the server's web UI and grant
830    /// access to their Matrix account.
831    ///
832    /// The [`OAuthAuthCodeUrlBuilder`] that is returned allows to customize a
833    /// few settings before calling `.build()` to obtain the URL to open in the
834    /// browser of the end-user.
835    ///
836    /// [`OAuth::finish_login()`] must be called once the user has been
837    /// redirected to the `redirect_uri`. [`OAuth::abort_login()`] should be
838    /// called instead if the authorization should be aborted before completion.
839    ///
840    /// # Arguments
841    ///
842    /// * `redirect_uri` - The URI where the end user will be redirected after
843    ///   authorizing the login. It must be one of the redirect URIs sent in the
844    ///   client metadata during registration.
845    ///
846    /// * `device_id` - The unique ID that will be associated with the session.
847    ///   If not set, a random one will be generated. It can be an existing
848    ///   device ID from a previous login call. Note that this should be done
849    ///   only if the client also holds the corresponding encryption keys.
850    ///
851    /// * `registration_data` - The data to restore or register the client with
852    ///   the server. If this is not provided, an error will occur unless
853    ///   [`OAuth::register_client()`] or [`OAuth::restore_registered_client()`]
854    ///   was called previously.
855    ///
856    /// * `additional_scopes` - Additional scopes to request from the
857    ///   authorization server, e.g. "urn:matrix:client:com.example.msc9999.foo".
858    ///   The scopes for API access and the device ID according to the
859    ///   [specification](https://spec.matrix.org/v1.15/client-server-api/#allocated-scope-tokens)
860    ///   are always requested.
861    ///
862    /// # Example
863    ///
864    /// ```no_run
865    /// use matrix_sdk::{
866    ///     authentication::oauth::registration::ClientMetadata,
867    ///     ruma::serde::Raw,
868    /// };
869    /// use url::Url;
870    /// # use matrix_sdk::Client;
871    /// # let client: Client = unimplemented!();
872    /// # let redirect_uri = unimplemented!();
873    /// # async fn open_uri_and_wait_for_redirect(uri: Url) -> Url { unimplemented!() };
874    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() };
875    /// # _ = async {
876    /// let oauth = client.oauth();
877    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
878    /// let registration_data = client_metadata.into();
879    ///
880    /// let auth_data = oauth.login(redirect_uri, None, Some(registration_data), None)
881    ///                      .build()
882    ///                      .await?;
883    ///
884    /// // Open auth_data.url and wait for response at the redirect URI.
885    /// let redirected_to_uri: Url = open_uri_and_wait_for_redirect(auth_data.url).await;
886    ///
887    /// oauth.finish_login(redirected_to_uri.into()).await?;
888    ///
889    /// // The session tokens can be persisted from the
890    /// // `OAuth::full_session()` method.
891    ///
892    /// // You can now make requests to the Matrix API.
893    /// let _me = client.whoami().await?;
894    /// # anyhow::Ok(()) }
895    /// ```
896    pub fn login(
897        &self,
898        redirect_uri: Url,
899        device_id: Option<OwnedDeviceId>,
900        registration_data: Option<ClientRegistrationData>,
901        additional_scopes: Option<Vec<Scope>>,
902    ) -> OAuthAuthCodeUrlBuilder {
903        let (scopes, device_id) = Self::login_scopes(device_id, additional_scopes);
904
905        OAuthAuthCodeUrlBuilder::new(
906            self.clone(),
907            scopes.to_vec(),
908            device_id,
909            redirect_uri,
910            registration_data,
911        )
912    }
913
914    /// Finish the login process.
915    ///
916    /// This method should be called after the URL returned by
917    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
918    /// been redirected to the redirect URI after completing the authorization.
919    ///
920    /// If the authorization needs to be cancelled before its completion,
921    /// [`OAuth::abort_login()`] should be used instead to clean up the local
922    /// data.
923    ///
924    /// # Arguments
925    ///
926    /// * `url_or_query` - The URI where the user was redirected, or just its
927    ///   query part.
928    ///
929    /// Returns an error if the authorization failed, if a request fails, or if
930    /// the client was already logged in with a different session.
931    pub async fn finish_login(&self, url_or_query: UrlOrQuery) -> Result<()> {
932        let response = AuthorizationResponse::parse_url_or_query(&url_or_query)
933            .map_err(|error| OAuthError::from(OAuthAuthorizationCodeError::from(error)))?;
934
935        let auth_code = match response {
936            AuthorizationResponse::Success(code) => code,
937            AuthorizationResponse::Error(error) => {
938                self.abort_login(&error.state).await;
939                return Err(OAuthError::from(OAuthAuthorizationCodeError::from(error.error)).into());
940            }
941        };
942
943        let device_id = self.finish_authorization(auth_code).await?;
944        self.load_session(device_id).await
945    }
946
947    /// Load the session after login.
948    ///
949    /// Returns an error if the request to get the user ID fails, or if the
950    /// client was already logged in with a different session.
951    pub(crate) async fn load_session(&self, device_id: OwnedDeviceId) -> Result<()> {
952        // Get the user ID.
953        let whoami_res = self.client.whoami().await.map_err(crate::Error::from)?;
954
955        let new_session = SessionMeta { user_id: whoami_res.user_id, device_id };
956
957        if let Some(current_session) = self.client.session_meta() {
958            if new_session != *current_session {
959                return Err(OAuthError::SessionMismatch.into());
960            }
961        } else {
962            self.client
963                .base_client()
964                .activate(
965                    new_session,
966                    RoomLoadSettings::default(),
967                    #[cfg(feature = "e2e-encryption")]
968                    None,
969                )
970                .await?;
971            // At this point the Olm machine has been set up.
972
973            // Enable the cross-process lock for refreshes, if needs be.
974            #[cfg(feature = "e2e-encryption")]
975            self.enable_cross_process_lock().await.map_err(OAuthError::from)?;
976
977            #[cfg(feature = "e2e-encryption")]
978            self.client.encryption().spawn_initialization_task(None).await;
979        }
980
981        Ok(())
982    }
983
984    #[cfg(feature = "e2e-encryption")]
985    pub(crate) async fn enable_cross_process_lock(
986        &self,
987    ) -> Result<(), CrossProcessRefreshLockError> {
988        // Enable the cross-process lock for refreshes, if needs be.
989        self.deferred_enable_cross_process_refresh_lock().await;
990
991        if let Some(cross_process_manager) = self.ctx().cross_process_token_refresh_manager.get()
992            && let Some(tokens) = self.client.session_tokens()
993        {
994            let mut cross_process_guard = cross_process_manager.spin_lock().await?;
995
996            if cross_process_guard.hash_mismatch {
997                // At this point, we're finishing a login while another process had written
998                // something in the database. It's likely the information in the database is
999                // just outdated and wasn't properly updated, but display a warning, just in
1000                // case this happens frequently.
1001                warn!("unexpected cross-process hash mismatch when finishing login (see comment)");
1002            }
1003
1004            cross_process_guard.save_in_memory_and_db(&tokens).await?;
1005        }
1006
1007        Ok(())
1008    }
1009
1010    /// Finish the authorization process.
1011    ///
1012    /// This method should be called after the URL returned by
1013    /// [`OAuthAuthCodeUrlBuilder::build()`] has been presented and the user has
1014    /// been redirected to the redirect URI after a successful authorization.
1015    ///
1016    /// # Arguments
1017    ///
1018    /// * `auth_code` - The response received as part of the redirect URI when
1019    ///   the authorization was successful.
1020    ///
1021    /// Returns the device ID used in the authorized scope if it succeeds.
1022    /// Returns an error if a request fails.
1023    async fn finish_authorization(
1024        &self,
1025        auth_code: AuthorizationCode,
1026    ) -> Result<OwnedDeviceId, OAuthError> {
1027        let data = self.data().ok_or(OAuthError::NotAuthenticated)?;
1028        let client_id = data.client_id.clone();
1029
1030        let validation_data = data
1031            .authorization_data
1032            .lock()
1033            .await
1034            .remove(&auth_code.state)
1035            .ok_or(OAuthAuthorizationCodeError::InvalidState)?;
1036
1037        let token_uri = TokenUrl::from_url(validation_data.server_metadata.token_endpoint.clone());
1038
1039        let response = OAuthClient::new(client_id)
1040            .set_token_uri(token_uri)
1041            .exchange_code(oauth2::AuthorizationCode::new(auth_code.code))
1042            .set_pkce_verifier(validation_data.pkce_verifier)
1043            .set_redirect_uri(Cow::Owned(validation_data.redirect_uri))
1044            .request_async(self.http_client())
1045            .await
1046            .map_err(OAuthAuthorizationCodeError::RequestToken)?;
1047
1048        self.client.auth_ctx().set_session_tokens(SessionTokens {
1049            access_token: response.access_token().secret().clone(),
1050            refresh_token: response.refresh_token().map(RefreshToken::secret).cloned(),
1051        });
1052
1053        Ok(validation_data.device_id)
1054    }
1055
1056    /// Abort the login process.
1057    ///
1058    /// This method should be called if a login should be aborted before it is
1059    /// completed.
1060    ///
1061    /// If the login has been completed, [`OAuth::finish_login()`] should be
1062    /// used instead.
1063    ///
1064    /// # Arguments
1065    ///
1066    /// * `state` - The state provided in [`OAuthAuthorizationData`] after
1067    ///   building the authorization URL.
1068    pub async fn abort_login(&self, state: &CsrfToken) {
1069        if let Some(data) = self.data() {
1070            data.authorization_data.lock().await.remove(state);
1071        }
1072    }
1073
1074    /// Request codes from the authorization server for logging in with another
1075    /// device.
1076    #[cfg(feature = "e2e-encryption")]
1077    async fn request_device_authorization(
1078        &self,
1079        server_metadata: &AuthorizationServerMetadata,
1080        device_id: Option<OwnedDeviceId>,
1081    ) -> Result<oauth2::StandardDeviceAuthorizationResponse, qrcode::DeviceAuthorizationOAuthError>
1082    {
1083        let (scopes, _) = Self::login_scopes(device_id, None);
1084
1085        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1086
1087        let device_authorization_url = server_metadata
1088            .device_authorization_endpoint
1089            .clone()
1090            .map(oauth2::DeviceAuthorizationUrl::from_url)
1091            .ok_or(qrcode::DeviceAuthorizationOAuthError::NoDeviceAuthorizationEndpoint)?;
1092
1093        let response = OAuthClient::new(client_id)
1094            .set_device_authorization_url(device_authorization_url)
1095            .exchange_device_code()
1096            .add_scopes(scopes)
1097            .request_async(self.http_client())
1098            .await?;
1099
1100        Ok(response)
1101    }
1102
1103    /// Exchange the device code against an access token.
1104    #[cfg(feature = "e2e-encryption")]
1105    async fn exchange_device_code(
1106        &self,
1107        server_metadata: &AuthorizationServerMetadata,
1108        device_authorization_response: &oauth2::StandardDeviceAuthorizationResponse,
1109    ) -> Result<(), qrcode::DeviceAuthorizationOAuthError> {
1110        use oauth2::TokenResponse;
1111
1112        let client_id = self.client_id().ok_or(OAuthError::NotRegistered)?.clone();
1113
1114        let token_uri = TokenUrl::from_url(server_metadata.token_endpoint.clone());
1115
1116        let response = OAuthClient::new(client_id)
1117            .set_token_uri(token_uri)
1118            .exchange_device_access_token(device_authorization_response)
1119            .request_async(self.http_client(), matrix_sdk_common::sleep::sleep, None)
1120            .await?;
1121
1122        self.client.auth_ctx().set_session_tokens(SessionTokens {
1123            access_token: response.access_token().secret().to_owned(),
1124            refresh_token: response.refresh_token().map(|t| t.secret().to_owned()),
1125        });
1126
1127        Ok(())
1128    }
1129
1130    async fn refresh_access_token_inner(
1131        self,
1132        refresh_token: String,
1133        token_endpoint: Url,
1134        client_id: ClientId,
1135        #[cfg(feature = "e2e-encryption")] cross_process_lock: Option<CrossProcessRefreshLockGuard>,
1136    ) -> Result<(), OAuthError> {
1137        trace!(
1138            "Token refresh: attempting to refresh with refresh_token {}",
1139            hash_str(&refresh_token)
1140        );
1141
1142        let token = RefreshToken::new(refresh_token.clone());
1143        let token_uri = TokenUrl::from_url(token_endpoint);
1144
1145        let response = OAuthClient::new(client_id)
1146            .set_token_uri(token_uri)
1147            .exchange_refresh_token(&token)
1148            .request_async(self.http_client())
1149            .await
1150            .map_err(OAuthError::RefreshToken)?;
1151
1152        let new_access_token = response.access_token().secret().clone();
1153        let new_refresh_token = response.refresh_token().map(RefreshToken::secret).cloned();
1154
1155        trace!(
1156            "Token refresh: new refresh_token: {} / access_token: {}",
1157            new_refresh_token.as_deref().map(hash_str).unwrap_or_else(|| "<none>".to_owned()),
1158            hash_str(&new_access_token)
1159        );
1160
1161        let tokens = SessionTokens {
1162            access_token: new_access_token,
1163            refresh_token: new_refresh_token.or(Some(refresh_token)),
1164        };
1165
1166        #[cfg(feature = "e2e-encryption")]
1167        let tokens_clone = tokens.clone();
1168
1169        self.client.auth_ctx().set_session_tokens(tokens);
1170
1171        // Call the save_session_callback if set, while the optional lock is being held.
1172        if let Some(save_session_callback) = self.client.auth_ctx().save_session_callback.get() {
1173            // Satisfies the save_session_callback invariant: set_session_tokens has
1174            // been called just above.
1175            tracing::debug!("call save_session_callback");
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        tracing::debug!("broadcast session changed");
1187        _ = self.client.auth_ctx().session_change_sender.send(SessionChange::TokensRefreshed);
1188
1189        Ok(())
1190    }
1191
1192    /// Refresh the access token.
1193    ///
1194    /// This should be called when the access token has expired. It should not
1195    /// be needed to call this manually if the [`Client`] was constructed with
1196    /// [`ClientBuilder::handle_refresh_tokens()`].
1197    ///
1198    /// This method is protected behind a lock, so calling this method several
1199    /// times at once will only call the endpoint once and all subsequent calls
1200    /// will wait for the result of the first call.
1201    ///
1202    /// [`ClientBuilder::handle_refresh_tokens()`]: crate::ClientBuilder::handle_refresh_tokens()
1203    #[instrument(skip_all)]
1204    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1205        macro_rules! fail {
1206            ($lock:expr, $err:expr) => {
1207                let error = $err;
1208                *$lock = Err(error.clone());
1209                return Err(error);
1210            };
1211        }
1212
1213        let client = &self.client;
1214
1215        let refresh_status_lock = client.auth_ctx().refresh_token_lock.clone().try_lock_owned();
1216
1217        let Ok(mut refresh_status_guard) = refresh_status_lock else {
1218            debug!("another refresh is happening, waiting for result.");
1219            // There's already a request to refresh happening in the same process. Wait for
1220            // it to finish.
1221            let res = client.auth_ctx().refresh_token_lock.lock().await.clone();
1222            debug!("other refresh is a {}", if res.is_ok() { "success" } else { "failure " });
1223            return res;
1224        };
1225
1226        debug!("no other refresh happening in background, starting.");
1227
1228        #[cfg(feature = "e2e-encryption")]
1229        let cross_process_guard =
1230            if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1231                let mut cross_process_guard = match manager
1232                    .spin_lock()
1233                    .await
1234                    .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))
1235                {
1236                    Ok(guard) => guard,
1237                    Err(err) => {
1238                        warn!("couldn't acquire cross-process lock (timeout)");
1239                        fail!(refresh_status_guard, err);
1240                    }
1241                };
1242
1243                if cross_process_guard.hash_mismatch {
1244                    Box::pin(self.handle_session_hash_mismatch(&mut cross_process_guard))
1245                        .await
1246                        .map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))?;
1247                    // Optimistic exit: assume that the underlying process did update fast enough.
1248                    // In the worst case, we'll do another refresh Soon™.
1249                    tracing::info!("other process handled refresh for us, assuming success");
1250                    *refresh_status_guard = Ok(());
1251                    return Ok(());
1252                }
1253
1254                Some(cross_process_guard)
1255            } else {
1256                None
1257            };
1258
1259        let Some(session_tokens) = self.client.session_tokens() else {
1260            warn!("invalid state: missing session tokens");
1261            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1262        };
1263
1264        let Some(refresh_token) = session_tokens.refresh_token else {
1265            warn!("invalid state: missing session tokens");
1266            fail!(refresh_status_guard, RefreshTokenError::RefreshTokenRequired);
1267        };
1268
1269        let server_metadata = match self.server_metadata().await {
1270            Ok(metadata) => metadata,
1271            Err(err) => {
1272                warn!("couldn't get authorization server metadata: {err:?}");
1273                fail!(refresh_status_guard, RefreshTokenError::OAuth(Arc::new(err.into())));
1274            }
1275        };
1276
1277        let Some(client_id) = self.client_id().cloned() else {
1278            warn!("invalid state: missing client ID");
1279            fail!(
1280                refresh_status_guard,
1281                RefreshTokenError::OAuth(Arc::new(OAuthError::NotAuthenticated))
1282            );
1283        };
1284
1285        // Do not interrupt refresh access token requests and processing, by detaching
1286        // the request sending and response processing.
1287        // Make sure to keep the `refresh_status_guard` during the entire processing.
1288
1289        let this = self.clone();
1290
1291        spawn(async move {
1292            match this
1293                .refresh_access_token_inner(
1294                    refresh_token,
1295                    server_metadata.token_endpoint,
1296                    client_id,
1297                    #[cfg(feature = "e2e-encryption")]
1298                    cross_process_guard,
1299                )
1300                .await
1301            {
1302                Ok(()) => {
1303                    debug!("success refreshing a token");
1304                    *refresh_status_guard = Ok(());
1305                    Ok(())
1306                }
1307
1308                Err(err) => {
1309                    let err = RefreshTokenError::OAuth(Arc::new(err));
1310                    warn!("error refreshing an OAuth 2.0 token: {err}");
1311                    fail!(refresh_status_guard, err);
1312                }
1313            }
1314        })
1315        .await
1316        .expect("joining")
1317    }
1318
1319    /// Log out from the currently authenticated session.
1320    pub async fn logout(&self) -> Result<(), OAuthError> {
1321        let client_id = self.client_id().ok_or(OAuthError::NotAuthenticated)?.clone();
1322
1323        let server_metadata = self.server_metadata().await?;
1324        let revocation_url = RevocationUrl::from_url(server_metadata.revocation_endpoint);
1325
1326        let tokens = self.client.session_tokens().ok_or(OAuthError::NotAuthenticated)?;
1327
1328        // Revoke the access token, it should revoke both tokens.
1329        OAuthClient::new(client_id)
1330            .set_revocation_url(revocation_url)
1331            .revoke_token(StandardRevocableToken::AccessToken(AccessToken::new(
1332                tokens.access_token,
1333            )))
1334            .map_err(OAuthTokenRevocationError::Url)?
1335            .request_async(self.http_client())
1336            .await
1337            .map_err(OAuthTokenRevocationError::Revoke)?;
1338
1339        #[cfg(feature = "e2e-encryption")]
1340        if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
1341            manager.on_logout().await?;
1342        }
1343
1344        Ok(())
1345    }
1346}
1347
1348/// Builder for QR login futures.
1349#[cfg(feature = "e2e-encryption")]
1350#[derive(Debug)]
1351pub struct LoginWithQrCodeBuilder<'a> {
1352    /// The underlying Matrix API client.
1353    client: &'a Client,
1354
1355    /// The data to restore or register the client with the server.
1356    registration_data: Option<&'a ClientRegistrationData>,
1357}
1358
1359#[cfg(feature = "e2e-encryption")]
1360impl<'a> LoginWithQrCodeBuilder<'a> {
1361    /// This method allows you to log in with a scanned QR code.
1362    ///
1363    /// The existing device needs to display the QR code which this device can
1364    /// scan and call this method to log in.
1365    ///
1366    /// A successful login using this method will automatically mark the device
1367    /// as verified and transfer all end-to-end encryption related secrets, like
1368    /// the private cross-signing keys and the backup key from the existing
1369    /// device to the new device.
1370    ///
1371    /// For the reverse flow where this device generates the QR code for the
1372    /// existing device to scan, use [`LoginWithQrCodeBuilder::generate`].
1373    ///
1374    /// # Arguments
1375    ///
1376    /// * `data` - The data scanned from a QR code.
1377    ///
1378    /// # Example
1379    ///
1380    /// ```no_run
1381    /// use anyhow::bail;
1382    /// use futures_util::StreamExt;
1383    /// use matrix_sdk::{
1384    ///     authentication::oauth::{
1385    ///         registration::ClientMetadata,
1386    ///         qrcode::{LoginProgress, Msc4108IntentData, QrCodeData, QrCodeIntentData, QrProgress},
1387    ///     },
1388    ///     ruma::serde::Raw,
1389    ///     Client,
1390    /// };
1391    /// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
1392    /// # _ = async {
1393    /// # let bytes = unimplemented!();
1394    /// // You'll need to use a different library to scan and extract the raw bytes from the QR
1395    /// // code.
1396    /// let qr_code_data = QrCodeData::from_bytes(bytes)?;
1397    ///
1398    /// // Fetch the homeserver out of the parsed QR code data.
1399    /// let QrCodeIntentData::Msc4108 { data: Msc4108IntentData::Reciprocate { server_name }, ..} = qr_code_data.intent_data() else {
1400    ///     bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
1401    /// };
1402    ///
1403    /// // Build the client as usual.
1404    /// let client = Client::builder()
1405    ///     .server_name_or_homeserver_url(server_name)
1406    ///     .handle_refresh_tokens()
1407    ///     .build()
1408    ///     .await?;
1409    ///
1410    /// let oauth = client.oauth();
1411    /// let client_metadata: Raw<ClientMetadata> = client_metadata();
1412    /// let registration_data = client_metadata.into();
1413    ///
1414    /// // Subscribing to the progress is necessary since we need to input the check
1415    /// // code on the existing device.
1416    /// let login = oauth.login_with_qr_code(Some(&registration_data)).scan(&qr_code_data);
1417    /// let mut progress = login.subscribe_to_progress();
1418    ///
1419    /// // Create a task which will show us the progress and tell us the check
1420    /// // code to input in the existing device.
1421    /// let task = tokio::spawn(async move {
1422    ///     while let Some(state) = progress.next().await {
1423    ///         match state {
1424    ///             LoginProgress::Starting | LoginProgress::SyncingSecrets => (),
1425    ///             LoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1426    ///                 println!("Please enter the following code into the other device {check_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},
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, 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, continuation_sender } => {
1619    ///                 println!("Please open {verification_uri} to confirm the new login");
1620    ///
1621    ///                 // Once the new login has been confirmed in the browser, we can let the
1622    ///                 // client continue with the process.
1623    ///                 continuation_sender.confirm().await?;
1624    ///             },
1625    ///             GrantLoginProgress::Done => break,
1626    ///         }
1627    ///     }
1628    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1629    /// });
1630    ///
1631    /// // Now run the future to grant the login.
1632    /// grant.await?;
1633    /// task.abort();
1634    ///
1635    /// println!("Successfully granted login");
1636    /// # anyhow::Ok(()) };
1637    /// ```
1638    pub fn scan(self, data: &'a QrCodeData) -> GrantLoginWithScannedQrCode<'a> {
1639        GrantLoginWithScannedQrCode::new(self.client, data, self.device_creation_timeout)
1640    }
1641
1642    /// This method allows you to grant login to a new device by generating a QR
1643    /// code on this device to be scanned by the new device.
1644    ///
1645    /// This device needs to call this method to generate and display the
1646    /// QR code which the new device can scan to initiate the grant process.
1647    ///
1648    /// A successful login grant using this method will automatically mark the
1649    /// new device as verified and transfer all end-to-end encryption
1650    /// related secrets, like the private cross-signing keys and the backup
1651    /// key from this device device to the new device.
1652    ///
1653    /// For the reverse flow where the new device generates the QR code
1654    /// for this device to scan, use [`GrantLoginWithQrCodeBuilder::scan`].
1655    ///
1656    /// # Example
1657    ///
1658    /// ```no_run
1659    /// use anyhow::bail;
1660    /// use futures_util::StreamExt;
1661    /// use matrix_sdk::{
1662    ///     Client, authentication::oauth::{
1663    ///         qrcode::{GeneratedQrProgress, GrantLoginProgress}
1664    ///     }
1665    /// };
1666    /// use std::{error::Error, io::stdin};
1667    /// # _ = async {
1668    /// // Build the client as usual.
1669    /// let client = Client::builder()
1670    ///     .server_name_or_homeserver_url("matrix.org")
1671    ///     .handle_refresh_tokens()
1672    ///     .build()
1673    ///     .await?;
1674    ///
1675    /// let oauth = client.oauth();
1676    ///
1677    /// // Subscribing to the progress is necessary since we need to capture the
1678    /// // QR code, feed the checkcode back in and obtain the verification URL to
1679    /// // open it in a browser so the user can consent to the new login.
1680    /// let mut grant = oauth.grant_login_with_qr_code().generate();
1681    /// let mut progress = grant.subscribe_to_progress();
1682    ///
1683    /// // Create a task which will show us the progress and allows us to receive
1684    /// // and feed back data.
1685    /// let task = tokio::spawn(async move {
1686    ///     while let Some(state) = progress.next().await {
1687    ///         match state {
1688    ///             GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
1689    ///             GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(qr_code_data)) => {
1690    ///                 println!("Please scan the QR code on your other device: {:?}", qr_code_data);
1691    ///             }
1692    ///             GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(checkcode_sender)) => {
1693    ///                 println!("Please enter the code displayed on your other device");
1694    ///                 let mut s = String::new();
1695    ///                 stdin().read_line(&mut s)?;
1696    ///                 let check_code = s.trim().parse::<u8>()?;
1697    ///                 checkcode_sender.send(check_code).await?;
1698    ///             }
1699    ///             GrantLoginProgress::WaitingForAuth { verification_uri, continuation_sender } => {
1700    ///                 println!("Please open {verification_uri} to confirm the new login");
1701    ///
1702    ///                 // Once the new login has been confirmed in the browser, we can let the
1703    ///                 // client continue with the process.
1704    ///                 continuation_sender.confirm().await?;
1705    ///             },
1706    ///             GrantLoginProgress::Done => break,
1707    ///         }
1708    ///     }
1709    ///     Ok::<(), Box<dyn Error + Send + Sync>>(())
1710    /// });
1711    ///
1712    /// // Now run the future to grant the login.
1713    /// grant.await?;
1714    /// task.abort();
1715    ///
1716    /// println!("Successfully granted login");
1717    /// # anyhow::Ok(()) };
1718    /// ```
1719    pub fn generate(self) -> GrantLoginWithGeneratedQrCode<'a> {
1720        GrantLoginWithGeneratedQrCode::new(self.client, self.device_creation_timeout)
1721    }
1722}
1723/// A full session for the OAuth 2.0 API.
1724#[derive(Debug, Clone)]
1725pub struct OAuthSession {
1726    /// The client ID obtained after registration.
1727    pub client_id: ClientId,
1728
1729    /// The user session.
1730    pub user: UserSession,
1731}
1732
1733/// A user session for the OAuth 2.0 API.
1734#[derive(Debug, Clone, Serialize, Deserialize)]
1735pub struct UserSession {
1736    /// The Matrix user session info.
1737    #[serde(flatten)]
1738    pub meta: SessionMeta,
1739
1740    /// The tokens used for authentication.
1741    #[serde(flatten)]
1742    pub tokens: SessionTokens,
1743}
1744
1745/// The data necessary to validate a response from the Token endpoint in the
1746/// Authorization Code flow.
1747#[derive(Debug)]
1748struct AuthorizationValidationData {
1749    /// The metadata of the server,
1750    server_metadata: AuthorizationServerMetadata,
1751
1752    /// The device ID used in the scope.
1753    device_id: OwnedDeviceId,
1754
1755    /// The URI where the end-user will be redirected after authorization.
1756    redirect_uri: RedirectUrl,
1757
1758    /// A string to correlate the authorization request to the token request.
1759    pkce_verifier: PkceCodeVerifier,
1760}
1761
1762/// The data returned by the server in the redirect URI after a successful
1763/// authorization.
1764#[derive(Debug, Clone)]
1765enum AuthorizationResponse {
1766    /// A successful response.
1767    Success(AuthorizationCode),
1768
1769    /// An error response.
1770    Error(AuthorizationError),
1771}
1772
1773impl AuthorizationResponse {
1774    /// Deserialize an `AuthorizationResponse` from a [`UrlOrQuery`].
1775    ///
1776    /// Returns an error if the URL or query doesn't have the expected format.
1777    fn parse_url_or_query(url_or_query: &UrlOrQuery) -> Result<Self, RedirectUriQueryParseError> {
1778        let query = url_or_query.query().ok_or(RedirectUriQueryParseError::MissingQuery)?;
1779        Self::parse_query(query)
1780    }
1781
1782    /// Deserialize an `AuthorizationResponse` from the query part of a URI.
1783    ///
1784    /// Returns an error if the query doesn't have the expected format.
1785    fn parse_query(query: &str) -> Result<Self, RedirectUriQueryParseError> {
1786        // For some reason deserializing the enum with `serde(untagged)` doesn't work,
1787        // so let's try both variants separately.
1788        if let Ok(code) = serde_html_form::from_str(query) {
1789            return Ok(AuthorizationResponse::Success(code));
1790        }
1791        if let Ok(error) = serde_html_form::from_str(query) {
1792            return Ok(AuthorizationResponse::Error(error));
1793        }
1794
1795        Err(RedirectUriQueryParseError::UnknownFormat)
1796    }
1797}
1798
1799/// The data returned by the server in the redirect URI after a successful
1800/// authorization.
1801#[derive(Debug, Clone, Deserialize)]
1802struct AuthorizationCode {
1803    /// The code to use to retrieve the access token.
1804    code: String,
1805    /// The unique identifier for this transaction.
1806    state: CsrfToken,
1807}
1808
1809/// The data returned by the server in the redirect URI after an authorization
1810/// error.
1811#[derive(Debug, Clone, Deserialize)]
1812struct AuthorizationError {
1813    /// The error.
1814    #[serde(flatten)]
1815    error: StandardErrorResponse<error::AuthorizationCodeErrorResponseType>,
1816    /// The unique identifier for this transaction.
1817    state: CsrfToken,
1818}
1819
1820fn hash_str(x: &str) -> String {
1821    hex::encode(sha2::Sha256::new().chain_update(x).finalize())
1822}
1823
1824/// Data to register or restore a client.
1825#[derive(Debug, Clone)]
1826pub struct ClientRegistrationData {
1827    /// The metadata to use to register the client when using dynamic client
1828    /// registration.
1829    pub metadata: Raw<ClientMetadata>,
1830
1831    /// Static registrations for servers that don't support dynamic registration
1832    /// but provide a client ID out-of-band.
1833    ///
1834    /// The keys of the map should be the URLs of the homeservers, but keys
1835    /// using `issuer` URLs are also supported.
1836    pub static_registrations: Option<HashMap<Url, ClientId>>,
1837}
1838
1839impl ClientRegistrationData {
1840    /// Construct a [`ClientRegistrationData`] with the given metadata and no
1841    /// static registrations.
1842    pub fn new(metadata: Raw<ClientMetadata>) -> Self {
1843        Self { metadata, static_registrations: None }
1844    }
1845}
1846
1847impl From<Raw<ClientMetadata>> for ClientRegistrationData {
1848    fn from(value: Raw<ClientMetadata>) -> Self {
1849        Self::new(value)
1850    }
1851}