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