feat: encrypt PII fields before saving it in the database (#1043)

Co-authored-by: Nishant Joshi <nishant.joshi@juspay.in>
This commit is contained in:
Kartikeya Hegde
2023-05-30 13:43:17 +05:30
committed by GitHub
parent 77e60c82fa
commit fa392c40a8
107 changed files with 3818 additions and 1267 deletions

View File

@ -2,14 +2,14 @@ use std::borrow::Cow;
use common_utils::{
ext_traits::{AsyncExt, ByteSliceExt, ValueExt},
fp_utils, pii,
fp_utils, generate_id, pii,
};
// TODO : Evaluate all the helper functions ()
use error_stack::{report, IntoReport, ResultExt};
use josekit::jwe;
use masking::{ExposeOptionInterface, PeekInterface};
use masking::{ExposeInterface, PeekInterface};
use router_env::{instrument, tracing};
use storage_models::{enums, merchant_account, payment_intent};
use storage_models::{enums, payment_intent};
use time::Duration;
use uuid::Uuid;
@ -30,10 +30,14 @@ use crate::{
scheduler::{metrics as scheduler_metrics, workflows::payment_sync},
services,
types::{
self,
api::{self, admin, enums as api_enums, CustomerAcceptanceExt, MandateValidationFieldsExt},
domain::{
self,
types::{self, AsyncLift},
},
storage::{self, enums as storage_enums, ephemeral_key},
transformers::ForeignInto,
ErrorResponse, RouterData,
},
utils::{
self,
@ -43,11 +47,9 @@ use crate::{
};
pub fn filter_mca_based_on_business_details(
merchant_connector_accounts: Vec<
storage_models::merchant_connector_account::MerchantConnectorAccount,
>,
merchant_connector_accounts: Vec<domain::MerchantConnectorAccount>,
payment_intent: Option<&storage_models::payment_intent::PaymentIntent>,
) -> Vec<storage_models::merchant_connector_account::MerchantConnectorAccount> {
) -> Vec<domain::MerchantConnectorAccount> {
if let Some(payment_intent) = payment_intent {
merchant_connector_accounts
.into_iter()
@ -67,15 +69,86 @@ pub async fn get_address_for_payment_request(
address_id: Option<&str>,
merchant_id: &str,
customer_id: &Option<String>,
) -> CustomResult<Option<storage::Address>, errors::ApiErrorResponse> {
) -> CustomResult<Option<domain::Address>, errors::ApiErrorResponse> {
let key = types::get_merchant_enc_key(db, merchant_id.to_string())
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while getting key for encryption")?;
Ok(match req_address {
Some(address) => {
match address_id {
Some(id) => Some(
db.update_address(id.to_owned(), address.foreign_into())
.await
.to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?,
),
Some(id) => {
let address_update = async {
Ok(storage::AddressUpdate::Update {
city: address
.address
.as_ref()
.and_then(|value| value.city.clone()),
country: address.address.as_ref().and_then(|value| value.country),
line1: address
.address
.as_ref()
.and_then(|value| value.line1.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
line2: address
.address
.as_ref()
.and_then(|value| value.line2.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
line3: address
.address
.as_ref()
.and_then(|value| value.line3.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
state: address
.address
.as_ref()
.and_then(|value| value.state.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
zip: address
.address
.as_ref()
.and_then(|value| value.zip.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
first_name: address
.address
.as_ref()
.and_then(|value| value.first_name.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
last_name: address
.address
.as_ref()
.and_then(|value| value.last_name.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
phone_number: address
.phone
.as_ref()
.and_then(|value| value.number.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
country_code: address
.phone
.as_ref()
.and_then(|value| value.country_code.clone()),
})
}
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting address")?;
Some(
db.update_address(id.to_owned(), address_update)
.await
.to_not_found_response(errors::ApiErrorResponse::AddressNotFound)?,
)
}
None => {
// generate a new address here
let customer_id = customer_id
@ -85,17 +158,61 @@ pub async fn get_address_for_payment_request(
let address_details = address.address.clone().unwrap_or_default();
Some(
db.insert_address(storage::AddressNew {
phone_number: address.phone.as_ref().and_then(|a| a.number.clone()),
country_code: address
.phone
.as_ref()
.and_then(|a| a.country_code.clone()),
customer_id: customer_id.to_string(),
merchant_id: merchant_id.to_string(),
..address_details.foreign_into()
})
db.insert_address(
async {
Ok(domain::Address {
phone_number: address
.phone
.as_ref()
.and_then(|a| a.number.clone())
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
country_code: address
.phone
.as_ref()
.and_then(|a| a.country_code.clone()),
customer_id: customer_id.to_string(),
merchant_id: merchant_id.to_string(),
address_id: generate_id(consts::ID_LENGTH, "add"),
city: address_details.city,
country: address_details.country,
line1: address_details
.line1
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
line2: address_details
.line2
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
line3: address_details
.line3
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
id: None,
state: address_details
.state
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
created_at: common_utils::date_time::now(),
first_name: address_details
.first_name
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
last_name: address_details
.last_name
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
modified_at: common_utils::date_time::now(),
zip: address_details
.zip
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
})
}
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting address while insert")?,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while inserting new address")?,
@ -115,7 +232,7 @@ pub async fn get_address_for_payment_request(
pub async fn get_address_by_id(
db: &dyn StorageInterface,
address_id: Option<String>,
) -> CustomResult<Option<storage::Address>, errors::ApiErrorResponse> {
) -> CustomResult<Option<domain::Address>, errors::ApiErrorResponse> {
match address_id {
None => Ok(None),
Some(address_id) => Ok(db.find_address(&address_id).await.ok()),
@ -126,7 +243,7 @@ pub async fn get_token_pm_type_mandate_details(
state: &AppState,
request: &api::PaymentsRequest,
mandate_type: Option<api::MandateTxnType>,
merchant_account: &storage::MerchantAccount,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<(
Option<String>,
Option<storage_enums::PaymentMethod>,
@ -160,7 +277,7 @@ pub async fn get_token_pm_type_mandate_details(
pub async fn get_token_for_recurring_mandate(
state: &AppState,
req: &api::PaymentsRequest,
merchant_account: &storage::MerchantAccount,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<(Option<String>, Option<storage_enums::PaymentMethod>)> {
let db = &*state.store;
let mandate_id = req.mandate_id.clone().get_required_value("mandate_id")?;
@ -559,7 +676,7 @@ where
pub(crate) async fn get_payment_method_create_request(
payment_method: Option<&api::PaymentMethodData>,
payment_method_type: Option<storage_enums::PaymentMethod>,
customer: &storage::Customer,
customer: &domain::Customer,
) -> RouterResult<api::PaymentMethodCreate> {
match payment_method {
Some(pm_data) => match payment_method_type {
@ -618,17 +735,21 @@ pub async fn get_customer_from_details<F: Clone>(
customer_id: Option<String>,
merchant_id: &str,
payment_data: &mut PaymentData<F>,
) -> CustomResult<Option<storage::Customer>, errors::StorageError> {
) -> CustomResult<Option<domain::Customer>, errors::StorageError> {
match customer_id {
None => Ok(None),
Some(c_id) => {
let customer = db
.find_customer_optional_by_customer_id_merchant_id(&c_id, merchant_id)
.await?;
payment_data.email = payment_data
.email
.clone()
.or_else(|| customer.as_ref().and_then(|inner| inner.email.clone()));
payment_data.email = payment_data.email.clone().or_else(|| {
customer.as_ref().and_then(|inner| {
inner
.email
.clone()
.map(|encrypted_value| encrypted_value.into())
})
});
Ok(customer)
}
}
@ -651,7 +772,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>(
payment_data: &mut PaymentData<F>,
req: Option<CustomerDetails>,
merchant_id: &str,
) -> CustomResult<(BoxedOperation<'a, F, R>, Option<storage::Customer>), errors::StorageError> {
) -> CustomResult<(BoxedOperation<'a, F, R>, Option<domain::Customer>), errors::StorageError> {
let req = req
.get_required_value("customer")
.change_context(errors::StorageError::ValueNotFound("customer".to_owned()))?;
@ -663,16 +784,39 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>(
Some(match customer_data {
Some(c) => Ok(c),
None => {
let new_customer = storage::CustomerNew {
customer_id: customer_id.to_string(),
merchant_id: merchant_id.to_string(),
name: req.name.expose_option(),
email: req.email.clone(),
phone: req.phone.clone(),
phone_country_code: req.phone_country_code.clone(),
..storage::CustomerNew::default()
};
let key = types::get_merchant_enc_key(db, merchant_id.to_string()).await?;
let new_customer = async {
Ok(domain::Customer {
customer_id: customer_id.to_string(),
merchant_id: merchant_id.to_string(),
name: req
.name
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
email: req
.email
.clone()
.async_lift(|inner| {
types::encrypt_optional(inner.map(|inner| inner.expose()), &key)
})
.await?,
phone: req
.phone
.clone()
.async_lift(|inner| types::encrypt_optional(inner, &key))
.await?,
phone_country_code: req.phone_country_code.clone(),
description: None,
created_at: common_utils::date_time::now(),
id: None,
metadata: None,
modified_at: common_utils::date_time::now(),
connector_customer: None,
})
}
.await
.change_context(errors::StorageError::SerializationFailed)
.attach_printable("Failed while encrypting Customer while insert")?;
metrics::CUSTOMER_CREATED.add(&metrics::CONTEXT, 1, &[]);
db.insert_customer(new_customer).await
}
@ -693,10 +837,12 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>(
let customer = customer?;
payment_data.payment_intent.customer_id = Some(customer.customer_id.clone());
payment_data.email = payment_data
.email
.clone()
.or_else(|| customer.email.clone());
payment_data.email = payment_data.email.clone().or_else(|| {
customer
.email
.clone()
.map(|encrypted_value| encrypted_value.into())
});
Some(customer)
}
@ -970,7 +1116,7 @@ pub(super) fn validate_payment_list_request(
pub fn get_handle_response_url(
payment_id: String,
merchant_account: &storage::MerchantAccount,
merchant_account: &domain::MerchantAccount,
response: api::PaymentsResponse,
connector: String,
) -> RouterResult<api::RedirectionResponse> {
@ -989,7 +1135,7 @@ pub fn get_handle_response_url(
}
pub fn make_merchant_url_with_response(
merchant_account: &storage::MerchantAccount,
merchant_account: &domain::MerchantAccount,
redirection_response: api::PgRedirectResponse,
request_return_url: Option<&String>,
) -> RouterResult<String> {
@ -1081,7 +1227,7 @@ pub fn make_pg_redirect_response(
pub fn make_url_with_signature(
redirect_url: &str,
merchant_account: &storage::MerchantAccount,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<api::RedirectionResponse> {
let mut url = url::Url::parse(redirect_url)
.into_report()
@ -1159,7 +1305,7 @@ pub fn generate_mandate(
merchant_id: String,
connector: String,
setup_mandate_details: Option<api::MandateData>,
customer: &Option<storage::Customer>,
customer: &Option<domain::Customer>,
payment_method_id: String,
connector_mandate_id: Option<pii::SecretSerdeValue>,
network_txn_id: Option<String>,
@ -1286,7 +1432,7 @@ pub(crate) fn validate_pm_or_token_given(
// A function to perform database lookup and then verify the client secret
pub async fn verify_payment_intent_time_and_client_secret(
db: &dyn StorageInterface,
merchant_account: &merchant_account::MerchantAccount,
merchant_account: &domain::MerchantAccount,
client_secret: Option<String>,
) -> error_stack::Result<Option<storage::PaymentIntent>, errors::ApiErrorResponse> {
client_secret
@ -1354,7 +1500,7 @@ pub fn get_connector_label(
pub fn get_business_details(
business_country: Option<api_enums::CountryAlpha2>,
business_label: Option<&String>,
merchant_account: &storage_models::merchant_account::MerchantAccount,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<(api_enums::CountryAlpha2, String)> {
let (business_country, business_label) = match business_country.zip(business_label) {
Some((business_country, business_label)) => {
@ -1555,7 +1701,7 @@ pub async fn insert_merchant_connector_creds_to_config(
}
pub enum MerchantConnectorAccountType {
DbVal(storage::MerchantConnectorAccount),
DbVal(domain::MerchantConnectorAccount),
CacheVal(api_models::admin::MerchantConnectorDetails),
}
@ -1568,7 +1714,7 @@ impl MerchantConnectorAccountType {
}
pub fn get_connector_account_details(&self) -> serde_json::Value {
match self {
Self::DbVal(val) => val.connector_account_details.to_owned(),
Self::DbVal(val) => val.connector_account_details.peek().to_owned(),
Self::CacheVal(val) => val.connector_account_details.peek().to_owned(),
}
}
@ -1644,11 +1790,11 @@ pub async fn get_merchant_connector_account(
/// * `request` - new request
/// * `response` - new response
pub fn router_data_type_conversion<F1, F2, Req1, Req2, Res1, Res2>(
router_data: types::RouterData<F1, Req1, Res1>,
router_data: RouterData<F1, Req1, Res1>,
request: Req2,
response: Result<Res2, types::ErrorResponse>,
) -> types::RouterData<F2, Req2, Res2> {
types::RouterData {
response: Result<Res2, ErrorResponse>,
) -> RouterData<F2, Req2, Res2> {
RouterData {
flow: std::marker::PhantomData,
request,
response,