mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +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")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct UpiData {
|
pub struct UpiData {
|
||||||
#[schema(value_type = Option<String>, example = "successtest@iata")]
|
#[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)]
|
#[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)]
|
#[cfg(test)]
|
||||||
mod pii_masking_strategy_tests {
|
mod pii_masking_strategy_tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use masking::{ExposeInterface, Secret};
|
use masking::{ExposeInterface, Secret};
|
||||||
|
|
||||||
use super::{ClientSecret, Email, IpAddress};
|
use super::{ClientSecret, Email, IpAddress, UpiVpaMaskingStrategy};
|
||||||
use crate::pii::{EmailStrategy, REDACTED};
|
use crate::pii::{EmailStrategy, REDACTED};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -435,4 +455,16 @@ mod pii_masking_strategy_tests {
|
|||||||
let secret: Secret<String> = Secret::new("+40712345678".to_string());
|
let secret: Secret<String> = Secret::new("+40712345678".to_string());
|
||||||
assert_eq!("*** alloc::string::String ***", format!("{secret:?}"));
|
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
|
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};
|
pub use strategy::{Strategy, WithType, WithoutType};
|
||||||
mod abs;
|
mod abs;
|
||||||
pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface};
|
pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface, SwitchStrategy};
|
||||||
|
|
||||||
mod secret;
|
mod secret;
|
||||||
mod strong_secret;
|
mod strong_secret;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use common_utils::{
|
|||||||
crypto::Encryptable,
|
crypto::Encryptable,
|
||||||
date_time,
|
date_time,
|
||||||
ext_traits::StringExt,
|
ext_traits::StringExt,
|
||||||
pii::{IpAddress, SecretSerdeValue},
|
pii::{IpAddress, SecretSerdeValue, UpiVpaMaskingStrategy},
|
||||||
};
|
};
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -63,7 +63,7 @@ pub enum StripeWallet {
|
|||||||
|
|
||||||
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
|
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
|
||||||
pub struct StripeUpi {
|
pub struct StripeUpi {
|
||||||
pub vpa_id: masking::Secret<String>,
|
pub vpa_id: masking::Secret<String, UpiVpaMaskingStrategy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use api_models::enums::PaymentMethod;
|
use api_models::enums::PaymentMethod;
|
||||||
use masking::Secret;
|
use masking::{Secret, SwitchStrategy};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -90,9 +90,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest {
|
|||||||
};
|
};
|
||||||
let return_url = item.get_return_url()?;
|
let return_url = item.get_return_url()?;
|
||||||
let payer_info = match item.request.payment_method_data.clone() {
|
let payer_info = match item.request.payment_method_data.clone() {
|
||||||
api::PaymentMethodData::Upi(upi_data) => {
|
api::PaymentMethodData::Upi(upi_data) => upi_data.vpa_id.map(|id| PayerInfo {
|
||||||
upi_data.vpa_id.map(|id| PayerInfo { token_id: id })
|
token_id: id.switch_strategy(),
|
||||||
}
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let amount =
|
let amount =
|
||||||
|
|||||||
Reference in New Issue
Block a user