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