mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 02:57:02 +08:00
feat(pii): implement a masking strategy for UPI VPAs (#1641)
Co-authored-by: Prasunna Soppa <prasunna.soppa@juspay.in> Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
@ -922,7 +922,7 @@ pub struct CryptoData {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct UpiData {
|
||||
#[schema(value_type = Option<String>, example = "successtest@iata")]
|
||||
pub vpa_id: Option<Secret<String>>,
|
||||
pub vpa_id: Option<Secret<String, pii::UpiVpaMaskingStrategy>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
|
||||
@ -329,13 +329,33 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Strategy for masking UPI VPA's
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpiVpaMaskingStrategy;
|
||||
|
||||
impl<T> Strategy<T> for UpiVpaMaskingStrategy
|
||||
where
|
||||
T: AsRef<str> + std::fmt::Debug,
|
||||
{
|
||||
fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let vpa_str: &str = val.as_ref();
|
||||
if let Some((user_identifier, bank_or_psp)) = vpa_str.split_once('@') {
|
||||
let masked_user_identifier = "*".repeat(user_identifier.len());
|
||||
write!(f, "{masked_user_identifier}@{bank_or_psp}")
|
||||
} else {
|
||||
WithType::fmt(val, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pii_masking_strategy_tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use masking::{ExposeInterface, Secret};
|
||||
|
||||
use super::{ClientSecret, Email, IpAddress};
|
||||
use super::{ClientSecret, Email, IpAddress, UpiVpaMaskingStrategy};
|
||||
use crate::pii::{EmailStrategy, REDACTED};
|
||||
|
||||
/*
|
||||
@ -435,4 +455,16 @@ mod pii_masking_strategy_tests {
|
||||
let secret: Secret<String> = Secret::new("+40712345678".to_string());
|
||||
assert_eq!("*** alloc::string::String ***", format!("{secret:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_upi_vpa_masking() {
|
||||
let secret: Secret<String, UpiVpaMaskingStrategy> = Secret::new("my_name@upi".to_string());
|
||||
assert_eq!("*******@upi", format!("{secret:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_upi_vpa_masking() {
|
||||
let secret: Secret<String, UpiVpaMaskingStrategy> = Secret::new("my_name_upi".to_string());
|
||||
assert_eq!("*** alloc::string::String ***", format!("{secret:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,3 +40,25 @@ where
|
||||
self.inner_secret
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that consumes a secret and converts it to a secret with a different masking strategy.
|
||||
pub trait SwitchStrategy<FromStrategy, ToStrategy> {
|
||||
/// The type returned by `switch_strategy()`.
|
||||
type Output;
|
||||
|
||||
/// Consumes the secret and converts it to a secret with a different masking strategy.
|
||||
fn switch_strategy(self) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<S, FromStrategy, ToStrategy> SwitchStrategy<FromStrategy, ToStrategy>
|
||||
for Secret<S, FromStrategy>
|
||||
where
|
||||
FromStrategy: crate::Strategy<S>,
|
||||
ToStrategy: crate::Strategy<S>,
|
||||
{
|
||||
type Output = Secret<S, ToStrategy>;
|
||||
|
||||
fn switch_strategy(self) -> Self::Output {
|
||||
Secret::new(self.inner_secret)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ mod strategy;
|
||||
|
||||
pub use strategy::{Strategy, WithType, WithoutType};
|
||||
mod abs;
|
||||
pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface};
|
||||
pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface, SwitchStrategy};
|
||||
|
||||
mod secret;
|
||||
mod strong_secret;
|
||||
|
||||
@ -5,7 +5,7 @@ use common_utils::{
|
||||
crypto::Encryptable,
|
||||
date_time,
|
||||
ext_traits::StringExt,
|
||||
pii::{IpAddress, SecretSerdeValue},
|
||||
pii::{IpAddress, SecretSerdeValue, UpiVpaMaskingStrategy},
|
||||
};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -63,7 +63,7 @@ pub enum StripeWallet {
|
||||
|
||||
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
|
||||
pub struct StripeUpi {
|
||||
pub vpa_id: masking::Secret<String>,
|
||||
pub vpa_id: masking::Secret<String, UpiVpaMaskingStrategy>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api_models::enums::PaymentMethod;
|
||||
use masking::Secret;
|
||||
use masking::{Secret, SwitchStrategy};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@ -90,9 +90,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest {
|
||||
};
|
||||
let return_url = item.get_return_url()?;
|
||||
let payer_info = match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Upi(upi_data) => {
|
||||
upi_data.vpa_id.map(|id| PayerInfo { token_id: id })
|
||||
}
|
||||
api::PaymentMethodData::Upi(upi_data) => upi_data.vpa_id.map(|id| PayerInfo {
|
||||
token_id: id.switch_strategy(),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
let amount =
|
||||
|
||||
Reference in New Issue
Block a user