From 91bccb33df6667f97898a40d0cc0999ccde9b240 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 27 Jul 2026 14:41:37 -0400 Subject: [PATCH 1/3] [PM-35791] Remove legacy Send access endpoints --- crates/bitwarden-send/src/access.rs | 178 +++------------------------- 1 file changed, 18 insertions(+), 160 deletions(-) diff --git a/crates/bitwarden-send/src/access.rs b/crates/bitwarden-send/src/access.rs index 81fe6e13ac..c10317b02e 100644 --- a/crates/bitwarden-send/src/access.rs +++ b/crates/bitwarden-send/src/access.rs @@ -101,19 +101,6 @@ pub enum GetFileDownloadDataError { // ===== HTTP request functions ===== -async fn access_send_v1( - api_client: &ApiClient, - send_id: &str, - password: Option, -) -> Result { - let resp = api_client - .sends_api() - .access(send_id, Some(models::SendAccessRequestModel { password })) - .await - .map_err(ApiError::from)?; - Ok(resp.try_into()?) -} - async fn access_send( api_client: &ApiClient, access_token: &str, @@ -126,24 +113,6 @@ async fn access_send( Ok(resp.try_into()?) } -async fn get_file_download_data_v1( - api_client: &ApiClient, - send_id: &str, - file_id: &str, - password: Option, -) -> Result { - let resp = api_client - .sends_api() - .get_send_file_download_data( - send_id, - file_id, - Some(models::SendAccessRequestModel { password }), - ) - .await - .map_err(ApiError::from)?; - Ok(resp.into()) -} - async fn get_file_download_data( api_client: &ApiClient, file_id: &str, @@ -196,20 +165,7 @@ impl From for SendFileDownloadData { #[cfg_attr(feature = "wasm", wasm_bindgen)] impl SendClient { - /// Accesses a send using the V1 (legacy) API endpoint. - /// The `password` is the SHA256 hash of the user-entered password, if the send is - /// password-protected. The returned [SendAccessResponse] contains encrypted fields that must - /// be decrypted client-side using the key derived from the URL fragment. - pub async fn access_send_v1( - &self, - send_id: String, - password: Option, - ) -> Result { - let config = self.client.internal.get_api_configurations(); - access_send_v1(&config.api_client, &send_id, password).await - } - - /// Accesses a send using the V2 API endpoint, authenticated with a send access token. + /// Accesses a send, authenticated with a send access token. /// The returned [SendAccessResponse] contains encrypted fields that must be decrypted /// client-side using the key derived from the URL fragment. pub async fn access_send( @@ -220,20 +176,7 @@ impl SendClient { access_send(&config.api_client, &access_token).await } - /// Gets file download data for a file send using the V1 (legacy) API endpoint. - /// The `password` is the SHA256 hash of the user-entered password, if the send is - /// password-protected. - pub async fn get_file_download_data_v1( - &self, - send_id: String, - file_id: String, - password: Option, - ) -> Result { - let config = self.client.internal.get_api_configurations(); - get_file_download_data_v1(&config.api_client, &send_id, &file_id, password).await - } - - /// Gets file download data for a file send using the V2 API endpoint, authenticated + /// Gets file download data for a file send, authenticated /// with a send access token. pub async fn get_file_download_data( &self, @@ -261,17 +204,15 @@ mod tests { const FILE_ID: &str = "file-id-abc"; const ACCESS_TOKEN: &str = "send-access-token"; - // ===== access_send_v1 ===== + // ===== access_send ===== #[tokio::test] - async fn test_access_send_v1_text() { + async fn test_access_send_text() { let api_client = ApiClient::new_mocked(|mock| { mock.sends_api - .expect_access() - .returning(|id, request| { - assert_eq!(id, SEND_ID); - let request = request.expect("request body should be present"); - assert_eq!(request.password, Some("hashed-password".to_string())); + .expect_access_using_auth() + .returning(|token| { + assert_eq!(token, ACCESS_TOKEN); Ok(SendAccessResponseModel { object: Some("send-access".to_string()), id: Some(SEND_ID.to_string()), @@ -280,57 +221,29 @@ mod tests { name: Some("encrypted-name".to_string()), file: None, text: Some(Box::new(SendTextModel { - text: Some("encrypted-text".to_string()), - hidden: Some(true), + text: Some("encrypted_send_text".to_string()), + hidden: Some(false), })), - expiration_date: Some("2025-01-10T00:00:00Z".to_string()), - creator_identifier: Some("user@example.com".to_string()), + expiration_date: None, + creator_identifier: None, }) }) .once(); }); - let result = access_send_v1(&api_client, SEND_ID, Some("hashed-password".to_string())) - .await - .unwrap(); + let result = access_send(&api_client, ACCESS_TOKEN).await.unwrap(); assert_eq!(result.id, Some(SEND_ID.to_string())); - assert_eq!(result.type_, Some(crate::SendType::Text)); + assert_eq!(result.type_, Some(crate::SendType::File)); assert_eq!(result.name, Some("encrypted-name".to_string())); - let text = result.text.expect("text variant should be populated"); - assert_eq!(text.text, Some("encrypted-text".to_string())); - assert!(text.hidden); assert!(result.file.is_none()); - assert_eq!( - result.expiration_date, - Some("2025-01-10T00:00:00Z".parse::>().unwrap()) - ); - assert_eq!( - result.creator_identifier, - Some("user@example.com".to_string()) - ); - } - - #[tokio::test] - async fn test_access_send_v1_http_error() { - let api_client = ApiClient::new_mocked(|mock| { - mock.sends_api - .expect_access() - .returning(|_id, _request| { - Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other( - "Simulated error", - ))) - }) - .once(); - }); - - let result = access_send_v1(&api_client, SEND_ID, None).await; - - assert!(matches!(result.unwrap_err(), AccessSendError::Api(_))); + let text = result.text.expect("text variant should be populated"); + assert_eq!(text.text, Some("encrypted_send_text".to_string())); + assert!(!text.hidden); + assert_eq!(result.expiration_date, None); + assert_eq!(result.creator_identifier, None); } - // ===== access_send ===== - #[tokio::test] async fn test_access_send_file() { let api_client = ApiClient::new_mocked(|mock| { @@ -391,61 +304,6 @@ mod tests { assert!(matches!(result.unwrap_err(), AccessSendError::Api(_))); } - // ===== get_file_download_data_v1 ===== - - #[tokio::test] - async fn test_get_file_download_data_v1() { - let api_client = ApiClient::new_mocked(|mock| { - mock.sends_api - .expect_get_send_file_download_data() - .returning(|send_id, file_id, request| { - assert_eq!(send_id, SEND_ID); - assert_eq!(file_id, FILE_ID); - let request = request.expect("request body should be present"); - assert_eq!(request.password, Some("hashed-password".to_string())); - Ok(SendFileDownloadDataResponseModel { - object: Some("send-fileDownload".to_string()), - id: Some(FILE_ID.to_string()), - url: Some("https://example.com/download".to_string()), - }) - }) - .once(); - }); - - let result = get_file_download_data_v1( - &api_client, - SEND_ID, - FILE_ID, - Some("hashed-password".to_string()), - ) - .await - .unwrap(); - - assert_eq!(result.id, Some(FILE_ID.to_string())); - assert_eq!(result.url, Some("https://example.com/download".to_string())); - } - - #[tokio::test] - async fn test_get_file_download_data_v1_http_error() { - let api_client = ApiClient::new_mocked(|mock| { - mock.sends_api - .expect_get_send_file_download_data() - .returning(|_send_id, _file_id, _request| { - Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other( - "Simulated error", - ))) - }) - .once(); - }); - - let result = get_file_download_data_v1(&api_client, SEND_ID, FILE_ID, None).await; - - assert!(matches!( - result.unwrap_err(), - GetFileDownloadDataError::Api(_) - )); - } - // ===== get_file_download_data ===== #[tokio::test] From 2077ce380ad7ee8f18b258d705f96fc8e1e8ffdc Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 28 Jul 2026 10:39:46 -0400 Subject: [PATCH 2/3] Fix test --- crates/bitwarden-send/src/access.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-send/src/access.rs b/crates/bitwarden-send/src/access.rs index c10317b02e..5070cbccee 100644 --- a/crates/bitwarden-send/src/access.rs +++ b/crates/bitwarden-send/src/access.rs @@ -234,7 +234,7 @@ mod tests { let result = access_send(&api_client, ACCESS_TOKEN).await.unwrap(); assert_eq!(result.id, Some(SEND_ID.to_string())); - assert_eq!(result.type_, Some(crate::SendType::File)); + assert_eq!(result.type_, Some(crate::SendType::Text)); assert_eq!(result.name, Some("encrypted-name".to_string())); assert!(result.file.is_none()); let text = result.text.expect("text variant should be populated"); From 912b14dcd505342b85b5309de2dbe2d0c3b20ef0 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 28 Jul 2026 13:37:50 -0400 Subject: [PATCH 3/3] Address AI review comments --- crates/bitwarden-send/src/access.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden-send/src/access.rs b/crates/bitwarden-send/src/access.rs index 5070cbccee..6d5e1a27bd 100644 --- a/crates/bitwarden-send/src/access.rs +++ b/crates/bitwarden-send/src/access.rs @@ -176,8 +176,7 @@ impl SendClient { access_send(&config.api_client, &access_token).await } - /// Gets file download data for a file send, authenticated - /// with a send access token. + /// Gets file download data for a file send, authenticated with a send access token. pub async fn get_file_download_data( &self, access_token: String, @@ -222,10 +221,10 @@ mod tests { file: None, text: Some(Box::new(SendTextModel { text: Some("encrypted_send_text".to_string()), - hidden: Some(false), + hidden: Some(true), })), - expiration_date: None, - creator_identifier: None, + expiration_date: Some("2025-01-10T00:00:00Z".to_string()), + creator_identifier: Some("user@example.com".to_string()), }) }) .once(); @@ -239,9 +238,15 @@ mod tests { assert!(result.file.is_none()); let text = result.text.expect("text variant should be populated"); assert_eq!(text.text, Some("encrypted_send_text".to_string())); - assert!(!text.hidden); - assert_eq!(result.expiration_date, None); - assert_eq!(result.creator_identifier, None); + assert!(text.hidden); + assert_eq!( + result.expiration_date, + Some("2025-01-10T00:00:00Z".parse::>().unwrap()) + ); + assert_eq!( + result.creator_identifier, + Some("user@example.com".to_string()) + ); } #[tokio::test]