From 5c3dd3846c19c871345f49e6cacbb73635d8704b Mon Sep 17 00:00:00 2001 From: Shankar Singh C Date: Mon, 28 Oct 2024 21:58:50 +0530 Subject: [PATCH] feat(router): add support v2 /add /retrieve /delete api handler --- src/routes/data.rs | 24 ++++---- src/routes/data/crypto_operation.rs | 41 +++++++++++--- src/routes/data/transformers.rs | 11 ++++ src/routes/data/types.rs | 35 ++++++++---- src/routes/routes_v2/data.rs | 87 ++++++++++++++++++++++++----- src/routes/routes_v2/data/types.rs | 19 ++++++- src/storage/storage_v2.rs | 2 - src/storage/storage_v2/db.rs | 16 ++---- src/storage/storage_v2/types.rs | 77 ++++++++++--------------- src/storage/types.rs | 25 --------- 10 files changed, 205 insertions(+), 132 deletions(-) diff --git a/src/routes/data.rs b/src/routes/data.rs index 67cb6a2..05ef340 100644 --- a/src/routes/data.rs +++ b/src/routes/data.rs @@ -117,17 +117,14 @@ pub async fn add_card( .await?; let (duplication_check, output) = match stored_data { - Some(locker) => { - let decrypted_locker_data = - crypto_operation::decrypt_data(&tenant_app_state, crypto_manager, locker) - .await?; + Some(mut locker) => { + crypto_operation::decrypt_data(&tenant_app_state, crypto_manager, &mut locker) + .await?; - let duplication_check = transformers::validate_card_metadata( - &decrypted_locker_data, - &request.data, - )?; + let duplication_check = + transformers::validate_card_metadata(&locker, &request.data)?; - (Some(duplication_check), decrypted_locker_data) + (Some(duplication_check), locker) } None => { let encrypted_locker_data = crypto_operation::encrypt_data_and_insert_into_db( @@ -197,7 +194,7 @@ pub async fn retrieve_card( .find_by_entity_id(&tenant_app_state, request.merchant_id.clone()) .await?; - let locker = tenant_app_state + let mut locker = tenant_app_state .db .find_by_locker_id_merchant_id_customer_id( request.card_reference.clone().into(), @@ -206,10 +203,9 @@ pub async fn retrieve_card( ) .await?; - let decrypted_locker_data = - crypto_operation::decrypt_data(&tenant_app_state, crypto_manager, locker).await?; + crypto_operation::decrypt_data(&tenant_app_state, crypto_manager, &mut locker).await?; - decrypted_locker_data + locker .ttl .map(|ttl| -> Result<(), error::ApiError> { if utils::date_time::now() > ttl { @@ -231,7 +227,7 @@ pub async fn retrieve_card( }) .transpose()?; - Ok(Json(decrypted_locker_data.try_into()?)) + Ok(Json(locker.try_into()?)) } /// `/cards/fingerprint` handling the creation and retrieval of card fingerprint diff --git a/src/routes/data/crypto_operation.rs b/src/routes/data/crypto_operation.rs index c7fb0a1..6836d4d 100644 --- a/src/routes/data/crypto_operation.rs +++ b/src/routes/data/crypto_operation.rs @@ -1,10 +1,13 @@ +use masking::ExposeInterface; + use crate::{ app::TenantAppState, crypto::keymanager::CryptoOperationsManager, error::{self, ContainerError, ResultContainerExt}, routes::data::types, storage::{ - types::{Encryptable, Locker, LockerNew}, + storage_v2::{types::VaultNew, VaultInterface}, + types::{Locker, LockerNew}, LockerInterface, }, }; @@ -35,18 +38,42 @@ pub async fn encrypt_data_and_insert_into_db<'a>( Ok(locker) } -pub async fn decrypt_data( +pub async fn decrypt_data( tenant_app_state: &TenantAppState, crypto_operator: Box, - mut locker: Locker, -) -> Result> { - if let Some(encrypted_data) = locker.data.get_encrypted_inner_value() { + data: &mut T, +) -> Result<(), ContainerError> +where + T: types::SecretDataManager, +{ + if let Some(encrypted_data) = data.get_encrypted_inner_value() { let decrypted_data = crypto_operator .decrypt_data(tenant_app_state, encrypted_data) .await?; - locker.data = Encryptable::from_decrypted_data(decrypted_data) + data.set_decrypted_data(decrypted_data); } + Ok(()) +} - Ok(locker) +pub async fn encrypt_data_and_insert_into_db_v2( + tenant_app_state: &TenantAppState, + crypto_operator: Box, + request: crate::routes::routes_v2::data::types::StoreDataRequest, +) -> Result> { + let data_to_be_encrypted = serde_json::to_vec(&request.data.clone().expose()) + .change_error(error::ApiError::EncodingError)?; + + let encrypted_data = crypto_operator + .encrypt_data(tenant_app_state, data_to_be_encrypted.into()) + .await?; + + let vault_new = VaultNew::new(request, encrypted_data.into()); + + let vault = tenant_app_state + .db + .insert_or_get_from_vault(vault_new) + .await?; + + Ok(vault) } diff --git a/src/routes/data/transformers.rs b/src/routes/data/transformers.rs index b6f7dd5..1c89d4e 100644 --- a/src/routes/data/transformers.rs +++ b/src/routes/data/transformers.rs @@ -25,6 +25,17 @@ impl From<(Option, storage::types::Locker)> } } +impl From + for crate::routes::routes_v2::data::types::StoreDataResponse +{ + fn from(value: storage::storage_v2::types::Vault) -> Self { + Self { + entity_id: value.entity_id, + vault_id: value.vault_id.expose(), + } + } +} + impl TryFrom for super::types::RetrieveCardResponse { type Error = ContainerError; fn try_from(value: storage::types::Locker) -> Result { diff --git a/src/routes/data/types.rs b/src/routes/data/types.rs index 4f031cc..0a76172 100644 --- a/src/routes/data/types.rs +++ b/src/routes/data/types.rs @@ -1,15 +1,13 @@ -// #[derive(serde::Serialize, serde::Deserialize)] -// #[serde(rename_all = "camelCase")] -// pub struct Dedup { -// hash1: Option, -// hash2: Option, -// hash1_reference: Option, -// hash2_reference: Option, -// } +use masking::{Secret, StrongSecret}; -use masking::Secret; - -use crate::{error, storage, utils}; +use crate::{ + error, + storage::{ + self, + types::{Encryptable, Locker}, + }, + utils, +}; #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Card { @@ -180,3 +178,18 @@ impl Validation for StoreCardRequest { } } } + +pub trait SecretDataManager { + fn get_encrypted_inner_value(&self) -> Option>>; + fn set_decrypted_data(&mut self, decrypted_data: StrongSecret>); +} + +impl SecretDataManager for Locker { + fn get_encrypted_inner_value(&self) -> Option>> { + self.data.get_encrypted_inner_value() + } + + fn set_decrypted_data(&mut self, decrypted_data: StrongSecret>) { + self.data = Encryptable::from_decrypted_data(decrypted_data); + } +} diff --git a/src/routes/routes_v2/data.rs b/src/routes/routes_v2/data.rs index ce2d6c0..9be1e53 100644 --- a/src/routes/routes_v2/data.rs +++ b/src/routes/routes_v2/data.rs @@ -1,31 +1,92 @@ use axum::Json; +use error_stack::ResultExt; +use masking::PeekInterface; pub mod types; use crate::{ + crypto::keymanager::{self, KeyProvider}, custom_extractors::TenantStateResolver, - error::{self, ContainerError}, + error::{self, ContainerError, ResultContainerExt}, + routes::data::crypto_operation, + storage::storage_v2::VaultInterface, + utils, }; pub async fn delete_data( - TenantStateResolver(_tenant_app_state): TenantStateResolver, - Json(_request): Json, + TenantStateResolver(tenant_app_state): TenantStateResolver, + Json(request): Json, ) -> Result, ContainerError> { - // need handle this once the key manger service is ready - todo!() + let _entity = keymanager::get_dek_manager() + .find_by_entity_id(&tenant_app_state, request.entity_id.clone()) + .await?; + + let _delete_status = tenant_app_state + .db + .delete_from_vault(request.vault_id.clone().into(), &request.entity_id) + .await?; + Ok(Json(types::DeleteDataResponse { + entity_id: request.entity_id, + vault_id: request.vault_id, + })) } pub async fn retrieve_data( - TenantStateResolver(_tenant_app_state): TenantStateResolver, - Json(_request): Json, + TenantStateResolver(tenant_app_state): TenantStateResolver, + Json(request): Json, ) -> Result, ContainerError> { - // need handle this once the key manger service is ready - todo!() + let crypto_manager = keymanager::get_dek_manager() + .find_by_entity_id(&tenant_app_state, request.entity_id.clone()) + .await?; + + let mut vault = tenant_app_state + .db + .find_by_vault_id_entity_id(request.vault_id.clone().into(), &request.entity_id) + .await?; + + crypto_operation::decrypt_data(&tenant_app_state, crypto_manager, &mut vault).await?; + + vault + .expires_at + .map(|ttl| -> Result<(), error::ApiError> { + if utils::date_time::now() > ttl { + tokio::spawn(async move { + tenant_app_state + .db + .delete_from_vault(request.vault_id.into(), &request.entity_id) + .await + }); + + Err(error::ApiError::NotFoundError) + } else { + Ok(()) + } + }) + .transpose()?; + let decrypted_data = vault + .data + .get_decrypted_inner_value() + .ok_or(error::ApiError::UnknownError) + .attach_printable("Failed to decrypt the stored data")?; + let og = serde_json::from_slice(decrypted_data.peek().as_ref()) + .change_error(error::ApiError::DecodingError)?; + + Ok(Json(types::RetrieveDataResponse { data: og })) } pub async fn add_data( - TenantStateResolver(_tenant_app_state): TenantStateResolver, - Json(_request): Json, + TenantStateResolver(tenant_app_state): TenantStateResolver, + Json(request): Json, ) -> Result, ContainerError> { - // need handle this once the key manger service is ready - todo!() + let crypto_manager = keymanager::get_dek_manager() + .find_or_create_entity(&tenant_app_state, request.entity_id.clone()) + .await?; + + let insert_data = crypto_operation::encrypt_data_and_insert_into_db_v2( + &tenant_app_state, + crypto_manager, + request, + ) + .await?; + + Ok(Json(types::StoreDataResponse::from(insert_data))) } diff --git a/src/routes/routes_v2/data/types.rs b/src/routes/routes_v2/data/types.rs index 319f199..f3790cb 100644 --- a/src/routes/routes_v2/data/types.rs +++ b/src/routes/routes_v2/data/types.rs @@ -1,6 +1,9 @@ -use masking::Secret; +use masking::{Secret, StrongSecret}; -use crate::routes::data::types::Ttl; +use crate::{ + routes::data::types::{SecretDataManager, Ttl}, + storage::{storage_v2::types::Vault, types::Encryptable}, +}; #[derive(serde::Serialize, serde::Deserialize)] pub struct DeleteDataRequest { @@ -22,7 +25,7 @@ pub struct RetrieveDataRequest { #[derive(serde::Serialize, serde::Deserialize)] pub struct RetrieveDataResponse { - pub payload: Secret, + pub data: Secret, } #[derive(serde::Serialize, serde::Deserialize, Debug)] @@ -38,3 +41,13 @@ pub struct StoreDataResponse { pub entity_id: String, pub vault_id: String, } + +impl SecretDataManager for Vault { + fn get_encrypted_inner_value(&self) -> Option>> { + self.data.get_encrypted_inner_value() + } + + fn set_decrypted_data(&mut self, decrypted_data: StrongSecret>) { + self.data = Encryptable::from_decrypted_data(decrypted_data); + } +} diff --git a/src/storage/storage_v2.rs b/src/storage/storage_v2.rs index 6668ae1..8ec46de 100644 --- a/src/storage/storage_v2.rs +++ b/src/storage/storage_v2.rs @@ -19,14 +19,12 @@ pub(crate) trait VaultInterface { &self, vault_id: Secret, entity_id: &str, - key: &Self::Algorithm, ) -> Result>; /// Insert data into vault table async fn insert_or_get_from_vault( &self, new: types::VaultNew, - key: &Self::Algorithm, ) -> Result>; /// Delete data from the vault diff --git a/src/storage/storage_v2/db.rs b/src/storage/storage_v2/db.rs index c39ee87..5a480dc 100644 --- a/src/storage/storage_v2/db.rs +++ b/src/storage/storage_v2/db.rs @@ -7,11 +7,7 @@ use masking::{ExposeInterface, Secret}; use crate::{ crypto::encryption_manager::managers::aes::GcmAes256, error::{self, ContainerError, ResultContainerExt}, - storage::{ - schema, - types::{StorageDecryption, StorageEncryption}, - Storage, - }, + storage::{schema, Storage}, }; use super::{types, VaultInterface}; @@ -24,7 +20,6 @@ impl VaultInterface for Storage { &self, vault_id: Secret, entity_id: &str, - key: &Self::Algorithm, ) -> Result> { let mut conn = self.get_conn().await?; @@ -44,31 +39,30 @@ impl VaultInterface for Storage { }) .map_err(error::ContainerError::from) .map_err(From::from) - .and_then(|inner| Ok(inner.decrypt(key)?)) + .map(|inner| inner.into()) } async fn insert_or_get_from_vault( &self, new: types::VaultNew, - key: &Self::Algorithm, ) -> Result> { let mut conn = self.get_conn().await?; let cloned_new = new.clone(); let query: Result<_, diesel::result::Error> = diesel::insert_into(types::VaultInner::table()) - .values(new.encrypt(key)?) + .values(new) .get_result::(&mut conn) .await; match query { - Ok(inner) => Ok(inner.decrypt(key)?), + Ok(inner) => Ok(inner.into()), Err(error) => match error { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UniqueViolation, _, ) => { - self.find_by_vault_id_entity_id(cloned_new.vault_id, &cloned_new.entity_id, key) + self.find_by_vault_id_entity_id(cloned_new.vault_id, &cloned_new.entity_id) .await } error => Err(error).change_error(error::StorageError::InsertError)?, diff --git a/src/storage/storage_v2/types.rs b/src/storage/storage_v2/types.rs index 289967c..269607c 100644 --- a/src/storage/storage_v2/types.rs +++ b/src/storage/storage_v2/types.rs @@ -1,11 +1,11 @@ use diesel::{Identifiable, Insertable, Queryable}; -use masking::{ExposeInterface, Secret}; +use masking::Secret; use crate::{ - crypto::encryption_manager::{encryption_interface::Encryption, managers::aes::GcmAes256}, + routes::routes_v2::data::types::StoreDataRequest, storage::{ schema, - types::{Encrypted, StorageDecryption, StorageEncryption}, + types::{Encryptable, Encrypted}, }, }; @@ -13,30 +13,54 @@ use crate::{ pub struct Vault { pub vault_id: Secret, pub entity_id: String, - pub encrypted_data: Secret>, + pub data: Encryptable, pub created_at: time::PrimitiveDateTime, pub expires_at: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Insertable)] +#[diesel(table_name = schema::vault)] pub struct VaultNew { pub vault_id: Secret, pub entity_id: String, - pub encrypted_data: Secret>, + pub encrypted_data: Encrypted, pub expires_at: Option, } +impl VaultNew { + pub fn new(request: StoreDataRequest, encrypted_data: Encrypted) -> Self { + Self { + vault_id: request.vault_id.into(), + entity_id: request.entity_id, + encrypted_data, + expires_at: *request.ttl, + } + } +} + #[derive(Debug, Identifiable, Queryable)] #[diesel(table_name = schema::vault)] pub(super) struct VaultInner { id: i32, - vault_id: Secret, entity_id: String, + vault_id: Secret, encrypted_data: Encrypted, created_at: time::PrimitiveDateTime, expires_at: Option, } +impl From for Vault { + fn from(value: VaultInner) -> Self { + Self { + vault_id: value.vault_id, + entity_id: value.entity_id, + data: value.encrypted_data.into(), + created_at: value.created_at, + expires_at: value.expires_at, + } + } +} + #[derive(Debug, Insertable)] #[diesel(table_name = schema::vault)] pub struct VaultNewInner { @@ -45,42 +69,3 @@ pub struct VaultNewInner { encrypted_data: Encrypted, expires_at: Option, } - -impl StorageDecryption for VaultInner { - type Output = Vault; - - type Algorithm = GcmAes256; - - fn decrypt( - self, - algo: &Self::Algorithm, - ) -> , Vec>>::ReturnType<'_, Self::Output> { - Ok(Self::Output { - vault_id: self.vault_id, - entity_id: self.entity_id, - encrypted_data: algo - .decrypt(self.encrypted_data.into_inner().expose())? - .into(), - created_at: self.created_at, - expires_at: self.expires_at, - }) - } -} - -impl StorageEncryption for VaultNew { - type Output = VaultNewInner; - - type Algorithm = GcmAes256; - - fn encrypt( - self, - algo: &Self::Algorithm, - ) -> , Vec>>::ReturnType<'_, Self::Output> { - Ok(Self::Output { - vault_id: self.vault_id, - entity_id: self.entity_id, - encrypted_data: algo.encrypt(self.encrypted_data.expose())?.into(), - expires_at: self.expires_at, - }) - } -} diff --git a/src/storage/types.rs b/src/storage/types.rs index 060022e..5975ed9 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -299,15 +299,6 @@ pub(super) trait StorageDecryption: Sized { ) -> , Vec>>::ReturnType<'_, Self::Output>; } -pub(super) trait StorageEncryption: Sized { - type Output; - type Algorithm: Encryption, Vec>; - fn encrypt( - self, - algo: &Self::Algorithm, - ) -> , Vec>>::ReturnType<'_, Self::Output>; -} - impl StorageDecryption for MerchantInner { type Output = Merchant; @@ -324,19 +315,3 @@ impl StorageDecryption for MerchantInner { }) } } - -impl<'a> StorageEncryption for MerchantNew<'a> { - type Output = MerchantNewInner<'a>; - - type Algorithm = GcmAes256; - - fn encrypt( - self, - algo: &Self::Algorithm, - ) -> , Vec>>::ReturnType<'_, Self::Output> { - Ok(Self::Output { - merchant_id: self.merchant_id, - enc_key: algo.encrypt(self.enc_key.expose())?.into(), - }) - } -}