From 09cf7a3ea9db3f760eb1c35ef3074dfedc8fc33f Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Sat, 14 Dec 2024 12:54:26 +0530 Subject: [PATCH] fix(webhooks): mask custom outgoing webhook headers in profile response (#6798) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/admin.rs | 43 ++++++++++++++++++++++++++-- crates/router/src/types/api/admin.rs | 20 +++++++------ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 226d39740533..ce6025f493ef 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -11,7 +11,7 @@ use common_utils::{ use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt}; #[cfg(feature = "v2")] use masking::ExposeInterface; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use url; use utoipa::ToSchema; @@ -2198,7 +2198,7 @@ pub struct ProfileResponse { /// These key-value pairs are sent as additional custom headers in the outgoing webhook request. #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] - pub outgoing_webhook_custom_http_headers: Option>>, + pub outgoing_webhook_custom_http_headers: Option, /// Merchant Connector id to be stored for tax_calculator connector #[schema(value_type = Option)] @@ -2317,7 +2317,7 @@ pub struct ProfileResponse { /// These key-value pairs are sent as additional custom headers in the outgoing webhook request. #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] - pub outgoing_webhook_custom_http_headers: Option>>, + pub outgoing_webhook_custom_http_headers: Option, /// Will be used to determine the time till which your payment will be active once the payment session starts #[schema(value_type = Option, example = 900)] @@ -2616,6 +2616,43 @@ pub struct BusinessPayoutLinkConfig { pub payout_test_mode: Option, } +#[derive(Clone, Debug, serde::Serialize)] +pub struct MaskedHeaders(HashMap); + +impl MaskedHeaders { + fn mask_value(value: &str) -> String { + let value_len = value.len(); + + let masked_value = if value_len <= 4 { + "*".repeat(value_len) + } else { + value + .char_indices() + .map(|(index, ch)| { + if index < 2 || index >= value_len - 2 { + // Show the first two and last two characters, mask the rest with '*' + ch + } else { + // Mask the remaining characters + '*' + } + }) + .collect::() + }; + + masked_value + } + + pub fn from_headers(headers: HashMap>) -> Self { + let masked_headers = headers + .into_iter() + .map(|(key, value)| (key, Self::mask_value(value.peek()))) + .collect(); + + Self(masked_headers) + } +} + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct BusinessGenericLinkConfig { /// Custom domain name to be used for hosting the link diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index 40d90125ba13..ff72af484016 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -4,12 +4,12 @@ use std::collections::HashMap; pub use api_models::admin; pub use api_models::{ admin::{ - MerchantAccountCreate, MerchantAccountDeleteResponse, MerchantAccountResponse, - MerchantAccountUpdate, MerchantConnectorCreate, MerchantConnectorDeleteResponse, - MerchantConnectorDetails, MerchantConnectorDetailsWrap, MerchantConnectorId, - MerchantConnectorResponse, MerchantDetails, MerchantId, PaymentMethodsEnabled, - ProfileCreate, ProfileResponse, ProfileUpdate, ToggleAllKVRequest, ToggleAllKVResponse, - ToggleKVRequest, ToggleKVResponse, WebhookDetails, + MaskedHeaders, MerchantAccountCreate, MerchantAccountDeleteResponse, + MerchantAccountResponse, MerchantAccountUpdate, MerchantConnectorCreate, + MerchantConnectorDeleteResponse, MerchantConnectorDetails, MerchantConnectorDetailsWrap, + MerchantConnectorId, MerchantConnectorResponse, MerchantDetails, MerchantId, + PaymentMethodsEnabled, ProfileCreate, ProfileResponse, ProfileUpdate, ToggleAllKVRequest, + ToggleAllKVResponse, ToggleKVRequest, ToggleKVResponse, WebhookDetails, }, organization::{ OrganizationCreateRequest, OrganizationId, OrganizationResponse, OrganizationUpdateRequest, @@ -131,6 +131,8 @@ impl ForeignTryFrom for ProfileResponse { ) }) .transpose()?; + let masked_outgoing_webhook_custom_http_headers = + outgoing_webhook_custom_http_headers.map(MaskedHeaders::from_headers); Ok(Self { merchant_id: item.merchant_id, @@ -168,7 +170,7 @@ impl ForeignTryFrom for ProfileResponse { always_collect_shipping_details_from_wallet_connector: item .always_collect_shipping_details_from_wallet_connector, is_connector_agnostic_mit_enabled: item.is_connector_agnostic_mit_enabled, - outgoing_webhook_custom_http_headers, + outgoing_webhook_custom_http_headers: masked_outgoing_webhook_custom_http_headers, tax_connector_id: item.tax_connector_id, is_tax_connector_enabled: item.is_tax_connector_enabled, is_network_tokenization_enabled: item.is_network_tokenization_enabled, @@ -204,6 +206,8 @@ impl ForeignTryFrom for ProfileResponse { .map(admin::OrderFulfillmentTime::try_new) .transpose() .change_context(errors::ParsingError::IntegerOverflow)?; + let masked_outgoing_webhook_custom_http_headers = + outgoing_webhook_custom_http_headers.map(MaskedHeaders::from_headers); Ok(Self { merchant_id: item.merchant_id, @@ -236,7 +240,7 @@ impl ForeignTryFrom for ProfileResponse { always_collect_billing_details_from_wallet_connector: item .always_collect_billing_details_from_wallet_connector, is_connector_agnostic_mit_enabled: item.is_connector_agnostic_mit_enabled, - outgoing_webhook_custom_http_headers, + outgoing_webhook_custom_http_headers: masked_outgoing_webhook_custom_http_headers, order_fulfillment_time, order_fulfillment_time_origin: item.order_fulfillment_time_origin, should_collect_cvv_during_payment: item.should_collect_cvv_during_payment,