refactor: revert redis temp locker logic (#2670)

This commit is contained in:
Abhishek Marrivagu
2023-10-23 20:18:47 +05:30
committed by GitHub
parent adad77f043
commit eaa9720520
20 changed files with 607 additions and 262 deletions

View File

@ -115,7 +115,6 @@ host = "" # Locker host
mock_locker = true # Emulate a locker locally using Postgres
basilisk_host = "" # Basilisk host
locker_signing_key_id = "1" # Key_id to sign basilisk hs locker
redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" # encryption key for redis temp locker
[delayed_session_response]
connectors_with_delayed_session_response = "trustpay,payme" # List of connectors which has delayed session response

View File

@ -50,7 +50,6 @@ applepay_endpoint = "DOMAIN SPECIFIC ENDPOINT"
host = ""
mock_locker = true
basilisk_host = ""
redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
[jwekey]
locker_key_identifier1 = ""

View File

@ -46,7 +46,6 @@ recon_admin_api_key = "recon_test_admin"
host = ""
mock_locker = true
basilisk_host = ""
redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
[jwekey]
locker_key_identifier1 = ""

View File

@ -771,6 +771,13 @@ pub struct DeleteTokenizeByTokenRequest {
pub service_name: String,
}
#[derive(Debug, serde::Serialize)] // Blocked: Yet to be implemented by `basilisk`
pub struct DeleteTokenizeByDateRequest {
pub buffer_minutes: i32,
pub service_name: String,
pub max_rows: i32,
}
#[derive(Debug, serde::Deserialize)]
pub struct GetTokenizePayloadResponse {
pub lookup_key: String,

View File

@ -120,14 +120,6 @@ pub enum KmsError {
/// The KMS client has not been initialized.
#[error("The KMS client has not been initialized")]
KmsClientNotInitialized,
/// The KMS client has not been initialized.
#[error("Hex decode failed")]
HexDecodeFailed,
/// The KMS client has not been initialized.
#[error("Utf8 decode failed")]
Utf8DecodeFailed,
}
impl KmsConfig {
@ -148,7 +140,7 @@ impl KmsConfig {
/// A wrapper around a KMS value that can be decrypted.
#[derive(Clone, Debug, Default, serde::Deserialize, Eq, PartialEq)]
#[serde(transparent)]
pub struct KmsValue(pub Secret<String>);
pub struct KmsValue(Secret<String>);
impl common_utils::ext_traits::ConfigExt for KmsValue {
fn is_empty_after_trim(&self) -> bool {

View File

@ -13,8 +13,9 @@ default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connec
s3 = ["dep:aws-sdk-s3", "dep:aws-config"]
kms = ["external_services/kms", "dep:aws-config"]
email = ["external_services/email", "dep:aws-config"]
basilisk = ["kms"]
stripe = ["dep:serde_qs"]
release = ["kms", "stripe", "s3", "email","accounts_cache"]
release = ["kms", "stripe", "basilisk", "s3", "email","accounts_cache"]
olap = ["data_models/olap", "storage_impl/olap", "scheduler/olap"]
oltp = ["data_models/oltp", "storage_impl/oltp"]
kv_store = ["scheduler/kv_store"]

View File

@ -49,9 +49,8 @@ impl Default for super::settings::Locker {
Self {
host: "localhost".into(),
mock_locker: true,
basilisk_host: "localhost".into(),
locker_signing_key_id: "1".into(),
redis_temp_locker_encryption_key: "".into(),
}
}
}

View File

@ -1,6 +1,5 @@
use common_utils::errors::CustomResult;
use error_stack::{IntoReport, ResultExt};
use external_services::kms::{decrypt::KmsDecrypt, KmsClient, KmsError, KmsValue};
use external_services::kms::{decrypt::KmsDecrypt, KmsClient, KmsError};
use masking::ExposeInterface;
use crate::configs::settings;
@ -42,19 +41,6 @@ impl KmsDecrypt for settings::ActiveKmsSecrets {
kms_client: &KmsClient,
) -> CustomResult<Self::Output, KmsError> {
self.jwekey = self.jwekey.expose().decrypt_inner(kms_client).await?.into();
self.redis_temp_locker_encryption_key = hex::decode(
KmsValue(
String::from_utf8(self.redis_temp_locker_encryption_key.expose())
.into_report()
.change_context(KmsError::Utf8DecodeFailed)?
.into(),
)
.decrypt_inner(kms_client)
.await?,
)
.into_report()
.change_context(KmsError::HexDecodeFailed)?
.into();
Ok(self)
}
}

View File

@ -52,7 +52,6 @@ pub enum Subcommand {
#[derive(Clone)]
pub struct ActiveKmsSecrets {
pub jwekey: masking::Secret<Jwekey>,
pub redis_temp_locker_encryption_key: masking::Secret<Vec<u8>>,
}
#[derive(Debug, Deserialize, Clone, Default)]
@ -411,8 +410,8 @@ pub struct Secrets {
pub struct Locker {
pub host: String,
pub mock_locker: bool,
pub basilisk_host: String,
pub locker_signing_key_id: String,
pub redis_temp_locker_encryption_key: String,
}
#[derive(Debug, Deserialize, Clone)]

View File

@ -56,19 +56,10 @@ impl super::settings::Locker {
})?;
when(
self.redis_temp_locker_encryption_key.is_default_or_empty(),
!self.mock_locker && self.basilisk_host.is_default_or_empty(),
|| {
Err(ApplicationError::InvalidConfigurationValueError(
"redis_temp_locker_encryption_key must not be empty".into(),
))
},
)?;
when(
self.redis_temp_locker_encryption_key.is_default_or_empty(),
|| {
Err(ApplicationError::InvalidConfigurationValueError(
"redis_temp_locker_encryption_key must not be empty".into(),
"basilisk host must not be empty when mock locker is disabled".into(),
))
},
)

View File

@ -2010,13 +2010,8 @@ pub async fn get_lookup_key_from_locker(
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Get Card Details Failed")?;
let card = card_detail.clone();
let resp = TempLockerCardSupport::create_payment_method_data_in_temp_locker(
state,
payment_token,
card,
pm,
)
let resp =
BasiliskCardSupport::create_payment_method_data_in_locker(state, payment_token, card, pm)
.await?;
Ok(resp)
}
@ -2063,11 +2058,106 @@ pub async fn get_lookup_key_for_payout_method(
}
}
pub struct TempLockerCardSupport;
pub struct BasiliskCardSupport;
impl TempLockerCardSupport {
#[cfg(not(feature = "basilisk"))]
impl BasiliskCardSupport {
async fn create_payment_method_data_in_locker(
state: &routes::AppState,
payment_token: &str,
card: api::CardDetailFromLocker,
pm: &storage::PaymentMethod,
) -> errors::RouterResult<api::CardDetailFromLocker> {
let card_number = card.card_number.clone().get_required_value("card_number")?;
let card_exp_month = card
.expiry_month
.clone()
.expose_option()
.get_required_value("expiry_month")?;
let card_exp_year = card
.expiry_year
.clone()
.expose_option()
.get_required_value("expiry_year")?;
let card_holder_name = card
.card_holder_name
.clone()
.expose_option()
.unwrap_or_default();
let value1 = payment_methods::mk_card_value1(
card_number,
card_exp_year,
card_exp_month,
Some(card_holder_name),
None,
None,
None,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value1 for locker")?;
let value2 = payment_methods::mk_card_value2(
None,
None,
None,
Some(pm.customer_id.to_string()),
Some(pm.payment_method_id.to_string()),
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value2 for locker")?;
let value1 = vault::VaultPaymentMethod::Card(value1);
let value2 = vault::VaultPaymentMethod::Card(value2);
let value1 = utils::Encode::<vault::VaultPaymentMethod>::encode_to_string_of_json(&value1)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Wrapped value1 construction failed when saving card to locker")?;
let value2 = utils::Encode::<vault::VaultPaymentMethod>::encode_to_string_of_json(&value2)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Wrapped value2 construction failed when saving card to locker")?;
let db_value = vault::MockTokenizeDBValue { value1, value2 };
let value_string =
utils::Encode::<vault::MockTokenizeDBValue>::encode_to_string_of_json(&db_value)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Mock tokenize value construction failed when saving card to locker",
)?;
let db = &*state.store;
let already_present = db.find_config_by_key(payment_token).await;
if already_present.is_err() {
let config = storage::ConfigNew {
key: payment_token.to_string(),
config: value_string,
};
db.insert_config(config)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Mock tokenization save to db failed")?;
} else {
let config_update = storage::ConfigUpdate::Update {
config: Some(value_string),
};
db.update_config_by_key(payment_token, config_update)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Mock tokenization db update failed")?;
}
Ok(card)
}
}
#[cfg(feature = "basilisk")]
impl BasiliskCardSupport {
#[instrument(skip_all)]
async fn create_payment_method_data_in_temp_locker(
async fn create_payment_method_data_in_locker(
state: &routes::AppState,
payment_token: &str,
card: api::CardDetailFromLocker,

View File

@ -580,6 +580,21 @@ pub fn get_card_detail(
}
//------------------------------------------------TokenizeService------------------------------------------------
pub fn mk_crud_locker_request(
locker: &settings::Locker,
path: &str,
req: api::TokenizePayloadEncrypted,
) -> CustomResult<services::Request, errors::VaultError> {
let body = utils::Encode::<api::TokenizePayloadEncrypted>::encode_to_value(&req)
.change_context(errors::VaultError::RequestEncodingFailed)?;
let mut url = locker.basilisk_host.to_owned();
url.push_str(path);
let mut request = services::Request::new(services::Method::Post, &url);
request.add_default_headers();
request.add_header(headers::CONTENT_TYPE, "application/json".into());
request.set_body(body.to_string());
Ok(request)
}
pub fn mk_card_value1(
card_number: cards::CardNumber,

View File

@ -1,31 +1,37 @@
use common_utils::{
crypto::{DecodeMessage, EncodeMessage, GcmAes256},
ext_traits::BytesExt,
generate_id_with_default_len,
};
use error_stack::{report, IntoReport, ResultExt};
use common_utils::generate_id_with_default_len;
#[cfg(feature = "basilisk")]
use error_stack::report;
use error_stack::{IntoReport, ResultExt};
#[cfg(feature = "basilisk")]
use josekit::jwe;
use masking::PeekInterface;
use router_env::{instrument, tracing};
#[cfg(feature = "basilisk")]
use scheduler::{types::process_data, utils as process_tracker_utils};
#[cfg(feature = "basilisk")]
use crate::routes::metrics;
#[cfg(feature = "payouts")]
use crate::types::api::payouts;
use crate::{
configs::settings,
core::errors::{self, CustomResult, RouterResult},
db, logger, routes,
routes::metrics,
logger, routes,
types::{
api,
storage::{self, enums, ProcessTrackerExt},
storage::{self, enums},
},
utils::{self, StringExt},
};
#[cfg(feature = "basilisk")]
use crate::{core::payment_methods::transformers as payment_methods, services, utils::BytesExt};
#[cfg(feature = "basilisk")]
use crate::{db, types::storage::ProcessTrackerExt};
#[cfg(feature = "basilisk")]
const VAULT_SERVICE_NAME: &str = "CARD";
const LOCKER_REDIS_PREFIX: &str = "LOCKER_TOKEN";
const LOCKER_REDIS_EXPIRY_SECONDS: u32 = 60 * 15; // 15 minutes
#[cfg(feature = "basilisk")]
const VAULT_VERSION: &str = "0";
pub struct SupplementaryVaultData {
pub customer_id: Option<String>,
@ -616,6 +622,189 @@ pub struct MockTokenizeDBValue {
pub struct Vault;
#[cfg(not(feature = "basilisk"))]
impl Vault {
#[instrument(skip_all)]
pub async fn get_payment_method_data_from_locker(
state: &routes::AppState,
lookup_key: &str,
) -> RouterResult<(Option<api::PaymentMethodData>, SupplementaryVaultData)> {
let config = state
.store
.find_config_by_key(lookup_key)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Could not find payment method in vault")?;
let tokenize_value: MockTokenizeDBValue = config
.config
.parse_struct("MockTokenizeDBValue")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to deserialize Mock tokenize db value")?;
let (payment_method, supp_data) =
api::PaymentMethodData::from_values(tokenize_value.value1, tokenize_value.value2)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error parsing Payment Method from Values")?;
Ok((Some(payment_method), supp_data))
}
#[cfg(feature = "payouts")]
#[instrument(skip_all)]
pub async fn get_payout_method_data_from_temporary_locker(
state: &routes::AppState,
lookup_key: &str,
) -> RouterResult<(Option<api::PayoutMethodData>, SupplementaryVaultData)> {
let config = state
.store
.find_config_by_key(lookup_key)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Could not find payment method in vault")?;
let tokenize_value: MockTokenizeDBValue = config
.config
.parse_struct("MockTokenizeDBValue")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to deserialize Mock tokenize db value")?;
let (payout_method, supp_data) =
api::PayoutMethodData::from_values(tokenize_value.value1, tokenize_value.value2)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error parsing Payout Method from Values")?;
Ok((Some(payout_method), supp_data))
}
#[cfg(feature = "payouts")]
#[instrument(skip_all)]
pub async fn store_payout_method_data_in_locker(
state: &routes::AppState,
token_id: Option<String>,
payout_method: &api::PayoutMethodData,
customer_id: Option<String>,
) -> RouterResult<String> {
let value1 = payout_method
.get_value1(customer_id.clone())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value1 for locker")?;
let value2 = payout_method
.get_value2(customer_id)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value2 for locker")?;
let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token"));
let db_value = MockTokenizeDBValue { value1, value2 };
let value_string =
utils::Encode::<MockTokenizeDBValue>::encode_to_string_of_json(&db_value)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encode payout method as mock tokenize db value")?;
let already_present = state.store.find_config_by_key(&lookup_key).await;
if already_present.is_err() {
let config = storage::ConfigNew {
key: lookup_key.clone(),
config: value_string,
};
state
.store
.insert_config(config)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Mock tokenization save to db failed insert")?;
} else {
let config_update = storage::ConfigUpdate::Update {
config: Some(value_string),
};
state
.store
.update_config_by_key(&lookup_key, config_update)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Mock tokenization save to db failed update")?;
}
Ok(lookup_key)
}
#[instrument(skip_all)]
pub async fn store_payment_method_data_in_locker(
state: &routes::AppState,
token_id: Option<String>,
payment_method: &api::PaymentMethodData,
customer_id: Option<String>,
_pm: enums::PaymentMethod,
) -> RouterResult<String> {
let value1 = payment_method
.get_value1(customer_id.clone())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value1 for locker")?;
let value2 = payment_method
.get_value2(customer_id)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Value12 for locker")?;
let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token"));
let db_value = MockTokenizeDBValue { value1, value2 };
let value_string =
utils::Encode::<MockTokenizeDBValue>::encode_to_string_of_json(&db_value)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encode payment method as mock tokenize db value")?;
let already_present = state.store.find_config_by_key(&lookup_key).await;
if already_present.is_err() {
let config = storage::ConfigNew {
key: lookup_key.clone(),
config: value_string,
};
state
.store
.insert_config(config)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Mock tokenization save to db failed insert")?;
} else {
let config_update = storage::ConfigUpdate::Update {
config: Some(value_string),
};
state
.store
.update_config_by_key(&lookup_key, config_update)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Mock tokenization save to db failed update")?;
}
Ok(lookup_key)
}
#[instrument(skip_all)]
pub async fn delete_locker_payment_method_by_lookup_key(
state: &routes::AppState,
lookup_key: &Option<String>,
) {
let db = &*state.store;
if let Some(id) = lookup_key {
match db.delete_config_by_key(id).await {
Ok(_) => logger::info!("Card Deleted from locker mock up"),
Err(err) => logger::error!("Err: Card Delete from locker Failed : {}", err),
}
}
}
}
#[cfg(feature = "basilisk")]
impl Vault {
#[instrument(skip_all)]
pub async fn get_payment_method_data_from_locker(
@ -704,41 +893,49 @@ impl Vault {
lookup_key: &Option<String>,
) {
if let Some(lookup_key) = lookup_key {
delete_tokenized_data(state, lookup_key)
.await
.map(|_| logger::info!("Card From locker deleted Successfully"))
.map_err(|err| logger::error!("Error: Deleting Card From Redis Locker : {:?}", err))
.ok();
let delete_resp = delete_tokenized_data(state, lookup_key).await;
match delete_resp {
Ok(resp) => {
if resp == "Ok" {
logger::info!("Card From locker deleted Successfully")
} else {
logger::error!("Error: Deleting Card From Locker : {:?}", resp)
}
}
Err(err) => logger::error!("Err: Deleting Card From Locker : {:?}", err),
}
}
}
}
//------------------------------------------------TokenizeService------------------------------------------------
fn get_redis_temp_locker_encryption_key(state: &routes::AppState) -> RouterResult<Vec<u8>> {
#[cfg(feature = "kms")]
let secret = state
.kms_secrets
.redis_temp_locker_encryption_key
.peek()
.to_owned();
#[cfg(not(feature = "kms"))]
let secret = hex::decode(
state
.conf
.locker
.redis_temp_locker_encryption_key
.to_owned(),
)
.into_report()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to decode redis temp locker data")?;
Ok(secret)
pub fn get_key_id(keys: &settings::Jwekey) -> &str {
let key_identifier = "1"; // [#46]: Fetch this value from redis or external sources
if key_identifier == "1" {
&keys.locker_key_identifier1
} else {
&keys.locker_key_identifier2
}
}
#[instrument(skip(state, value1, value2))]
#[cfg(feature = "basilisk")]
async fn get_locker_jwe_keys(
keys: &settings::ActiveKmsSecrets,
) -> CustomResult<(String, String), errors::EncryptionError> {
let keys = keys.jwekey.peek();
let key_id = get_key_id(keys);
let (public_key, private_key) = if key_id == keys.locker_key_identifier1 {
(&keys.locker_encryption_key1, &keys.locker_decryption_key1)
} else if key_id == keys.locker_key_identifier2 {
(&keys.locker_encryption_key2, &keys.locker_decryption_key2)
} else {
return Err(errors::EncryptionError.into());
};
Ok((public_key.to_string(), private_key.to_string()))
}
#[cfg(feature = "basilisk")]
pub async fn create_tokenize(
state: &routes::AppState,
value1: String,
@ -746,125 +943,216 @@ pub async fn create_tokenize(
lookup_key: String,
) -> RouterResult<String> {
metrics::CREATED_TOKENIZED_CARD.add(&metrics::CONTEXT, 1, &[]);
let redis_key = format!("{}_{}", LOCKER_REDIS_PREFIX, lookup_key);
let payload_to_be_encrypted = api::TokenizePayloadRequest {
value1,
value2: value2.unwrap_or_default(),
lookup_key: lookup_key.to_owned(),
lookup_key,
service_name: VAULT_SERVICE_NAME.to_string(),
};
let payload = utils::Encode::<api::TokenizePayloadRequest>::encode_to_string_of_json(
&payload_to_be_encrypted,
)
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let secret = get_redis_temp_locker_encryption_key(state)?;
let encrypted_payload = GcmAes256
.encode_message(secret.as_ref(), payload.as_bytes())
let (public_key, private_key) = get_locker_jwe_keys(&state.kms_secrets)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encode redis temp locker data")?;
let redis_conn = state
.store
.get_redis_conn()
.attach_printable("Error getting Encryption key")?;
let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get redis connection")?;
.attach_printable("Error getting Encrypt JWE response")?;
redis_conn
.set_key_if_not_exists_with_expiry(
redis_key.as_str(),
bytes::Bytes::from(encrypted_payload),
Some(i64::from(LOCKER_REDIS_EXPIRY_SECONDS)),
let create_tokenize_request = api::TokenizePayloadEncrypted {
payload: encrypted_payload,
key_id: get_key_id(&state.conf.jwekey).to_string(),
version: Some(VAULT_VERSION.to_string()),
};
let request = payment_methods::mk_crud_locker_request(
&state.conf.locker,
"/tokenize",
create_tokenize_request,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Making tokenize request failed")?;
let response = services::call_connector_api(state, request)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?;
match response {
Ok(r) => {
let resp: api::TokenizePayloadEncrypted = r
.response
.parse_struct("TokenizePayloadEncrypted")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Decoding Failed for TokenizePayloadEncrypted")?;
let alg = jwe::RSA_OAEP_256;
let decrypted_payload = services::decrypt_jwe(
&resp.payload,
services::KeyIdCheck::RequestResponseKeyId((
get_key_id(&state.conf.jwekey),
&resp.key_id,
)),
private_key,
alg,
)
.await
.map(|_| lookup_key)
.map_err(|err| {
metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
err
})
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error from redis locker")
.attach_printable("Decrypt Jwe failed for TokenizePayloadEncrypted")?;
let get_response: api::GetTokenizePayloadResponse = decrypted_payload
.parse_struct("GetTokenizePayloadResponse")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Error getting GetTokenizePayloadResponse from tokenize response",
)?;
Ok(get_response.lookup_key)
}
Err(err) => {
metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
Err(errors::ApiErrorResponse::InternalServerError)
.into_report()
.attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))
}
}
}
#[instrument(skip(state))]
#[cfg(feature = "basilisk")]
pub async fn get_tokenized_data(
state: &routes::AppState,
lookup_key: &str,
_should_get_value2: bool,
should_get_value2: bool,
) -> RouterResult<api::TokenizePayloadRequest> {
metrics::GET_TOKENIZED_CARD.add(&metrics::CONTEXT, 1, &[]);
let payload_to_be_encrypted = api::GetTokenizePayloadRequest {
lookup_key: lookup_key.to_string(),
get_value2: should_get_value2,
service_name: VAULT_SERVICE_NAME.to_string(),
};
let payload = serde_json::to_string(&payload_to_be_encrypted)
.map_err(|_x| errors::ApiErrorResponse::InternalServerError)?;
let redis_key = format!("{}_{}", LOCKER_REDIS_PREFIX, lookup_key);
let redis_conn = state
.store
.get_redis_conn()
let (public_key, private_key) = get_locker_jwe_keys(&state.kms_secrets)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get redis connection")?;
let response = redis_conn.get_key::<bytes::Bytes>(redis_key.as_str()).await;
.attach_printable("Error getting Encryption key")?;
let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Encrypt JWE response")?;
let create_tokenize_request = api::TokenizePayloadEncrypted {
payload: encrypted_payload,
key_id: get_key_id(&state.conf.jwekey).to_string(),
version: Some("0".to_string()),
};
let request = payment_methods::mk_crud_locker_request(
&state.conf.locker,
"/tokenize/get",
create_tokenize_request,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Making Get Tokenized request failed")?;
let response = services::call_connector_api(state, request)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?;
match response {
Ok(resp) => {
let secret = get_redis_temp_locker_encryption_key(state)?;
let decrypted_payload = GcmAes256
.decode_message(secret.as_ref(), masking::Secret::new(resp.into()))
Ok(r) => {
let resp: api::TokenizePayloadEncrypted = r
.response
.parse_struct("TokenizePayloadEncrypted")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to decode redis temp locker data")?;
let get_response: api::TokenizePayloadRequest = bytes::Bytes::from(decrypted_payload)
.attach_printable("Decoding Failed for TokenizePayloadEncrypted")?;
let alg = jwe::RSA_OAEP_256;
let decrypted_payload = services::decrypt_jwe(
&resp.payload,
services::KeyIdCheck::RequestResponseKeyId((
get_key_id(&state.conf.jwekey),
&resp.key_id,
)),
private_key,
alg,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("GetTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted")?;
let get_response: api::TokenizePayloadRequest = decrypted_payload
.parse_struct("TokenizePayloadRequest")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting TokenizePayloadRequest from tokenize response")?;
Ok(get_response)
}
Err(err) => {
metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
Err(err).change_context(errors::ApiErrorResponse::UnprocessableEntity {
match err.status_code {
404 => Err(errors::ApiErrorResponse::UnprocessableEntity {
message: "Token is invalid or expired".into(),
})
}
.into()),
_ => Err(errors::ApiErrorResponse::InternalServerError)
.into_report()
.attach_printable(format!("Got error from the basilisk locker: {err:?}")),
}
}
}
}
#[instrument(skip(state))]
pub async fn delete_tokenized_data(state: &routes::AppState, lookup_key: &str) -> RouterResult<()> {
#[cfg(feature = "basilisk")]
pub async fn delete_tokenized_data(
state: &routes::AppState,
lookup_key: &str,
) -> RouterResult<String> {
metrics::DELETED_TOKENIZED_CARD.add(&metrics::CONTEXT, 1, &[]);
let redis_key = format!("{}_{}", LOCKER_REDIS_PREFIX, lookup_key);
let redis_conn = state
.store
.get_redis_conn()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get redis connection")?;
let response = redis_conn.delete_key(redis_key.as_str()).await;
match response {
Ok(redis_interface::DelReply::KeyDeleted) => Ok(()),
Ok(redis_interface::DelReply::KeyNotDeleted) => {
Err(errors::ApiErrorResponse::InternalServerError)
let payload_to_be_encrypted = api::DeleteTokenizeByTokenRequest {
lookup_key: lookup_key.to_string(),
service_name: VAULT_SERVICE_NAME.to_string(),
};
let payload = serde_json::to_string(&payload_to_be_encrypted)
.into_report()
.attach_printable("Token invalid or expired")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error serializing api::DeleteTokenizeByTokenRequest")?;
let (public_key, _private_key) = get_locker_jwe_keys(&state.kms_secrets.clone())
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Encryption key")?;
let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error getting Encrypt JWE response")?;
let create_tokenize_request = api::TokenizePayloadEncrypted {
payload: encrypted_payload,
key_id: get_key_id(&state.conf.jwekey).to_string(),
version: Some("0".to_string()),
};
let request = payment_methods::mk_crud_locker_request(
&state.conf.locker,
"/tokenize/delete/token",
create_tokenize_request,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Making Delete Tokenized request failed")?;
let response = services::call_connector_api(state, request)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while making /tokenize/delete/token call to the locker")?;
match response {
Ok(r) => {
let delete_response = std::str::from_utf8(&r.response)
.into_report()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Decoding Failed for basilisk delete response")?;
Ok(delete_response.to_string())
}
Err(err) => {
metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]);
Err(errors::ApiErrorResponse::InternalServerError)
.into_report()
.attach_printable_lazy(|| format!("Failed to delete from redis locker: {err:?}"))
.attach_printable(format!("Got 4xx from the basilisk locker: {err:?}"))
}
}
}
// ********************************************** PROCESS TRACKER **********************************************
#[cfg(feature = "basilisk")]
pub async fn add_delete_tokenized_data_task(
db: &dyn db::StorageInterface,
lookup_key: &str,
@ -907,6 +1195,7 @@ pub async fn add_delete_tokenized_data_task(
})
}
#[cfg(feature = "basilisk")]
pub async fn start_tokenize_data_workflow(
state: &routes::AppState,
tokenize_tracker: &storage::ProcessTracker,
@ -924,8 +1213,10 @@ pub async fn start_tokenize_data_workflow(
)
})?;
match delete_tokenized_data(state, &delete_tokenize_data.lookup_key).await {
Ok(()) => {
let delete_resp = delete_tokenized_data(state, &delete_tokenize_data.lookup_key).await;
match delete_resp {
Ok(resp) => {
if resp == "Ok" {
logger::info!("Card From locker deleted Successfully");
//mark task as finished
let id = tokenize_tracker.id.clone();
@ -933,6 +1224,12 @@ pub async fn start_tokenize_data_workflow(
.clone()
.finish_with_status(db.as_scheduler(), format!("COMPLETED_BY_PT_{id}"))
.await?;
} else {
logger::error!("Error: Deleting Card From Locker : {:?}", resp);
retry_delete_tokenize(db, &delete_tokenize_data.pm, tokenize_tracker.to_owned())
.await?;
metrics::RETRIED_DELETE_DATA_COUNT.add(&metrics::CONTEXT, 1, &[]);
}
}
Err(err) => {
logger::error!("Err: Deleting Card From Locker : {:?}", err);
@ -944,6 +1241,7 @@ pub async fn start_tokenize_data_workflow(
Ok(())
}
#[cfg(feature = "basilisk")]
pub async fn get_delete_tokenize_schedule_time(
db: &dyn db::StorageInterface,
pm: &enums::PaymentMethod,
@ -967,6 +1265,7 @@ pub async fn get_delete_tokenize_schedule_time(
process_tracker_utils::get_time_from_delta(time_delta)
}
#[cfg(feature = "basilisk")]
pub async fn retry_delete_tokenize(
db: &dyn db::StorageInterface,
pm: &enums::PaymentMethod,

View File

@ -1,5 +1,5 @@
use async_trait::async_trait;
use error_stack::{self, IntoReport, ResultExt};
use error_stack;
use super::{ConstructFlowSpecificData, Feature};
use crate::{
@ -95,8 +95,7 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
metrics::PAYMENT_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics
if resp.request.setup_mandate_details.clone().is_some() {
let payment_method_id = tokenization::save_payment_method(
let save_payment_result = tokenization::save_payment_method(
state,
connector,
resp.to_owned(),
@ -105,57 +104,21 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
self.request.payment_method_type,
key_store,
)
.await?;
Ok(
mandate::mandate_procedure(state, resp, maybe_customer, payment_method_id)
.await?,
)
} else {
let arbiter = actix::Arbiter::try_current()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report()
.attach_printable("arbiter retrieval failure")
.map_err(|err| {
logger::error!(?err);
err
})
.ok();
let connector = connector.clone();
let response = resp.clone();
let maybe_customer = maybe_customer.clone();
let merchant_account = merchant_account.clone();
let key_store = key_store.clone();
let state = state.clone();
logger::info!("Initiating async call to save_payment_method in locker");
arbiter.map(|arb| {
arb.spawn(async move {
logger::info!("Starting async call to save_payment_method in locker");
let result = tokenization::save_payment_method(
&state,
&connector,
response,
&maybe_customer,
&merchant_account,
self.request.payment_method_type,
&key_store,
)
.await;
if let Err(err) = result {
logger::error!(
"Asynchronously saving card in locker failed : {:?}",
err
);
let pm_id = match save_payment_result {
Ok(payment_method_id) => Ok(payment_method_id),
Err(error) => {
if resp.request.setup_mandate_details.clone().is_some() {
Err(error)
} else {
logger::error!(save_payment_method_error=?error);
Ok(None)
}
})
});
}
}?;
Ok(resp)
}
Ok(mandate::mandate_procedure(state, resp, maybe_customer, pm_id).await?)
} else {
Ok(self.clone())
}

View File

@ -1,7 +1,6 @@
use common_utils::{ext_traits::ValueExt, pii};
use error_stack::{report, ResultExt};
use masking::ExposeInterface;
use router_env::{instrument, tracing};
use super::helpers;
use crate::{
@ -21,7 +20,6 @@ use crate::{
utils::OptionExt,
};
#[instrument(skip_all)]
pub async fn save_payment_method<F: Clone, FData>(
state: &AppState,
connector: &api::ConnectorData,

View File

@ -128,12 +128,6 @@ impl AppState {
#[allow(clippy::expect_used)]
let kms_secrets = settings::ActiveKmsSecrets {
jwekey: conf.jwekey.clone().into(),
redis_temp_locker_encryption_key: conf
.locker
.redis_temp_locker_encryption_key
.clone()
.into_bytes()
.into(),
}
.decrypt_inner(kms_client)
.await
@ -141,7 +135,6 @@ impl AppState {
#[cfg(feature = "email")]
let email_client = Arc::new(AwsSes::new(&conf.email).await);
Self {
flow_name: String::from("default"),
store,

View File

@ -110,6 +110,7 @@ pub(super) fn create_client(
pub fn proxy_bypass_urls(locker: &Locker) -> Vec<String> {
let locker_host = locker.host.to_owned();
let basilisk_host = locker.basilisk_host.to_owned();
vec![
format!("{locker_host}/cards/add"),
format!("{locker_host}/cards/retrieve"),
@ -117,6 +118,10 @@ pub fn proxy_bypass_urls(locker: &Locker) -> Vec<String> {
format!("{locker_host}/card/addCard"),
format!("{locker_host}/card/getCard"),
format!("{locker_host}/card/deleteCard"),
format!("{basilisk_host}/tokenize"),
format!("{basilisk_host}/tokenize/get"),
format!("{basilisk_host}/tokenize/delete"),
format!("{basilisk_host}/tokenize/delete/token"),
]
}

View File

@ -1,11 +1,12 @@
use api_models::enums as api_enums;
pub use api_models::payment_methods::{
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod,
CustomerPaymentMethodsListResponse, GetTokenizePayloadRequest, GetTokenizePayloadResponse,
PaymentMethodCreate, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList,
PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodResponse,
PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest,
TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2,
CustomerPaymentMethodsListResponse, DeleteTokenizeByDateRequest, DeleteTokenizeByTokenRequest,
GetTokenizePayloadRequest, GetTokenizePayloadResponse, PaymentMethodCreate,
PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, PaymentMethodListRequest,
PaymentMethodListResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData,
TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2,
TokenizedWalletValue1, TokenizedWalletValue2,
};
use error_stack::report;

View File

@ -1,13 +1,14 @@
use scheduler::consumer::workflows::ProcessTrackerWorkflow;
use crate::{
core::payment_methods::vault, errors, logger::error, routes::AppState, types::storage,
};
#[cfg(feature = "basilisk")]
use crate::core::payment_methods::vault;
use crate::{errors, logger::error, routes::AppState, types::storage};
pub struct DeleteTokenizeDataWorkflow;
#[async_trait::async_trait]
impl ProcessTrackerWorkflow<AppState> for DeleteTokenizeDataWorkflow {
#[cfg(feature = "basilisk")]
async fn execute_workflow<'a>(
&'a self,
state: &'a AppState,
@ -16,6 +17,15 @@ impl ProcessTrackerWorkflow<AppState> for DeleteTokenizeDataWorkflow {
Ok(vault::start_tokenize_data_workflow(state, &process).await?)
}
#[cfg(not(feature = "basilisk"))]
async fn execute_workflow<'a>(
&'a self,
_state: &'a AppState,
_process: storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError> {
Ok(())
}
async fn error_handler<'a>(
&'a self,
_state: &'a AppState,

View File

@ -32,7 +32,6 @@ jwt_secret = "secret"
host = ""
mock_locker = true
basilisk_host = ""
redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
[eph_key]
validity = 1