bitwarden_auth/token_management/
password_manager_token_handler.rs1use std::sync::{Arc, RwLock};
4
5use bitwarden_core::{
6 NotAuthenticatedError,
7 auth::{TokenHandler, login::LoginError},
8 client::login_method::LoginMethod,
9 key_management::KeyIds,
10};
11use bitwarden_crypto::KeyStore;
12use chrono::Utc;
13
14use super::middleware::{MiddlewareExt, MiddlewareWrapper};
15use crate::token_management::middleware::TOKEN_RENEW_MARGIN_SECONDS;
16
17#[derive(Clone, Default)]
19pub struct PasswordManagerTokenHandler {
20 inner: Arc<RwLock<PasswordManagerTokenHandlerInner>>,
21}
22
23#[derive(Clone, Default)]
24struct PasswordManagerTokenHandlerInner {
25 access_token: Option<String>,
26 expires_on: Option<i64>,
27
28 refresh_token: Option<String>,
29
30 login_method: Option<Arc<RwLock<Option<Arc<LoginMethod>>>>>,
33 identity_config: Option<bitwarden_api_api::Configuration>,
34}
35
36impl TokenHandler for PasswordManagerTokenHandler {
37 fn initialize_middleware(
38 &self,
39 login_method: Arc<RwLock<Option<Arc<LoginMethod>>>>,
40 identity_config: bitwarden_api_api::Configuration,
41 _key_store: KeyStore<KeyIds>,
42 ) -> Arc<dyn reqwest_middleware::Middleware> {
43 {
44 let mut inner = self.inner.write().expect("RwLock is not poisoned");
45 inner.login_method = Some(login_method);
46 inner.identity_config = Some(identity_config);
47 }
48 Arc::new(MiddlewareWrapper(self.clone()))
49 }
50
51 fn set_tokens(&self, access_token: String, refresh_token: Option<String>, expires_in: u64) {
52 let mut inner = self.inner.write().expect("RwLock is not poisoned");
53 inner.access_token = Some(access_token);
54 inner.refresh_token = refresh_token;
55 inner.expires_on = Some(Utc::now().timestamp() + expires_in as i64);
56 }
57}
58
59#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
60#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
61impl MiddlewareExt for PasswordManagerTokenHandler {
62 async fn get_token(&self) -> Result<Option<String>, LoginError> {
63 let inner = self.inner.read().expect("RwLock is not poisoned").clone();
69
70 if let Some(expires) = inner.expires_on
72 && Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS
73 {
74 return Ok(inner.access_token.clone());
75 }
76
77 let login_method = inner.login_method.ok_or(NotAuthenticatedError)?;
80 let identity_config = inner.identity_config.ok_or(NotAuthenticatedError)?;
81
82 let login_method = login_method
83 .read()
84 .expect("RwLock is not poisoned")
85 .clone()
86 .ok_or(NotAuthenticatedError)?;
87
88 let LoginMethod::User(user_login_method) = login_method.as_ref() else {
89 return Err(NotAuthenticatedError.into());
90 };
91
92 let (access_token, refresh_token, expires_in) =
93 bitwarden_core::auth::renew::renew_pm_token_sdk_managed(
94 inner.refresh_token,
95 user_login_method,
96 identity_config,
97 )
98 .await?;
99
100 self.set_tokens(access_token.clone(), refresh_token, expires_in);
101 Ok(Some(access_token))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use std::sync::{Arc, RwLock};
108
109 use bitwarden_core::{
110 auth::TokenHandler,
111 client::login_method::{LoginMethod, UserLoginMethod},
112 key_management::KeyIds,
113 };
114 use bitwarden_crypto::{Kdf, KeyStore};
115 use wiremock::MockServer;
116
117 use super::*;
118 use crate::token_management::test_utils::*;
119
120 fn api_key_login_method() -> Arc<RwLock<Option<Arc<LoginMethod>>>> {
121 Arc::new(RwLock::new(Some(Arc::new(LoginMethod::User(
122 UserLoginMethod::ApiKey {
123 client_id: "test-client".to_string(),
124 client_secret: "test-secret".to_string(),
125 email: "[email protected]".to_string(),
126 kdf: Kdf::default_pbkdf2(),
127 },
128 )))))
129 }
130
131 #[tokio::test]
132 async fn attaches_existing_token_when_not_expired() {
133 let app_server = start_app_server().await;
134 let identity_server = MockServer::start().await;
135
136 let handler = PasswordManagerTokenHandler::default();
137 handler.set_tokens(
138 "original-token".to_string(),
139 Some("refresh".to_string()),
140 5000,
141 );
142 let client = build_client(handler.initialize_middleware(
143 api_key_login_method(),
144 identity_config(&identity_server.uri()),
145 KeyStore::<KeyIds>::default(),
146 ));
147
148 let auth = send_auth_request(&client, &app_server).await;
149 assert_eq!(auth.as_deref(), Some("Bearer original-token"));
150 assert_eq!(identity_server.received_requests().await.unwrap().len(), 0);
151 assert_eq!(app_server.received_requests().await.unwrap().len(), 1);
152 }
153
154 #[tokio::test]
155 async fn renews_expired_token() {
156 let app_server = start_app_server().await;
157 let identity_server = start_renewal_server("renewed-token").await;
158
159 let handler = PasswordManagerTokenHandler::default();
160 handler.set_tokens(
162 "expired-token".to_string(),
163 Some("old-refresh".to_string()),
164 0,
165 );
166
167 let client = build_client(handler.initialize_middleware(
168 api_key_login_method(),
169 identity_config(&identity_server.uri()),
170 KeyStore::<KeyIds>::default(),
171 ));
172
173 let auth = send_auth_request(&client, &app_server).await;
174 assert_eq!(auth.as_deref(), Some("Bearer renewed-token"));
175 assert_eq!(identity_server.received_requests().await.unwrap().len(), 1);
176 assert_eq!(app_server.received_requests().await.unwrap().len(), 1);
177 }
178}