feat: encryption service integration to support batch encryption and decryption (#5164)

Co-authored-by: dracarys18 <karthikey.hegde@juspay.in>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Arjun Karthik
2024-07-19 13:08:58 +05:30
committed by GitHub
parent c698921c41
commit 33298b3808
127 changed files with 4239 additions and 1378 deletions

View File

@ -15,6 +15,7 @@ kv_store = []
async-bb8-diesel = { git = "https://github.com/jarnura/async-bb8-diesel", rev = "53b4ab901aab7635c8215fd1c2d542c8db443094" }
diesel = { version = "2.1.5", features = ["postgres", "serde_json", "time", "64-column-tables"] }
error-stack = "0.4.1"
rustc-hash = "1.1.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
strum = { version = "0.26.2", features = ["derive"] }

View File

@ -1,9 +1,17 @@
use common_utils::id_type;
use common_utils::{
crypto::{self, Encryptable},
encryption::Encryption,
id_type,
pii::EmailStrategy,
types::keymanager::ToEncryptable,
};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::{Secret, SwitchStrategy};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, enums, schema::address};
use crate::{enums, schema::address};
#[derive(Clone, Debug, Insertable, Serialize, Deserialize, router_derive::DebugAsDisplay)]
#[diesel(table_name = address)]
@ -54,6 +62,62 @@ pub struct Address {
pub email: Option<Encryption>,
}
#[derive(Clone)]
// Intermediate struct to convert HashMap to Address
pub struct EncryptableAddress {
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,
pub email: crypto::OptionalEncryptableEmail,
}
impl ToEncryptable<EncryptableAddress, Secret<String>, Encryption> for Address {
fn to_encryptable(self) -> FxHashMap<String, Encryption> {
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<String, Encryptable<Secret<String>>>,
) -> common_utils::errors::CustomResult<EncryptableAddress, common_utils::errors::ParsingError>
{
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<Secret<String, EmailStrategy>> = 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 {

View File

@ -1,7 +1,7 @@
use common_utils::pii;
use common_utils::{encryption::Encryption, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use crate::{encryption::Encryption, schema::business_profile};
use crate::schema::business_profile;
#[derive(
Clone,

View File

@ -1,8 +1,8 @@
use common_utils::{id_type, pii};
use common_utils::{encryption::Encryption, id_type, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, schema::customers};
use crate::schema::customers;
#[derive(
Clone, Debug, Insertable, router_derive::DebugAsDisplay, serde::Deserialize, serde::Serialize,

View File

@ -1,71 +0,0 @@
use common_utils::pii::EncryptionStrategy;
use diesel::{
backend::Backend,
deserialize::{self, FromSql, Queryable},
serialize::ToSql,
sql_types, AsExpression,
};
use masking::Secret;
#[derive(Debug, AsExpression, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[diesel(sql_type = sql_types::Binary)]
#[repr(transparent)]
pub struct Encryption {
inner: Secret<Vec<u8>, EncryptionStrategy>,
}
impl<T: Clone> From<common_utils::crypto::Encryptable<T>> for Encryption {
fn from(value: common_utils::crypto::Encryptable<T>) -> Self {
Self::new(value.into_encrypted())
}
}
impl Encryption {
pub fn new(item: Secret<Vec<u8>, EncryptionStrategy>) -> Self {
Self { inner: item }
}
#[inline]
pub fn into_inner(self) -> Secret<Vec<u8>, EncryptionStrategy> {
self.inner
}
#[inline]
pub fn get_inner(&self) -> &Secret<Vec<u8>, EncryptionStrategy> {
&self.inner
}
}
impl<DB> FromSql<sql_types::Binary, DB> for Encryption
where
DB: Backend,
Secret<Vec<u8>, EncryptionStrategy>: FromSql<sql_types::Binary, DB>,
{
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
<Secret<Vec<u8>, EncryptionStrategy>>::from_sql(bytes).map(Self::new)
}
}
impl<DB> ToSql<sql_types::Binary, DB> for Encryption
where
DB: Backend,
Secret<Vec<u8>, EncryptionStrategy>: ToSql<sql_types::Binary, DB>,
{
fn to_sql<'b>(
&'b self,
out: &mut diesel::serialize::Output<'b, '_, DB>,
) -> diesel::serialize::Result {
self.get_inner().to_sql(out)
}
}
impl<DB> Queryable<sql_types::Binary, DB> for Encryption
where
DB: Backend,
Secret<Vec<u8>, EncryptionStrategy>: FromSql<sql_types::Binary, DB>,
{
type Row = Secret<Vec<u8>, EncryptionStrategy>;
fn build(row: Self::Row) -> deserialize::Result<Self> {
Ok(Self { inner: row })
}
}

View File

@ -1,11 +1,15 @@
use common_utils::custom_serde;
use common_utils::{
crypto::OptionalEncryptableSecretString, custom_serde, encryption::Encryption,
types::keymanager::ToEncryptable,
};
use diesel::{
expression::AsExpression, AsChangeset, Identifiable, Insertable, Queryable, Selectable,
};
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, enums as storage_enums, schema::events};
use crate::{enums as storage_enums, schema::events};
#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
#[diesel(table_name = events)]
@ -59,6 +63,38 @@ pub struct Event {
pub metadata: Option<EventMetadata>,
}
pub struct EventWithEncryption {
pub request: Option<Encryption>,
pub response: Option<Encryption>,
}
pub struct EncryptableEvent {
pub request: OptionalEncryptableSecretString,
pub response: OptionalEncryptableSecretString,
}
impl ToEncryptable<EncryptableEvent, Secret<String>, Encryption> for EventWithEncryption {
fn to_encryptable(self) -> rustc_hash::FxHashMap<String, Encryption> {
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<Secret<String>>,
>,
) -> common_utils::errors::CustomResult<EncryptableEvent, common_utils::errors::ParsingError>
{
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 {

View File

@ -12,7 +12,6 @@ pub mod blocklist;
pub mod blocklist_fingerprint;
pub mod customers;
pub mod dispute;
pub mod encryption;
pub mod enums;
pub mod ephemeral_key;
pub mod errors;

View File

@ -1,7 +1,7 @@
use common_utils::pii;
use common_utils::{encryption::Encryption, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use crate::{encryption::Encryption, enums as storage_enums, schema::merchant_account};
use crate::{enums as storage_enums, schema::merchant_account};
#[derive(
Clone,

View File

@ -1,10 +1,10 @@
use std::fmt::Debug;
use common_utils::pii;
use common_utils::{encryption::Encryption, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::Secret;
use crate::{encryption::Encryption, enums as storage_enums, schema::merchant_connector_account};
use crate::{enums as storage_enums, schema::merchant_connector_account};
#[derive(
Clone,

View File

@ -1,8 +1,8 @@
use common_utils::custom_serde;
use common_utils::{custom_serde, encryption::Encryption};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, schema::merchant_key_store};
use crate::schema::merchant_key_store;
#[derive(
Clone,

View File

@ -1,10 +1,10 @@
use common_enums::RequestIncrementalAuthorization;
use common_utils::{id_type, pii, types::MinorUnit};
use common_utils::{encryption::Encryption, id_type, pii, types::MinorUnit};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, enums as storage_enums, schema::payment_intent};
use crate::{enums as storage_enums, schema::payment_intent};
#[derive(Clone, Debug, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = payment_intent, primary_key(payment_id, merchant_id), check_for_backend(diesel::pg::Pg))]

View File

@ -1,11 +1,11 @@
use common_enums::MerchantStorageScheme;
use common_utils::{id_type, pii};
use common_utils::{encryption::Encryption, id_type, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, enums as storage_enums, schema::payment_methods};
use crate::{enums as storage_enums, schema::payment_methods};
#[derive(
Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize,

View File

@ -1,11 +1,9 @@
use common_utils::pii;
use common_utils::{encryption::Encryption, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::Secret;
use time::PrimitiveDateTime;
use crate::{
diesel_impl::OptionalDieselArray, encryption::Encryption, enums::TotpStatus, schema::users,
};
use crate::{diesel_impl::OptionalDieselArray, enums::TotpStatus, schema::users};
pub mod dashboard_metadata;

View File

@ -1,7 +1,8 @@
use common_utils::encryption::Encryption;
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, enums, schema::user_authentication_methods};
use crate::{enums, schema::user_authentication_methods};
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
#[diesel(table_name = user_authentication_methods, check_for_backend(diesel::pg::Pg))]

View File

@ -1,7 +1,8 @@
use common_utils::encryption::Encryption;
use diesel::{Identifiable, Insertable, Queryable, Selectable};
use time::PrimitiveDateTime;
use crate::{encryption::Encryption, schema::user_key_store};
use crate::schema::user_key_store;
#[derive(
Clone, Debug, serde::Serialize, serde::Deserialize, Identifiable, Queryable, Selectable,