bitwarden_auth/token_management/
middleware.rs

1//! Shared utilities for token renewal.
2
3use bitwarden_api_api::apis::AuthRequired;
4use bitwarden_core::auth::login::LoginError;
5use reqwest_middleware::Middleware;
6
7pub(crate) const TOKEN_RENEW_MARGIN_SECONDS: i64 = 5 * 60;
8
9/// A wrapper that implements [reqwest_middleware::Middleware] by delegating to a [MiddlewareExt]
10/// for token retrieval. We can't implement [Middleware] directly on [MiddlewareExt] because
11/// [Middleware] is defined in an external crate, so we use this wrapper to bridge between them.
12pub(crate) struct MiddlewareWrapper<T>(pub(crate) T);
13
14/// Trait used to share over the token attaching middleware. This is implemented by the token
15/// management structs, leaving them responsible for handling token retrieval and renewal logic. The
16/// middleware simply calls [MiddlewareExt::get_token] to get the current token (renewing if
17/// necessary) and attaches it to the request.
18#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
19#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
20pub(crate) trait MiddlewareExt: 'static + Send + Sync {
21    async fn get_token(&self) -> Result<Option<String>, LoginError>;
22}
23
24/// Implements HTTP middleware that attaches authentication tokens to requests.
25/// Delegates to [MiddlewareExt::get_token] to retrieve tokens (which handles renewal).
26/// Only applies to requests marked with [AuthRequired].
27#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
28#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
29impl<T: MiddlewareExt> Middleware for MiddlewareWrapper<T> {
30    async fn handle(
31        &self,
32        mut req: reqwest::Request,
33        ext: &mut http::Extensions,
34        next: reqwest_middleware::Next<'_>,
35    ) -> Result<reqwest::Response, reqwest_middleware::Error> {
36        match ext.get::<AuthRequired>() {
37            Some(AuthRequired::Bearer) => {
38                match self.0.get_token().await {
39                    Ok(Some(token)) => match format!("Bearer {}", token).parse() {
40                        Ok(header_value) => {
41                            req.headers_mut()
42                                .insert(http::header::AUTHORIZATION, header_value);
43                        }
44                        Err(e) => {
45                            tracing::warn!("Failed to parse auth token for header: {e}");
46                        }
47                    },
48                    Ok(None) => {
49                        tracing::warn!("No token available for request requiring authentication");
50                    }
51                    Err(e) => {
52                        tracing::warn!("Failed to get auth token: {e}");
53                    }
54                };
55            }
56            Some(auth) => {
57                tracing::warn!(?auth, "Unsupported authentication method in request");
58            }
59            None => (),
60        }
61
62        next.run(req, ext).await
63    }
64}