diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs index eb196a72074..4f6411a42cd 100644 --- a/crates/api_models/src/customers.rs +++ b/crates/api_models/src/customers.rs @@ -1,12 +1,5 @@ -use common_utils::{ - crypto, custom_serde, - encryption::Encryption, - id_type, - pii::{self, EmailStrategy}, - types::{keymanager::ToEncryptable, Description}, -}; -use masking::{ExposeInterface, Secret, SwitchStrategy}; -use rustc_hash::FxHashMap; +use common_utils::{crypto, custom_serde, id_type, pii, types::Description}; +use masking::Secret; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -129,85 +122,6 @@ impl CustomerRequest { } } -pub struct CustomerRequestWithEmail { - pub name: Option>, - pub email: Option, - pub phone: Option>, -} - -pub struct CustomerRequestWithEncryption { - pub name: Option, - pub phone: Option, - pub email: Option, -} - -pub struct EncryptableCustomer { - pub name: crypto::OptionalEncryptableName, - pub phone: crypto::OptionalEncryptablePhone, - pub email: crypto::OptionalEncryptableEmail, -} - -impl ToEncryptable, Encryption> - for CustomerRequestWithEncryption -{ - fn to_encryptable(self) -> FxHashMap { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - self.name.map(|x| map.insert("name".to_string(), x)); - self.phone.map(|x| map.insert("phone".to_string(), x)); - self.email.map(|x| map.insert("email".to_string(), x)); - map - } - - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableCustomer { - name: hashmap.remove("name"), - phone: hashmap.remove("phone"), - email: hashmap.remove("email").map(|email| { - let encryptable: crypto::Encryptable> = - crypto::Encryptable::new( - email.clone().into_inner().switch_strategy(), - email.into_encrypted(), - ); - encryptable - }), - }) - } -} - -impl ToEncryptable, Secret> - for CustomerRequestWithEmail -{ - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - self.name.map(|x| map.insert("name".to_string(), x)); - self.phone.map(|x| map.insert("phone".to_string(), x)); - self.email - .map(|x| map.insert("email".to_string(), x.expose().switch_strategy())); - map - } - - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableCustomer { - name: hashmap.remove("name"), - email: hashmap.remove("email").map(|email| { - let encryptable: crypto::Encryptable> = - crypto::Encryptable::new( - email.clone().into_inner().switch_strategy(), - email.into_encrypted(), - ); - encryptable - }), - phone: hashmap.remove("phone"), - }) - } -} - #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[derive(Debug, Clone, Serialize, ToSchema)] pub struct CustomerResponse { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 777cabe3002..8fd3b5d25dc 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -12,13 +12,12 @@ use common_utils::{ ext_traits::{ConfigExt, Encode, ValueExt}, hashing::HashedString, id_type, - pii::{self, Email, EmailStrategy}, - types::{keymanager::ToEncryptable, MinorUnit, StringMajorUnit}, + pii::{self, Email}, + types::{MinorUnit, StringMajorUnit}, }; use error_stack::ResultExt; -use masking::{ExposeInterface, PeekInterface, Secret, SwitchStrategy, WithType}; +use masking::{PeekInterface, Secret, WithType}; use router_derive::Setter; -use rustc_hash::FxHashMap; use serde::{de, ser::Serializer, Deserialize, Deserializer, Serialize}; use strum::Display; use time::{Date, PrimitiveDateTime}; @@ -3818,54 +3817,6 @@ pub struct EncryptableAddressDetails { pub email: crypto::OptionalEncryptableEmail, } -impl ToEncryptable, Secret> - for AddressDetailsWithPhone -{ - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult< - EncryptableAddressDetails, - common_utils::errors::ParsingError, - > { - Ok(EncryptableAddressDetails { - line1: hashmap.remove("line1"), - line2: hashmap.remove("line2"), - line3: hashmap.remove("line3"), - state: hashmap.remove("state"), - zip: hashmap.remove("zip"), - first_name: hashmap.remove("first_name"), - last_name: hashmap.remove("last_name"), - phone_number: hashmap.remove("phone_number"), - email: hashmap.remove("email").map(|x| { - let inner: Secret = x.clone().into_inner().switch_strategy(); - crypto::Encryptable::new(inner, x.into_encrypted()) - }), - }) - } - - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(9, Default::default()); - self.address.map(|address| { - address.line1.map(|x| map.insert("line1".to_string(), x)); - address.line2.map(|x| map.insert("line2".to_string(), x)); - address.line3.map(|x| map.insert("line3".to_string(), x)); - address.state.map(|x| map.insert("state".to_string(), x)); - address.zip.map(|x| map.insert("zip".to_string(), x)); - address - .first_name - .map(|x| map.insert("first_name".to_string(), x)); - address - .last_name - .map(|x| map.insert("last_name".to_string(), x)); - }); - self.email - .map(|x| map.insert("email".to_string(), x.expose().switch_strategy())); - self.phone_number - .map(|x| map.insert("phone_number".to_string(), x)); - map - } -} - #[derive(Debug, Clone, Default, Eq, PartialEq, ToSchema, serde::Deserialize, serde::Serialize)] pub struct PhoneDetails { /// The contact number diff --git a/crates/diesel_models/src/address.rs b/crates/diesel_models/src/address.rs index a1cfb668716..06b82cb2c20 100644 --- a/crates/diesel_models/src/address.rs +++ b/crates/diesel_models/src/address.rs @@ -1,12 +1,5 @@ -use common_utils::{ - crypto::{self, Encryptable}, - encryption::Encryption, - pii::EmailStrategy, - types::keymanager::ToEncryptable, -}; +use common_utils::{crypto, encryption::Encryption}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; -use masking::{Secret, SwitchStrategy}; -use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -74,48 +67,6 @@ pub struct EncryptableAddress { pub email: crypto::OptionalEncryptableEmail, } -impl ToEncryptable, Encryption> for Address { - fn to_encryptable(self) -> FxHashMap { - let mut map = FxHashMap::with_capacity_and_hasher(9, Default::default()); - self.line1.map(|x| map.insert("line1".to_string(), x)); - self.line2.map(|x| map.insert("line2".to_string(), x)); - self.line3.map(|x| map.insert("line3".to_string(), x)); - self.zip.map(|x| map.insert("zip".to_string(), x)); - self.state.map(|x| map.insert("state".to_string(), x)); - self.first_name - .map(|x| map.insert("first_name".to_string(), x)); - self.last_name - .map(|x| map.insert("last_name".to_string(), x)); - self.phone_number - .map(|x| map.insert("phone_number".to_string(), x)); - self.email.map(|x| map.insert("email".to_string(), x)); - map - } - - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableAddress { - line1: hashmap.remove("line1"), - line2: hashmap.remove("line2"), - line3: hashmap.remove("line3"), - zip: hashmap.remove("zip"), - state: hashmap.remove("state"), - first_name: hashmap.remove("first_name"), - last_name: hashmap.remove("last_name"), - phone_number: hashmap.remove("phone_number"), - email: hashmap.remove("email").map(|email| { - let encryptable: Encryptable> = Encryptable::new( - email.clone().into_inner().switch_strategy(), - email.into_encrypted(), - ); - encryptable - }), - }) - } -} - #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)] #[diesel(table_name = address)] pub struct AddressUpdateInternal { diff --git a/crates/diesel_models/src/events.rs b/crates/diesel_models/src/events.rs index b6c5efe0fd1..82b2b58f80b 100644 --- a/crates/diesel_models/src/events.rs +++ b/crates/diesel_models/src/events.rs @@ -1,11 +1,7 @@ -use common_utils::{ - crypto::OptionalEncryptableSecretString, custom_serde, encryption::Encryption, - types::keymanager::ToEncryptable, -}; +use common_utils::{custom_serde, encryption::Encryption}; use diesel::{ expression::AsExpression, AsChangeset, Identifiable, Insertable, Queryable, Selectable, }; -use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -63,38 +59,6 @@ pub struct Event { pub metadata: Option, } -pub struct EventWithEncryption { - pub request: Option, - pub response: Option, -} - -pub struct EncryptableEvent { - pub request: OptionalEncryptableSecretString, - pub response: OptionalEncryptableSecretString, -} - -impl ToEncryptable, Encryption> for EventWithEncryption { - fn to_encryptable(self) -> rustc_hash::FxHashMap { - let mut map = rustc_hash::FxHashMap::default(); - self.request.map(|x| map.insert("request".to_string(), x)); - self.response.map(|x| map.insert("response".to_string(), x)); - map - } - - fn from_encryptable( - mut hashmap: rustc_hash::FxHashMap< - String, - common_utils::crypto::Encryptable>, - >, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableEvent { - request: hashmap.remove("request"), - response: hashmap.remove("response"), - }) - } -} - #[derive(Clone, Debug, Deserialize, Serialize, AsExpression, diesel::FromSqlRow)] #[diesel(sql_type = diesel::sql_types::Jsonb)] pub enum EventMetadata { diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs index 9f079f1312e..71cb4ebc2f9 100644 --- a/crates/hyperswitch_domain_models/src/customer.rs +++ b/crates/hyperswitch_domain_models/src/customer.rs @@ -1,8 +1,8 @@ -use api_models::customers::CustomerRequestWithEncryption; #[cfg(all(feature = "v2", feature = "customer_v2"))] use common_enums::DeleteStatus; use common_utils::{ - crypto, date_time, + crypto::{self, Encryptable}, + date_time, encryption::Encryption, errors::{CustomResult, ValidationError}, id_type, pii, @@ -13,19 +13,23 @@ use common_utils::{ }; use diesel_models::customers::CustomerUpdateInternal; use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; +use masking::{PeekInterface, Secret, SwitchStrategy}; +use rustc_hash::FxHashMap; use time::PrimitiveDateTime; use crate::type_encryption as types; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct Customer { pub customer_id: id_type::CustomerId, pub merchant_id: id_type::MerchantId, - pub name: crypto::OptionalEncryptableName, - pub email: crypto::OptionalEncryptableEmail, - pub phone: crypto::OptionalEncryptablePhone, + #[encrypt] + pub name: Option>>, + #[encrypt] + pub email: Option>>, + #[encrypt] + pub phone: Option>>, pub phone_country_code: Option, pub description: Option, pub created_at: PrimitiveDateTime, @@ -39,12 +43,15 @@ pub struct Customer { } #[cfg(all(feature = "v2", feature = "customer_v2"))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct Customer { pub merchant_id: id_type::MerchantId, - pub name: crypto::OptionalEncryptableName, - pub email: crypto::OptionalEncryptableEmail, - pub phone: crypto::OptionalEncryptablePhone, + #[encrypt] + pub name: Option>>, + #[encrypt] + pub email: Option>>, + #[encrypt] + pub phone: Option>>, pub phone_country_code: Option, pub description: Option, pub created_at: PrimitiveDateTime, @@ -98,8 +105,8 @@ impl super::behaviour::Conversion for Customer { let decrypted = types::crypto_operation( state, common_utils::type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(CustomerRequestWithEncryption::to_encryptable( - CustomerRequestWithEncryption { + types::CryptoOperation::BatchDecrypt(EncryptedCustomer::to_encryptable( + EncryptedCustomer { name: item.name.clone(), phone: item.phone.clone(), email: item.email.clone(), @@ -113,16 +120,23 @@ impl super::behaviour::Conversion for Customer { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), })?; - let encryptable_customer = CustomerRequestWithEncryption::from_encryptable(decrypted) - .change_context(ValidationError::InvalidValue { + let encryptable_customer = EncryptedCustomer::from_encryptable(decrypted).change_context( + ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), - })?; + }, + )?; Ok(Self { customer_id: item.customer_id, merchant_id: item.merchant_id, name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, phone_country_code: item.phone_country_code, description: item.description, @@ -198,8 +212,8 @@ impl super::behaviour::Conversion for Customer { let decrypted = types::crypto_operation( state, common_utils::type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(CustomerRequestWithEncryption::to_encryptable( - CustomerRequestWithEncryption { + types::CryptoOperation::BatchDecrypt(EncryptedCustomer::to_encryptable( + EncryptedCustomer { name: item.name.clone(), phone: item.phone.clone(), email: item.email.clone(), @@ -213,17 +227,24 @@ impl super::behaviour::Conversion for Customer { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), })?; - let encryptable_customer = CustomerRequestWithEncryption::from_encryptable(decrypted) - .change_context(ValidationError::InvalidValue { + let encryptable_customer = EncryptedCustomer::from_encryptable(decrypted).change_context( + ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), - })?; + }, + )?; Ok(Self { id: item.id, merchant_reference_id: item.merchant_reference_id, merchant_id: item.merchant_id, name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, phone_country_code: item.phone_country_code, description: item.description, diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index 9418f6f8f1e..0d730be203b 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -10,16 +10,18 @@ use diesel_models::{enums, merchant_connector_account::MerchantConnectorAccountU use error_stack::ResultExt; use masking::{PeekInterface, Secret}; use rustc_hash::FxHashMap; +use serde_json::Value; use super::behaviour; use crate::type_encryption::{crypto_operation, CryptoOperation}; #[cfg(feature = "v1")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct MerchantConnectorAccount { pub merchant_id: id_type::MerchantId, pub connector_name: String, - pub connector_account_details: Encryptable, + #[encrypt] + pub connector_account_details: Encryptable>, pub test_mode: Option, pub disabled: Option, pub merchant_connector_id: id_type::MerchantConnectorAccountId, @@ -38,8 +40,10 @@ pub struct MerchantConnectorAccount { pub applepay_verified_domains: Option>, pub pm_auth_config: Option, pub status: enums::ConnectorStatus, - pub connector_wallets_details: Option>, - pub additional_merchant_data: Option>, + #[encrypt] + pub connector_wallets_details: Option>>, + #[encrypt] + pub additional_merchant_data: Option>>, pub version: common_enums::ApiVersion, } @@ -51,12 +55,13 @@ impl MerchantConnectorAccount { } #[cfg(feature = "v2")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct MerchantConnectorAccount { pub id: id_type::MerchantConnectorAccountId, pub merchant_id: id_type::MerchantId, pub connector_name: String, - pub connector_account_details: Encryptable, + #[encrypt] + pub connector_account_details: Encryptable>, pub disabled: Option, pub payment_methods_enabled: Option>, pub connector_type: enums::ConnectorType, @@ -70,8 +75,10 @@ pub struct MerchantConnectorAccount { pub applepay_verified_domains: Option>, pub pm_auth_config: Option, pub status: enums::ConnectorStatus, - pub connector_wallets_details: Option>, - pub additional_merchant_data: Option>, + #[encrypt] + pub connector_wallets_details: Option>>, + #[encrypt] + pub additional_merchant_data: Option>>, pub version: common_enums::ApiVersion, } @@ -179,11 +186,13 @@ impl behaviour::Conversion for MerchantConnectorAccount { let decrypted_data = crypto_operation( state, type_name!(Self::DstType), - CryptoOperation::BatchDecrypt(EncryptedMca::to_encryptable(EncryptedMca { - connector_account_details: other.connector_account_details, - additional_merchant_data: other.additional_merchant_data, - connector_wallets_details: other.connector_wallets_details, - })), + CryptoOperation::BatchDecrypt(EncryptedMerchantConnectorAccount::to_encryptable( + EncryptedMerchantConnectorAccount { + connector_account_details: other.connector_account_details, + additional_merchant_data: other.additional_merchant_data, + connector_wallets_details: other.connector_wallets_details, + }, + )), identifier.clone(), key.peek(), ) @@ -193,11 +202,10 @@ impl behaviour::Conversion for MerchantConnectorAccount { message: "Failed while decrypting connector account details".to_string(), })?; - let decrypted_data = EncryptedMca::from_encryptable(decrypted_data).change_context( - ValidationError::InvalidValue { + let decrypted_data = EncryptedMerchantConnectorAccount::from_encryptable(decrypted_data) + .change_context(ValidationError::InvalidValue { message: "Failed while decrypting connector account details".to_string(), - }, - )?; + })?; Ok(Self { merchant_id: other.merchant_id, @@ -308,11 +316,13 @@ impl behaviour::Conversion for MerchantConnectorAccount { let decrypted_data = crypto_operation( state, type_name!(Self::DstType), - CryptoOperation::BatchDecrypt(EncryptedMca::to_encryptable(EncryptedMca { - connector_account_details: other.connector_account_details, - additional_merchant_data: other.additional_merchant_data, - connector_wallets_details: other.connector_wallets_details, - })), + CryptoOperation::BatchDecrypt(EncryptedMerchantConnectorAccount::to_encryptable( + EncryptedMerchantConnectorAccount { + connector_account_details: other.connector_account_details, + additional_merchant_data: other.additional_merchant_data, + connector_wallets_details: other.connector_wallets_details, + }, + )), identifier.clone(), key.peek(), ) @@ -322,11 +332,10 @@ impl behaviour::Conversion for MerchantConnectorAccount { message: "Failed while decrypting connector account details".to_string(), })?; - let decrypted_data = EncryptedMca::from_encryptable(decrypted_data).change_context( - ValidationError::InvalidValue { + let decrypted_data = EncryptedMerchantConnectorAccount::from_encryptable(decrypted_data) + .change_context(ValidationError::InvalidValue { message: "Failed while decrypting connector account details".to_string(), - }, - )?; + })?; Ok(Self { id: other.id, @@ -502,121 +511,3 @@ impl From for MerchantConnectorAccountUpdateInte } } } - -pub struct McaFromRequestfromUpdate { - pub connector_account_details: Option, - pub connector_wallets_details: Option, - pub additional_merchant_data: Option, -} -pub struct McaFromRequest { - pub connector_account_details: pii::SecretSerdeValue, - pub connector_wallets_details: Option, - pub additional_merchant_data: Option, -} - -pub struct DecryptedMca { - pub connector_account_details: Encryptable, - pub connector_wallets_details: Option>, - pub additional_merchant_data: Option>, -} - -pub struct EncryptedMca { - pub connector_account_details: Encryption, - pub connector_wallets_details: Option, - pub additional_merchant_data: Option, -} - -pub struct DecryptedUpdateMca { - pub connector_account_details: Option>, - pub connector_wallets_details: Option>, - pub additional_merchant_data: Option>, -} - -impl ToEncryptable, Encryption> for EncryptedMca { - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> CustomResult { - Ok(DecryptedMca { - connector_account_details: hashmap.remove("connector_account_details").ok_or( - error_stack::report!(common_utils::errors::ParsingError::EncodeError( - "Unable to convert from HashMap to DecryptedMca", - )), - )?, - connector_wallets_details: hashmap.remove("connector_wallets_details"), - additional_merchant_data: hashmap.remove("additional_merchant_data"), - }) - } - - fn to_encryptable(self) -> FxHashMap { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - - map.insert( - "connector_account_details".to_string(), - self.connector_account_details, - ); - self.connector_wallets_details - .map(|s| map.insert("connector_wallets_details".to_string(), s)); - self.additional_merchant_data - .map(|s| map.insert("additional_merchant_data".to_string(), s)); - map - } -} - -impl ToEncryptable, Secret> - for McaFromRequestfromUpdate -{ - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> CustomResult { - Ok(DecryptedUpdateMca { - connector_account_details: hashmap.remove("connector_account_details"), - connector_wallets_details: hashmap.remove("connector_wallets_details"), - additional_merchant_data: hashmap.remove("additional_merchant_data"), - }) - } - - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - - self.connector_account_details - .map(|cad| map.insert("connector_account_details".to_string(), cad)); - - self.connector_wallets_details - .map(|s| map.insert("connector_wallets_details".to_string(), s)); - self.additional_merchant_data - .map(|s| map.insert("additional_merchant_data".to_string(), s)); - map - } -} - -impl ToEncryptable, Secret> - for McaFromRequest -{ - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> CustomResult { - Ok(DecryptedMca { - connector_account_details: hashmap.remove("connector_account_details").ok_or( - error_stack::report!(common_utils::errors::ParsingError::EncodeError( - "Unable to convert from HashMap to DecryptedMca", - )), - )?, - connector_wallets_details: hashmap.remove("connector_wallets_details"), - additional_merchant_data: hashmap.remove("additional_merchant_data"), - }) - } - - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - - map.insert( - "connector_account_details".to_string(), - self.connector_account_details, - ); - self.connector_wallets_details - .map(|s| map.insert("connector_wallets_details".to_string(), s)); - self.additional_merchant_data - .map(|s| map.insert("additional_merchant_data".to_string(), s)); - map - } -} diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 933c13416f2..cd41458dfbf 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -5,11 +5,21 @@ use std::marker::PhantomData; use api_models::payments::Address; #[cfg(feature = "v2")] use api_models::payments::OrderDetailsWithAmount; -use common_utils::{self, crypto::Encryptable, id_type, pii, types::MinorUnit}; +use common_utils::{ + self, + crypto::Encryptable, + encryption::Encryption, + errors::CustomResult, + id_type, pii, + types::{keymanager::ToEncryptable, MinorUnit}, +}; use diesel_models::payment_intent::TaxDetails; #[cfg(feature = "v2")] use error_stack::ResultExt; use masking::Secret; +use router_derive::ToEncryption; +use rustc_hash::FxHashMap; +use serde_json::Value; use time::PrimitiveDateTime; pub mod payment_attempt; @@ -25,7 +35,7 @@ use crate::{business_profile, merchant_account}; use crate::{errors, payment_method_data, ApiModelToDieselModelConvertor}; #[cfg(feature = "v1")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToEncryption)] pub struct PaymentIntent { pub payment_id: id_type::PaymentId, pub merchant_id: id_type::MerchantId, @@ -37,7 +47,7 @@ pub struct PaymentIntent { pub customer_id: Option, pub description: Option, pub return_url: Option, - pub metadata: Option, + pub metadata: Option, pub connector_id: Option, pub shipping_address_id: Option, pub billing_address_id: Option, @@ -56,9 +66,9 @@ pub struct PaymentIntent { pub business_country: Option, pub business_label: Option, pub order_details: Option>, - pub allowed_payment_method_types: Option, - pub connector_metadata: Option, - pub feature_metadata: Option, + pub allowed_payment_method_types: Option, + pub connector_metadata: Option, + pub feature_metadata: Option, pub attempt_count: i16, pub profile_id: Option, pub payment_link_id: Option, @@ -78,10 +88,13 @@ pub struct PaymentIntent { pub request_external_three_ds_authentication: Option, pub charges: Option, pub frm_metadata: Option, - pub customer_details: Option>>, - pub billing_details: Option>>, + #[encrypt] + pub customer_details: Option>>, + #[encrypt] + pub billing_details: Option>>, pub merchant_order_reference_id: Option, - pub shipping_details: Option>>, + #[encrypt] + pub shipping_details: Option>>, pub is_payment_processor_token_flow: Option, pub organization_id: id_type::OrganizationId, pub tax_details: Option, @@ -225,7 +238,7 @@ impl AmountDetails { } #[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToEncryption)] pub struct PaymentIntent { /// The global identifier for the payment intent. This is generated by the system. /// The format of the global id is `{cell_id:5}_pay_{time_ordered_uuid:32}`. @@ -292,19 +305,22 @@ pub struct PaymentIntent { /// Metadata related to fraud and risk management pub frm_metadata: Option, /// The details of the customer in a denormalized form. Only a subset of fields are stored. - pub customer_details: Option>>, + #[encrypt] + pub customer_details: Option>>, /// The reference id for the order in the merchant's system. This value can be passed by the merchant. pub merchant_reference_id: Option, /// The billing address for the order in a denormalized form. + #[encrypt(ty = Value)] pub billing_address: Option>>, /// The shipping address for the order in a denormalized form. + #[encrypt(ty = Value)] pub shipping_address: Option>>, /// Capture method for the payment pub capture_method: storage_enums::CaptureMethod, /// Authentication type that is requested by the merchant for this payment. pub authentication_type: common_enums::AuthenticationType, /// This contains the pre routing results that are done when routing is done during listing the payment methods. - pub prerouting_algorithm: Option, + pub prerouting_algorithm: Option, /// The organization id for the payment. This is derived from the merchant account pub organization_id: id_type::OrganizationId, /// Denotes the request by the merchant whether to enable a payment link for this payment. @@ -323,7 +339,7 @@ pub struct PaymentIntent { impl PaymentIntent { fn get_request_incremental_authorization_value( request: &api_models::payments::PaymentsCreateIntentRequest, - ) -> common_utils::errors::CustomResult< + ) -> CustomResult< common_enums::RequestIncrementalAuthorization, errors::api_error_response::ApiErrorResponse, > { @@ -364,8 +380,7 @@ impl PaymentIntent { request: api_models::payments::PaymentsCreateIntentRequest, billing_address: Option>>, shipping_address: Option>>, - ) -> common_utils::errors::CustomResult - { + ) -> CustomResult { let allowed_payment_method_types = request .get_allowed_payment_method_types_as_value() .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index f1e4de499e6..c9f7a5e2ae5 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -3,7 +3,7 @@ use common_enums as storage_enums; use common_utils::ext_traits::{Encode, ValueExt}; use common_utils::{ consts::{PAYMENTS_LIST_MAX_LIMIT_V1, PAYMENTS_LIST_MAX_LIMIT_V2}, - crypto::{self, Encryptable}, + crypto::Encryptable, encryption::Encryption, errors::{CustomResult, ValidationError}, id_type, @@ -21,7 +21,6 @@ use error_stack::ResultExt; #[cfg(feature = "v2")] use masking::ExposeInterface; use masking::{Deserialize, PeekInterface, Secret}; -use rustc_hash::FxHashMap; use serde::Serialize; use time::PrimitiveDateTime; @@ -1241,10 +1240,10 @@ impl behaviour::Conversion for PaymentIntent { let decrypted_data = crypto_operation( state, type_name!(Self::DstType), - CryptoOperation::BatchDecrypt(EncryptedPaymentIntentAddress::to_encryptable( - EncryptedPaymentIntentAddress { - billing: storage_model.billing_address, - shipping: storage_model.shipping_address, + CryptoOperation::BatchDecrypt(super::EncryptedPaymentIntent::to_encryptable( + super::EncryptedPaymentIntent { + billing_address: storage_model.billing_address, + shipping_address: storage_model.shipping_address, customer_details: storage_model.customer_details, }, )), @@ -1254,7 +1253,7 @@ impl behaviour::Conversion for PaymentIntent { .await .and_then(|val| val.try_into_batchoperation())?; - let data = EncryptedPaymentIntentAddress::from_encryptable(decrypted_data) + let data = super::EncryptedPaymentIntent::from_encryptable(decrypted_data) .change_context(common_utils::errors::CryptoError::DecodingFailed) .attach_printable("Invalid batch operation data")?; @@ -1275,7 +1274,7 @@ impl behaviour::Conversion for PaymentIntent { }; let billing_address = data - .billing + .billing_address .map(|billing| { billing.deserialize_inner_value(|value| value.parse_value("Address")) }) @@ -1284,7 +1283,7 @@ impl behaviour::Conversion for PaymentIntent { .attach_printable("Error while deserializing Address")?; let shipping_address = data - .shipping + .shipping_address .map(|shipping| { shipping.deserialize_inner_value(|value| value.parse_value("Address")) }) @@ -1513,10 +1512,10 @@ impl behaviour::Conversion for PaymentIntent { let decrypted_data = crypto_operation( state, type_name!(Self::DstType), - CryptoOperation::BatchDecrypt(EncryptedPaymentIntentAddress::to_encryptable( - EncryptedPaymentIntentAddress { - billing: storage_model.billing_details, - shipping: storage_model.shipping_details, + CryptoOperation::BatchDecrypt(super::EncryptedPaymentIntent::to_encryptable( + super::EncryptedPaymentIntent { + billing_details: storage_model.billing_details, + shipping_details: storage_model.shipping_details, customer_details: storage_model.customer_details, }, )), @@ -1526,7 +1525,7 @@ impl behaviour::Conversion for PaymentIntent { .await .and_then(|val| val.try_into_batchoperation())?; - let data = EncryptedPaymentIntentAddress::from_encryptable(decrypted_data) + let data = super::EncryptedPaymentIntent::from_encryptable(decrypted_data) .change_context(common_utils::errors::CryptoError::DecodingFailed) .attach_printable("Invalid batch operation data")?; @@ -1578,9 +1577,9 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: storage_model.shipping_cost, tax_details: storage_model.tax_details, customer_details: data.customer_details, - billing_details: data.billing, + billing_details: data.billing_details, merchant_order_reference_id: storage_model.merchant_order_reference_id, - shipping_details: data.shipping, + shipping_details: data.shipping_details, is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, organization_id: storage_model.organization_id, skip_external_tax_calculation: storage_model.skip_external_tax_calculation, @@ -1649,73 +1648,3 @@ impl behaviour::Conversion for PaymentIntent { }) } } - -pub struct EncryptedPaymentIntentAddress { - pub shipping: Option, - pub billing: Option, - pub customer_details: Option, -} - -pub struct PaymentAddressFromRequest { - pub shipping: Option>, - pub billing: Option>, - pub customer_details: Option>, -} - -pub struct DecryptedPaymentIntentAddress { - pub shipping: crypto::OptionalEncryptableValue, - pub billing: crypto::OptionalEncryptableValue, - pub customer_details: crypto::OptionalEncryptableValue, -} - -impl ToEncryptable, Encryption> - for EncryptedPaymentIntentAddress -{ - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> CustomResult { - Ok(DecryptedPaymentIntentAddress { - shipping: hashmap.remove("shipping"), - billing: hashmap.remove("billing"), - customer_details: hashmap.remove("customer_details"), - }) - } - - fn to_encryptable(self) -> FxHashMap { - let mut map = FxHashMap::with_capacity_and_hasher(9, Default::default()); - - self.shipping.map(|s| map.insert("shipping".to_string(), s)); - self.billing.map(|s| map.insert("billing".to_string(), s)); - self.customer_details - .map(|s| map.insert("customer_details".to_string(), s)); - map - } -} - -impl - ToEncryptable< - DecryptedPaymentIntentAddress, - Secret, - Secret, - > for PaymentAddressFromRequest -{ - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> CustomResult { - Ok(DecryptedPaymentIntentAddress { - shipping: hashmap.remove("shipping"), - billing: hashmap.remove("billing"), - customer_details: hashmap.remove("customer_details"), - }) - } - - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(9, Default::default()); - - self.shipping.map(|s| map.insert("shipping".to_string(), s)); - self.billing.map(|s| map.insert("billing".to_string(), s)); - self.customer_details - .map(|s| map.insert("customer_details".to_string(), s)); - map - } -} diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index f49f4096340..cffb8267bff 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -15,7 +15,7 @@ use diesel_models::configs; use diesel_models::organization::OrganizationBridge; use error_stack::{report, FutureExt, ResultExt}; use hyperswitch_domain_models::merchant_connector_account::{ - McaFromRequest, McaFromRequestfromUpdate, + FromRequestEncryptableMerchantConnectorAccount, UpdateEncryptableMerchantConnectorAccount, }; use masking::{ExposeInterface, PeekInterface, Secret}; use pm_auth::{connector::plaid::transformers::PlaidAuthType, types as pm_auth_types}; @@ -2095,18 +2095,20 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect let encrypted_data = domain_types::crypto_operation( key_manager_state, type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::BatchEncrypt(McaFromRequestfromUpdate::to_encryptable( - McaFromRequestfromUpdate { - connector_account_details: self.connector_account_details, - connector_wallets_details: - helpers::get_connector_wallets_details_with_apple_pay_certificates( - &self.metadata, - &self.connector_wallets_details, - ) - .await?, - additional_merchant_data: merchant_recipient_data.map(Secret::new), - }, - )), + domain_types::CryptoOperation::BatchEncrypt( + UpdateEncryptableMerchantConnectorAccount::to_encryptable( + UpdateEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), km_types::Identifier::Merchant(key_store.merchant_id.clone()), key_store.key.peek(), ) @@ -2115,9 +2117,10 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while decrypting connector account details".to_string())?; - let encrypted_data = McaFromRequestfromUpdate::from_encryptable(encrypted_data) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while decrypting connector account details")?; + let encrypted_data = + UpdateEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; Ok(storage::MerchantConnectorAccountUpdate::Update { connector_type: Some(self.connector_type), @@ -2262,18 +2265,20 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect let encrypted_data = domain_types::crypto_operation( key_manager_state, type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::BatchEncrypt(McaFromRequestfromUpdate::to_encryptable( - McaFromRequestfromUpdate { - connector_account_details: self.connector_account_details, - connector_wallets_details: - helpers::get_connector_wallets_details_with_apple_pay_certificates( - &self.metadata, - &self.connector_wallets_details, - ) - .await?, - additional_merchant_data: merchant_recipient_data.map(Secret::new), - }, - )), + domain_types::CryptoOperation::BatchEncrypt( + UpdateEncryptableMerchantConnectorAccount::to_encryptable( + UpdateEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), km_types::Identifier::Merchant(key_store.merchant_id.clone()), key_store.key.peek(), ) @@ -2282,9 +2287,10 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while decrypting connector account details".to_string())?; - let encrypted_data = McaFromRequestfromUpdate::from_encryptable(encrypted_data) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while decrypting connector account details")?; + let encrypted_data = + UpdateEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; Ok(storage::MerchantConnectorAccountUpdate::Update { connector_type: Some(self.connector_type), @@ -2404,22 +2410,24 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { let encrypted_data = domain_types::crypto_operation( key_manager_state, type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::BatchEncrypt(McaFromRequest::to_encryptable( - McaFromRequest { - connector_account_details: self.connector_account_details.ok_or( - errors::ApiErrorResponse::MissingRequiredField { - field_name: "connector_account_details", - }, - )?, - connector_wallets_details: - helpers::get_connector_wallets_details_with_apple_pay_certificates( - &self.metadata, - &self.connector_wallets_details, - ) - .await?, - additional_merchant_data: merchant_recipient_data.map(Secret::new), - }, - )), + domain_types::CryptoOperation::BatchEncrypt( + FromRequestEncryptableMerchantConnectorAccount::to_encryptable( + FromRequestEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "connector_account_details", + }, + )?, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), identifier.clone(), key_store.key.peek(), ) @@ -2428,9 +2436,10 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while decrypting connector account details".to_string())?; - let encrypted_data = McaFromRequest::from_encryptable(encrypted_data) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while decrypting connector account details")?; + let encrypted_data = + FromRequestEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; Ok(domain::MerchantConnectorAccount { merchant_id: business_profile.merchant_id.clone(), @@ -2573,22 +2582,24 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { let encrypted_data = domain_types::crypto_operation( key_manager_state, type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::BatchEncrypt(McaFromRequest::to_encryptable( - McaFromRequest { - connector_account_details: self.connector_account_details.ok_or( - errors::ApiErrorResponse::MissingRequiredField { - field_name: "connector_account_details", - }, - )?, - connector_wallets_details: - helpers::get_connector_wallets_details_with_apple_pay_certificates( - &self.metadata, - &self.connector_wallets_details, - ) - .await?, - additional_merchant_data: merchant_recipient_data.map(Secret::new), - }, - )), + domain_types::CryptoOperation::BatchEncrypt( + FromRequestEncryptableMerchantConnectorAccount::to_encryptable( + FromRequestEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "connector_account_details", + }, + )?, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), identifier.clone(), key_store.key.peek(), ) @@ -2597,9 +2608,10 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while decrypting connector account details".to_string())?; - let encrypted_data = McaFromRequest::from_encryptable(encrypted_data) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while decrypting connector account details")?; + let encrypted_data = + FromRequestEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; Ok(domain::MerchantConnectorAccount { merchant_id: business_profile.merchant_id.clone(), diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 18cc8682eee..6d47ac174f3 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -1,16 +1,15 @@ -use api_models::customers::CustomerRequestWithEmail; use common_utils::{ crypto::Encryptable, errors::ReportSwitchExt, ext_traits::{AsyncExt, OptionExt}, - id_type, type_name, + id_type, pii, type_name, types::{ keymanager::{Identifier, KeyManagerState, ToEncryptable}, Description, }, }; use error_stack::{report, ResultExt}; -use masking::{Secret, SwitchStrategy}; +use masking::{ExposeInterface, Secret, SwitchStrategy}; use router_env::{instrument, tracing}; #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -145,13 +144,15 @@ impl CustomerCreateBridge for customers::CustomerRequest { let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: self.name.clone(), - email: self.email.clone(), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: self.name.clone(), + email: self.email.clone().map(|a| a.expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -160,8 +161,9 @@ impl CustomerCreateBridge for customers::CustomerRequest { .switch() .attach_printable("Failed while encrypting Customer")?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; Ok(domain::Customer { customer_id: merchant_reference_id @@ -169,7 +171,13 @@ impl CustomerCreateBridge for customers::CustomerRequest { .ok_or(errors::CustomersErrorResponse::InternalServerError)?, merchant_id: merchant_id.to_owned(), name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, description: self.description.clone(), phone_country_code: self.phone_country_code.clone(), @@ -234,13 +242,15 @@ impl CustomerCreateBridge for customers::CustomerRequest { let encrypted_data = types::crypto_operation( key_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: Some(self.name.clone()), - email: Some(self.email.clone()), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: Some(self.name.clone()), + email: Some(self.email.clone().expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -249,15 +259,22 @@ impl CustomerCreateBridge for customers::CustomerRequest { .switch() .attach_printable("Failed while encrypting Customer")?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; Ok(domain::Customer { id: common_utils::generate_time_ordered_id("cus"), merchant_reference_id: merchant_reference_id.to_owned(), merchant_id, name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, description: self.description.clone(), phone_country_code: self.phone_country_code.clone(), @@ -1174,13 +1191,18 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: self.name.clone(), - email: self.email.clone(), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: self.name.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -1188,8 +1210,9 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { .and_then(|val| val.try_into_batchoperation()) .switch()?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; let response = db .update_customer_by_customer_id_merchant_id( @@ -1199,7 +1222,14 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { domain_customer.to_owned(), storage::CustomerUpdate::Update { name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: Box::new(encryptable_customer.phone), phone_country_code: self.phone_country_code.clone(), metadata: self.metadata.clone(), @@ -1266,13 +1296,18 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: self.name.clone(), - email: self.email.clone(), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: self.name.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -1280,8 +1315,9 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { .and_then(|val| val.try_into_batchoperation()) .switch()?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; let response = db .update_customer_by_global_id( @@ -1291,7 +1327,14 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { merchant_account.get_id(), storage::CustomerUpdate::Update { name: encryptable_customer.name, - email: Box::new(encryptable_customer.email), + email: Box::new(encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + })), phone: Box::new(encryptable_customer.phone), phone_country_code: self.phone_country_code.clone(), metadata: self.metadata.clone(), diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index ea0d5d2bf83..5a8bc5cd12b 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1,12 +1,9 @@ use std::{borrow::Cow, str::FromStr}; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -use api_models::customers::CustomerRequestWithEmail; use api_models::{ mandates::RecurringDetails, payments::{ - additional_info as payment_additional_types, AddressDetailsWithPhone, PaymentChargeRequest, - RequestSurchargeDetails, + additional_info as payment_additional_types, PaymentChargeRequest, RequestSurchargeDetails, }, }; use base64::Engine; @@ -39,7 +36,7 @@ use hyperswitch_domain_models::{ }; use hyperswitch_interfaces::integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject}; use josekit::jwe; -use masking::{ExposeInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface, SwitchStrategy}; use openssl::{ derive::Deriver, pkey::PKey, @@ -126,16 +123,33 @@ pub async fn create_or_update_address_for_payment_by_request( let encrypted_data = types::crypto_operation( &session_state.into(), type_name!(domain::Address), - types::CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: address.address.clone(), - phone_number: address - .phone - .as_ref() - .and_then(|phone| phone.number.clone()), - email: address.email.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address.address.as_ref().and_then(|a| a.line1.clone()), + line2: address.address.as_ref().and_then(|a| a.line2.clone()), + line3: address.address.as_ref().and_then(|a| a.line3.clone()), + state: address.address.as_ref().and_then(|a| a.state.clone()), + first_name: address + .address + .as_ref() + .and_then(|a| a.first_name.clone()), + last_name: address + .address + .as_ref() + .and_then(|a| a.last_name.clone()), + zip: address.address.as_ref().and_then(|a| a.zip.clone()), + phone_number: address + .phone + .as_ref() + .and_then(|phone| phone.number.clone()), + email: address + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), + }, + ), + ), Identifier::Merchant(merchant_key_store.merchant_id.clone()), key, ) @@ -143,9 +157,10 @@ pub async fn create_or_update_address_for_payment_by_request( .and_then(|val| val.try_into_batchoperation()) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while encrypting address")?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while encrypting address")?; + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting address")?; let address_update = storage::AddressUpdate::Update { city: address .address @@ -165,7 +180,14 @@ pub async fn create_or_update_address_for_payment_by_request( .as_ref() .and_then(|value| value.country_code.clone()), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }; let address = db .find_address_by_merchant_id_payment_id_address_id( @@ -317,23 +339,35 @@ pub async fn get_domain_address( let encrypted_data = types::crypto_operation( &session_state.into(), type_name!(domain::Address), - types::CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: address_details.cloned(), - phone_number: address - .phone - .as_ref() - .and_then(|phone| phone.number.clone()), - email: address.email.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address.address.as_ref().and_then(|a| a.line1.clone()), + line2: address.address.as_ref().and_then(|a| a.line2.clone()), + line3: address.address.as_ref().and_then(|a| a.line3.clone()), + state: address.address.as_ref().and_then(|a| a.state.clone()), + first_name: address.address.as_ref().and_then(|a| a.first_name.clone()), + last_name: address.address.as_ref().and_then(|a| a.last_name.clone()), + zip: address.address.as_ref().and_then(|a| a.zip.clone()), + phone_number: address + .phone + .as_ref() + .and_then(|phone| phone.number.clone()), + email: address + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), + }, + ), + ), Identifier::Merchant(merchant_id.to_owned()), key, ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; Ok(domain::Address { phone_number: encryptable_address.phone_number, country_code: address.phone.as_ref().and_then(|a| a.country_code.clone()), @@ -351,7 +385,14 @@ pub async fn get_domain_address( modified_at: common_utils::date_time::now(), zip: encryptable_address.zip, updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } .await @@ -1589,13 +1630,18 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: request_customer_details.name.clone(), - email: request_customer_details.email.clone(), - phone: request_customer_details.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: request_customer_details.name.clone(), + email: request_customer_details + .email + .as_ref() + .map(|e| e.clone().expose().switch_strategy()), + phone: request_customer_details.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -1603,9 +1649,10 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( .and_then(|val| val.try_into_batchoperation()) .change_context(errors::StorageError::SerializationFailed) .attach_printable("Failed while encrypting Customer while Update")?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::StorageError::SerializationFailed) - .attach_printable("Failed while encrypting Customer while Update")?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::StorageError::SerializationFailed) + .attach_printable("Failed while encrypting Customer while Update")?; Some(match customer_data { Some(c) => { // Update the customer data if new data is passed in the request @@ -1616,7 +1663,15 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( { let customer_update = Update { name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable< + masking::Secret, + > = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: Box::new(encryptable_customer.phone), phone_country_code: request_customer_details.phone_country_code, description: None, @@ -1644,7 +1699,15 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( customer_id, merchant_id: merchant_id.to_owned(), name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable< + masking::Secret, + > = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, phone_country_code: request_customer_details.phone_country_code.clone(), description: None, diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index a9b5b571c5e..d4057c523bc 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -21,8 +21,8 @@ use error_stack::{self, ResultExt}; use hyperswitch_domain_models::{ mandates::{MandateData, MandateDetails}, payments::{ - payment_attempt::PaymentAttempt, - payment_intent::{CustomerData, PaymentAddressFromRequest}, + payment_attempt::PaymentAttempt, payment_intent::CustomerData, + FromRequestEncryptablePaymentIntent, }, }; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -1390,11 +1390,13 @@ impl PaymentCreate { &key_manager_state, type_name!(storage::PaymentIntent), domain::types::CryptoOperation::BatchEncrypt( - PaymentAddressFromRequest::to_encryptable(PaymentAddressFromRequest { - shipping: shipping_details_encoded, - billing: billing_details_encoded, - customer_details: customer_details_encoded, - }), + FromRequestEncryptablePaymentIntent::to_encryptable( + FromRequestEncryptablePaymentIntent { + shipping_details: shipping_details_encoded, + billing_details: billing_details_encoded, + customer_details: customer_details_encoded, + }, + ), ), identifier.clone(), key, @@ -1404,7 +1406,7 @@ impl PaymentCreate { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt data")?; - let encrypted_data = PaymentAddressFromRequest::from_encryptable(encrypted_data) + let encrypted_data = FromRequestEncryptablePaymentIntent::from_encryptable(encrypted_data) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt the payment intent data")?; @@ -1457,10 +1459,10 @@ impl PaymentCreate { .request_external_three_ds_authentication, charges, frm_metadata: request.frm_metadata.clone(), - billing_details: encrypted_data.billing, + billing_details: encrypted_data.billing_details, customer_details: encrypted_data.customer_details, merchant_order_reference_id: request.merchant_order_reference_id.clone(), - shipping_details: encrypted_data.shipping, + shipping_details: encrypted_data.shipping_details, is_payment_processor_token_flow, organization_id: merchant_account.organization_id.clone(), shipping_cost: request.shipping_cost, diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 46f45905448..49b18e53a0c 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -1,11 +1,10 @@ -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -use api_models::customers::CustomerRequestWithEmail; use api_models::{enums, payment_methods::Card, payouts}; use common_utils::{ + crypto::Encryptable, encryption::Encryption, errors::CustomResult, ext_traits::{AsyncExt, StringExt}, - fp_utils, id_type, payout_method_utils as payout_additional, type_name, + fp_utils, id_type, payout_method_utils as payout_additional, pii, type_name, types::{ keymanager::{Identifier, KeyManagerState}, MinorUnit, UnifiedCode, UnifiedMessage, @@ -15,7 +14,7 @@ use common_utils::{ use common_utils::{generate_customer_id_of_default_length, types::keymanager::ToEncryptable}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation}; -use masking::{PeekInterface, Secret}; +use masking::{ExposeInterface, PeekInterface, Secret, SwitchStrategy}; use router_env::logger; use super::PayoutData; @@ -696,13 +695,18 @@ pub(super) async fn get_or_create_customer_details( let encrypted_data = crypto_operation( &state.into(), type_name!(domain::Customer), - CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: customer_details.name.clone(), - email: customer_details.email.clone(), - phone: customer_details.phone.clone(), - }, - )), + CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: customer_details.name.clone(), + email: customer_details + .email + .clone() + .map(|a| a.expose().switch_strategy()), + phone: customer_details.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -711,7 +715,7 @@ pub(super) async fn get_or_create_customer_details( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to encrypt customer")?; let encryptable_customer = - CustomerRequestWithEmail::from_encryptable(encrypted_data) + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to form EncryptableCustomer")?; @@ -719,7 +723,14 @@ pub(super) async fn get_or_create_customer_details( customer_id: customer_id.clone(), merchant_id: merchant_id.to_owned().clone(), name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, description: None, phone_country_code: customer_details.phone_country_code.to_owned(), diff --git a/crates/router/src/types/domain/address.rs b/crates/router/src/types/domain/address.rs index 5d9af527cd4..2d1e98ec1b8 100644 --- a/crates/router/src/types/domain/address.rs +++ b/crates/router/src/types/domain/address.rs @@ -4,30 +4,38 @@ use common_utils::{ date_time, encryption::Encryption, errors::{CustomResult, ValidationError}, - id_type, type_name, + id_type, pii, type_name, types::keymanager::{Identifier, KeyManagerState, ToEncryptable}, }; use diesel_models::{address::AddressUpdateInternal, enums}; use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; +use masking::{PeekInterface, Secret, SwitchStrategy}; use rustc_hash::FxHashMap; use time::{OffsetDateTime, PrimitiveDateTime}; use super::{behaviour, types}; -#[derive(Clone, Debug, serde::Serialize)] +#[derive(Clone, Debug, serde::Serialize, router_derive::ToEncryption)] pub struct Address { pub address_id: String, pub city: Option, pub country: Option, - pub line1: crypto::OptionalEncryptableSecretString, - pub line2: crypto::OptionalEncryptableSecretString, - pub line3: crypto::OptionalEncryptableSecretString, - pub state: crypto::OptionalEncryptableSecretString, - pub zip: crypto::OptionalEncryptableSecretString, - pub first_name: crypto::OptionalEncryptableSecretString, - pub last_name: crypto::OptionalEncryptableSecretString, - pub phone_number: crypto::OptionalEncryptableSecretString, + #[encrypt] + pub line1: Option>>, + #[encrypt] + pub line2: Option>>, + #[encrypt] + pub line3: Option>>, + #[encrypt] + pub state: Option>>, + #[encrypt] + pub zip: Option>>, + #[encrypt] + pub first_name: Option>>, + #[encrypt] + pub last_name: Option>>, + #[encrypt] + pub phone_number: Option>>, pub country_code: Option, #[serde(skip_serializing)] #[serde(with = "custom_serde::iso8601")] @@ -37,7 +45,8 @@ pub struct Address { pub modified_at: PrimitiveDateTime, pub merchant_id: id_type::MerchantId, pub updated_by: String, - pub email: crypto::OptionalEncryptableEmail, + #[encrypt] + pub email: Option>>, } /// Based on the flow, appropriate address has to be used @@ -191,8 +200,18 @@ impl behaviour::Conversion for Address { let decrypted: FxHashMap>> = types::crypto_operation( state, type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(diesel_models::Address::to_encryptable( - other.clone(), + types::CryptoOperation::BatchDecrypt(EncryptedAddress::to_encryptable( + EncryptedAddress { + line1: other.line1, + line2: other.line2, + line3: other.line3, + state: other.state, + zip: other.zip, + first_name: other.first_name, + last_name: other.last_name, + phone_number: other.phone_number, + email: other.email, + }, )), identifier.clone(), key.peek(), @@ -202,10 +221,11 @@ impl behaviour::Conversion for Address { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting".to_string(), })?; - let encryptable_address = diesel_models::Address::from_encryptable(decrypted) - .change_context(ValidationError::InvalidValue { + let encryptable_address = EncryptedAddress::from_encryptable(decrypted).change_context( + ValidationError::InvalidValue { message: "Failed while decrypting".to_string(), - })?; + }, + )?; Ok(Self { address_id: other.address_id, city: other.city, @@ -223,7 +243,13 @@ impl behaviour::Conversion for Address { modified_at: other.modified_at, updated_by: other.updated_by, merchant_id: other.merchant_id, - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } diff --git a/crates/router/src/types/domain/event.rs b/crates/router/src/types/domain/event.rs index badbb9df7b4..db2d7530164 100644 --- a/crates/router/src/types/domain/event.rs +++ b/crates/router/src/types/domain/event.rs @@ -1,22 +1,23 @@ use common_utils::{ - crypto::OptionalEncryptableSecretString, + crypto::{Encryptable, OptionalEncryptableSecretString}, + encryption::Encryption, type_name, types::keymanager::{KeyManagerState, ToEncryptable}, }; use diesel_models::{ enums::{EventClass, EventObjectType, EventType, WebhookDeliveryAttempt}, events::{EventMetadata, EventUpdateInternal}, - EventWithEncryption, }; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; +use rustc_hash::FxHashMap; use crate::{ errors::{CustomResult, ValidationError}, types::domain::types, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct Event { pub event_id: String, pub event_type: EventType, @@ -30,8 +31,10 @@ pub struct Event { pub primary_object_created_at: Option, pub idempotent_event_id: Option, pub initial_attempt_id: Option, - pub request: OptionalEncryptableSecretString, - pub response: OptionalEncryptableSecretString, + #[encrypt] + pub request: Option>>, + #[encrypt] + pub response: Option>>, pub delivery_attempt: Option, pub metadata: Option, } @@ -96,12 +99,10 @@ impl super::behaviour::Conversion for Event { let decrypted = types::crypto_operation( state, type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(EventWithEncryption::to_encryptable( - EventWithEncryption { - request: item.request.clone(), - response: item.response.clone(), - }, - )), + types::CryptoOperation::BatchDecrypt(EncryptedEvent::to_encryptable(EncryptedEvent { + request: item.request.clone(), + response: item.response.clone(), + })), key_manager_identifier, key.peek(), ) @@ -110,7 +111,7 @@ impl super::behaviour::Conversion for Event { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting event data".to_string(), })?; - let encryptable_event = EventWithEncryption::from_encryptable(decrypted).change_context( + let encryptable_event = EncryptedEvent::from_encryptable(decrypted).change_context( ValidationError::InvalidValue { message: "Failed while decrypting event data".to_string(), }, diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index f56a741dc1a..515c9a94ce8 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -13,8 +13,6 @@ pub mod user_role; pub mod verify_connector; use std::fmt::Debug; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -use api_models::payments::AddressDetailsWithPhone; use api_models::{ enums, payments::{self}, @@ -22,10 +20,10 @@ use api_models::{ }; use common_utils::types::keymanager::KeyManagerState; pub use common_utils::{ - crypto, + crypto::{self, Encryptable}, ext_traits::{ByteSliceExt, BytesExt, Encode, StringExt, ValueExt}, fp_utils::when, - id_type, + id_type, pii, validation::validate_email, }; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] @@ -38,6 +36,7 @@ pub use hyperswitch_connectors::utils::QrImage; use hyperswitch_domain_models::payments::PaymentIntent; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation}; +use masking::{ExposeInterface, SwitchStrategy}; use nanoid::nanoid; use router_env::metrics::add_attributes; use serde::de::DeserializeOwned; @@ -779,20 +778,32 @@ impl CustomerAddress for api_models::customers::CustomerRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), - Identifier::Merchant(merchant_id), + Identifier::Merchant(merchant_id.to_owned()), key, ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + Ok(storage::AddressUpdate::Update { city: address_details.city, country: address_details.country, @@ -806,7 +817,14 @@ impl CustomerAddress for api_models::customers::CustomerRequest { phone_number: encryptable_address.phone_number, country_code: self.phone_country_code.clone(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } @@ -822,11 +840,20 @@ impl CustomerAddress for api_models::customers::CustomerRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -834,8 +861,11 @@ impl CustomerAddress for api_models::customers::CustomerRequest { ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + let address = domain::Address { city: address_details.city, country: address_details.country, @@ -853,7 +883,14 @@ impl CustomerAddress for api_models::customers::CustomerRequest { created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }; Ok(domain::CustomerAddress { @@ -877,20 +914,31 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), - Identifier::Merchant(merchant_id), + Identifier::Merchant(merchant_id.to_owned()), key, ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; Ok(storage::AddressUpdate::Update { city: address_details.city, country: address_details.country, @@ -904,7 +952,14 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { phone_number: encryptable_address.phone_number, country_code: self.phone_country_code.clone(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } @@ -920,11 +975,20 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -932,8 +996,10 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; let address = domain::Address { city: address_details.city, country: address_details.country, @@ -951,7 +1017,14 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }; Ok(domain::CustomerAddress { diff --git a/crates/router_derive/src/lib.rs b/crates/router_derive/src/lib.rs index 69865512a37..33edd5e215e 100644 --- a/crates/router_derive/src/lib.rs +++ b/crates/router_derive/src/lib.rs @@ -644,6 +644,7 @@ pub fn try_get_enum_variant(input: proc_macro::TokenStream) -> proc_macro::Token /// ("address.zip", "941222"), /// ("email", "test@example.com"), /// ] +/// #[proc_macro_derive(FlatStruct)] pub fn flat_struct_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); @@ -749,3 +750,18 @@ pub fn flat_struct_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt pub fn generate_permissions(input: proc_macro::TokenStream) -> proc_macro::TokenStream { macros::generate_permissions_inner(input) } + +/// Generates the ToEncryptable trait for a type +/// +/// This macro generates the temporary structs which has the fields that needs to be encrypted +/// +/// fn to_encryptable: Convert the temp struct to a hashmap that can be sent over the network +/// fn from_encryptable: Convert the hashmap back to temp struct +#[proc_macro_derive(ToEncryption, attributes(encrypt))] +pub fn derive_to_encryption_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + macros::derive_to_encryption(input) + .unwrap_or_else(|err| err.into_compile_error()) + .into() +} diff --git a/crates/router_derive/src/macros.rs b/crates/router_derive/src/macros.rs index 32e6c213ca6..e227c6533e9 100644 --- a/crates/router_derive/src/macros.rs +++ b/crates/router_derive/src/macros.rs @@ -4,6 +4,7 @@ pub(crate) mod generate_permissions; pub(crate) mod generate_schema; pub(crate) mod misc; pub(crate) mod operation; +pub(crate) mod to_encryptable; pub(crate) mod try_get_enum; mod helpers; @@ -17,6 +18,7 @@ pub(crate) use self::{ diesel::{diesel_enum_derive_inner, diesel_enum_text_derive_inner}, generate_permissions::generate_permissions_inner, generate_schema::polymorphic_macro_derive_inner, + to_encryptable::derive_to_encryption, }; pub(crate) fn debug_as_display_inner(ast: &DeriveInput) -> syn::Result { diff --git a/crates/router_derive/src/macros/to_encryptable.rs b/crates/router_derive/src/macros/to_encryptable.rs new file mode 100644 index 00000000000..dfcfb72169b --- /dev/null +++ b/crates/router_derive/src/macros/to_encryptable.rs @@ -0,0 +1,326 @@ +use std::iter::Iterator; + +use quote::{format_ident, quote}; +use syn::{parse::Parse, punctuated::Punctuated, token::Comma, Field, Ident, Type as SynType}; + +use crate::macros::{helpers::get_struct_fields, misc::get_field_type}; + +pub struct FieldMeta { + _meta_type: Ident, + pub value: Ident, +} + +impl Parse for FieldMeta { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let _meta_type: Ident = input.parse()?; + input.parse::()?; + let value: Ident = input.parse()?; + Ok(Self { _meta_type, value }) + } +} + +impl quote::ToTokens for FieldMeta { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.value.to_tokens(tokens); + } +} + +fn get_encryption_ty_meta(field: &Field) -> Option { + let attrs = &field.attrs; + + attrs + .iter() + .flat_map(|s| s.parse_args::()) + .find(|s| s._meta_type.eq("ty")) +} + +fn get_inner_type(path: &syn::TypePath) -> syn::Result { + path.path + .segments + .last() + .and_then(|segment| match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => args.args.first(), + _ => None, + }) + .and_then(|arg| match arg { + syn::GenericArgument::Type(SynType::Path(path)) => Some(path.clone()), + _ => None, + }) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "Only path fields are supported", + ) + }) +} + +/// This function returns the inner most type recursively +/// For example: +/// +/// In the case of `Encryptable>> this returns String +fn get_inner_most_type(ty: SynType) -> syn::Result { + fn get_inner_type_recursive(path: syn::TypePath) -> syn::Result { + match get_inner_type(&path) { + Ok(inner_path) => get_inner_type_recursive(inner_path), + Err(_) => Ok(path), + } + } + + match ty { + SynType::Path(path) => { + let inner_path = get_inner_type_recursive(path)?; + inner_path + .path + .segments + .last() + .map(|last_segment| last_segment.ident.to_owned()) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "At least one ident must be specified", + ) + }) + } + _ => Err(syn::Error::new( + proc_macro2::Span::call_site(), + "Only path fields are supported", + )), + } +} + +/// This returns the field which implement #[encrypt] attribute +fn get_encryptable_fields(fields: Punctuated) -> Vec { + fields + .into_iter() + .filter(|field| { + field + .attrs + .iter() + .any(|attr| attr.path().is_ident("encrypt")) + }) + .collect() +} + +/// This function returns the inner most type of a field +fn get_field_and_inner_types(fields: &[Field]) -> Vec<(Field, Ident)> { + fields + .iter() + .flat_map(|field| { + get_inner_most_type(field.ty.clone()).map(|field_name| (field.to_owned(), field_name)) + }) + .collect() +} + +/// The type of the struct for which the batch encryption/decryption needs to be implemented +#[derive(PartialEq, Copy, Clone)] +enum StructType { + Encrypted, + Decrypted, + DecryptedUpdate, + FromRequest, + Updated, +} + +impl StructType { + /// Generates the fields for temporary structs which consists of the fields that should be + /// encrypted/decrypted + fn generate_struct_fields(&self, fields: &[(Field, Ident)]) -> Vec { + fields + .iter() + .map(|(field, inner_ty)| { + let provided_ty = get_encryption_ty_meta(field); + let is_option = get_field_type(field.ty.clone()) + .map(|f| f.eq("Option")) + .unwrap_or_default(); + let ident = &field.ident; + let inner_ty = if let Some(ref ty) = provided_ty { + &ty.value + } else { + inner_ty + }; + match (self, is_option) { + (Self::Encrypted, true) => quote! { pub #ident: Option }, + (Self::Encrypted, false) => quote! { pub #ident: Encryption }, + (Self::Decrypted, true) => { + quote! { pub #ident: Option>> } + } + (Self::Decrypted, false) => { + quote! { pub #ident: Encryptable> } + } + (Self::DecryptedUpdate, _) => { + quote! { pub #ident: Option>> } + } + (Self::FromRequest, true) => { + quote! { pub #ident: Option> } + } + (Self::FromRequest, false) => quote! { pub #ident: Secret<#inner_ty> }, + (Self::Updated, _) => quote! { pub #ident: Option> }, + } + }) + .collect() + } + + /// Generates the ToEncryptable trait implementation + fn generate_impls( + &self, + gen1: proc_macro2::TokenStream, + gen2: proc_macro2::TokenStream, + gen3: proc_macro2::TokenStream, + impl_st: proc_macro2::TokenStream, + inner: &[Field], + ) -> proc_macro2::TokenStream { + let map_length = inner.len(); + + let to_encryptable_impl = inner.iter().flat_map(|field| { + get_field_type(field.ty.clone()).map(|field_ty| { + let is_option = field_ty.eq("Option"); + let field_ident = &field.ident; + let field_ident_string = field_ident.as_ref().map(|s| s.to_string()); + + if is_option || *self == Self::Updated { + quote! { self.#field_ident.map(|s| map.insert(#field_ident_string.to_string(), s)) } + } else { + quote! { map.insert(#field_ident_string.to_string(), self.#field_ident) } + } + }) + }); + + let from_encryptable_impl = inner.iter().flat_map(|field| { + get_field_type(field.ty.clone()).map(|field_ty| { + let is_option = field_ty.eq("Option"); + let field_ident = &field.ident; + let field_ident_string = field_ident.as_ref().map(|s| s.to_string()); + + if is_option || *self == Self::Updated { + quote! { #field_ident: map.remove(#field_ident_string) } + } else { + quote! { + #field_ident: map.remove(#field_ident_string).ok_or( + error_stack::report!(common_utils::errors::ParsingError::EncodeError( + "Unable to convert from HashMap", + )) + )? + } + } + }) + }); + + quote! { + impl ToEncryptable<#gen1, #gen2, #gen3> for #impl_st { + fn to_encryptable(self) -> FxHashMap { + let mut map = FxHashMap::with_capacity_and_hasher(#map_length, Default::default()); + #(#to_encryptable_impl;)* + map + } + + fn from_encryptable( + mut map: FxHashMap>, + ) -> CustomResult<#gen1, common_utils::errors::ParsingError> { + Ok(#gen1 { + #(#from_encryptable_impl,)* + }) + } + } + } + } +} + +/// This function generates the temporary struct and ToEncryptable impls for the temporary structs +fn generate_to_encryptable( + struct_name: Ident, + fields: Vec, +) -> syn::Result { + let struct_types = [ + // The first two are to be used as return types we do not need to implement ToEncryptable + // on it + ("Decrypted", StructType::Decrypted), + ("DecryptedUpdate", StructType::DecryptedUpdate), + ("FromRequestEncryptable", StructType::FromRequest), + ("Encrypted", StructType::Encrypted), + ("UpdateEncryptable", StructType::Updated), + ]; + + let inner_types = get_field_and_inner_types(&fields); + + let inner_type = inner_types.first().map(|(_, ty)| ty).ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "Please use the macro with attribute #[encrypt] on the fields you want to encrypt", + ) + })?; + + let structs = struct_types.iter().map(|(prefix, struct_type)| { + let name = format_ident!("{}{}", prefix, struct_name); + let temp_fields = struct_type.generate_struct_fields(&inner_types); + quote! { + pub struct #name { + #(#temp_fields,)* + } + } + }); + + // These implementations shouldn't be implemented Decrypted and DecryptedUpdate temp structs + // So skip the first two entries in the list + let impls = struct_types + .iter() + .skip(2) + .map(|(prefix, struct_type)| { + let name = format_ident!("{}{}", prefix, struct_name); + + let impl_block = if *struct_type != StructType::DecryptedUpdate + || *struct_type != StructType::Decrypted + { + let (gen1, gen2, gen3) = match struct_type { + StructType::FromRequest => { + let decrypted_name = format_ident!("Decrypted{}", struct_name); + ( + quote! { #decrypted_name }, + quote! { Secret<#inner_type> }, + quote! { Secret<#inner_type> }, + ) + } + StructType::Encrypted => { + let decrypted_name = format_ident!("Decrypted{}", struct_name); + ( + quote! { #decrypted_name }, + quote! { Secret<#inner_type> }, + quote! { Encryption }, + ) + } + StructType::Updated => { + let decrypted_update_name = format_ident!("DecryptedUpdate{}", struct_name); + ( + quote! { #decrypted_update_name }, + quote! { Secret<#inner_type> }, + quote! { Secret<#inner_type> }, + ) + } + //Unreachable statement + _ => (quote! {}, quote! {}, quote! {}), + }; + + struct_type.generate_impls(gen1, gen2, gen3, quote! { #name }, &fields) + } else { + quote! {} + }; + + Ok(quote! { + #impl_block + }) + }) + .collect::>>()?; + + Ok(quote! { + #(#structs)* + #(#impls)* + }) +} + +pub fn derive_to_encryption( + input: syn::DeriveInput, +) -> Result { + let struct_name = input.ident; + let fields = get_encryptable_fields(get_struct_fields(input.data)?); + + generate_to_encryptable(struct_name, fields) +}