diff --git a/config/config.example.toml b/config/config.example.toml index e97b73d87c6b..bfbd27f0681e 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -115,6 +115,7 @@ 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 diff --git a/config/development.toml b/config/development.toml index a29cc9ec5be2..0f9214f978e4 100644 --- a/config/development.toml +++ b/config/development.toml @@ -50,6 +50,7 @@ applepay_endpoint = "DOMAIN SPECIFIC ENDPOINT" host = "" mock_locker = true basilisk_host = "" +redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" [jwekey] locker_key_identifier1 = "" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 8504ec130e62..1c7a56bfb8ef 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -46,6 +46,7 @@ recon_admin_api_key = "recon_test_admin" host = "" mock_locker = true basilisk_host = "" +redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" [jwekey] locker_key_identifier1 = "" diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 3bb1c31d180b..8c1d00e21261 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -51,6 +51,7 @@ impl Default for super::settings::Locker { mock_locker: true, basilisk_host: "localhost".into(), locker_signing_key_id: "1".into(), + redis_temp_locker_encryption_key: "".into(), } } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 5dd58c5d5f26..83fde253938f 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -52,6 +52,7 @@ pub enum Subcommand { #[derive(Clone)] pub struct ActiveKmsSecrets { pub jwekey: masking::Secret, + pub redis_temp_locker_encryption_key: masking::Secret, } #[derive(Debug, Deserialize, Clone, Default)] @@ -412,6 +413,7 @@ pub struct Locker { 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)] diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index 569262d0d210..d090ca00b8dc 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -62,6 +62,15 @@ impl super::settings::Locker { "basilisk host must not be empty when mock locker is disabled".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(), + )) + }, ) } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 2161ab69222e..93918432f50b 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2010,9 +2010,13 @@ 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 = - BasiliskCardSupport::create_payment_method_data_in_locker(state, payment_token, card, pm) - .await?; + let resp = BasiliskCardSupport::create_payment_method_data_in_temp_locker( + state, + payment_token, + card, + pm, + ) + .await?; Ok(resp) } @@ -2060,104 +2064,9 @@ pub async fn get_lookup_key_for_payout_method( pub struct BasiliskCardSupport; -#[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 { - 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::::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::::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::::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_locker( + async fn create_payment_method_data_in_temp_locker( state: &routes::AppState, payment_token: &str, card: api::CardDetailFromLocker, diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index d16269deb9b2..945745ef38c1 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1,37 +1,31 @@ -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 common_utils::{ + crypto::{DecodeMessage, EncodeMessage, GcmAes256}, + ext_traits::BytesExt, + generate_id_with_default_len, +}; +use error_stack::{report, IntoReport, ResultExt}; 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}, - logger, routes, + db, logger, routes, + routes::metrics, types::{ api, - storage::{self, enums}, + storage::{self, enums, ProcessTrackerExt}, }, 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"; -#[cfg(feature = "basilisk")] -const VAULT_VERSION: &str = "0"; + +const LOCKER_REDIS_PREFIX: &str = "LOCKER_TOKEN"; + +const LOCKER_REDIS_EXPIRY_SECONDS: u32 = 60 * 15; // 15 minutes pub struct SupplementaryVaultData { pub customer_id: Option, @@ -622,189 +616,6 @@ 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, 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, 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, - payout_method: &api::PayoutMethodData, - customer_id: Option, - ) -> RouterResult { - 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::::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, - payment_method: &api::PaymentMethodData, - customer_id: Option, - _pm: enums::PaymentMethod, - ) -> RouterResult { - 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::::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, - ) { - 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( @@ -893,49 +704,42 @@ impl Vault { lookup_key: &Option, ) { if let Some(lookup_key) = lookup_key { - 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), - } + 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(); } } } //------------------------------------------------TokenizeService------------------------------------------------ -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 - } -} -#[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()); - }; +fn get_redis_temp_locker_encryption_key(state: &routes::AppState) -> RouterResult> { + #[cfg(feature = "kms")] + let secret = state + .kms_secrets + .redis_temp_locker_encryption_key + .peek() + .as_bytes() + .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((public_key.to_string(), private_key.to_string())) + Ok(secret) } -#[cfg(feature = "basilisk")] +#[instrument(skip(state, value1, value2))] pub async fn create_tokenize( state: &routes::AppState, value1: String, @@ -943,216 +747,125 @@ pub async fn create_tokenize( lookup_key: String, ) -> RouterResult { 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: lookup_key.to_owned(), service_name: VAULT_SERVICE_NAME.to_string(), }; + let payload = utils::Encode::::encode_to_string_of_json( &payload_to_be_encrypted, ) .change_context(errors::ApiErrorResponse::InternalServerError)?; - let (public_key, private_key) = get_locker_jwe_keys(&state.kms_secrets) - .await + let secret = get_redis_temp_locker_encryption_key(state)?; + + let encrypted_payload = GcmAes256 + .encode_message(secret.as_ref(), payload.as_bytes()) .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encryption key")?; - let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key) - .await + .attach_printable("Failed to encode redis temp locker data")?; + + let redis_conn = state + .store + .get_redis_conn() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; + .attach_printable("Failed to get redis connection")?; - 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) + 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)), + ) .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 - .change_context(errors::ApiErrorResponse::InternalServerError) - .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) => { + .map(|_| lookup_key) + .map_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:?}")) - } - } + err + }) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error from redis locker") } -#[cfg(feature = "basilisk")] +#[instrument(skip(state))] pub async fn get_tokenized_data( state: &routes::AppState, lookup_key: &str, - should_get_value2: bool, + _should_get_value2: bool, ) -> RouterResult { 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 (public_key, private_key) = get_locker_jwe_keys(&state.kms_secrets) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encryption key")?; - let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key) - .await + let redis_key = format!("{}_{}", LOCKER_REDIS_PREFIX, lookup_key); + + let redis_conn = state + .store + .get_redis_conn() .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)?; + .attach_printable("Failed to get redis connection")?; + + let response = redis_conn.get_key::(redis_key.as_str()).await; + match response { - Ok(r) => { - let resp: api::TokenizePayloadEncrypted = r - .response - .parse_struct("TokenizePayloadEncrypted") + 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())) .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 - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("GetTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted")?; - let get_response: api::TokenizePayloadRequest = decrypted_payload + .attach_printable("Failed to decode redis temp locker data")?; + + let get_response: api::TokenizePayloadRequest = bytes::Bytes::from(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, &[]); - 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:?}")), - } + Err(err).change_context(errors::ApiErrorResponse::UnprocessableEntity { + message: "Token is invalid or expired".into(), + }) } } } -#[cfg(feature = "basilisk")] -pub async fn delete_tokenized_data( - state: &routes::AppState, - lookup_key: &str, -) -> RouterResult { +#[instrument(skip(state))] +pub async fn delete_tokenized_data(state: &routes::AppState, lookup_key: &str) -> RouterResult<()> { metrics::DELETED_TOKENIZED_CARD.add(&metrics::CONTEXT, 1, &[]); - 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() - .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 + let redis_key = format!("{}_{}", LOCKER_REDIS_PREFIX, lookup_key); + + let redis_conn = state + .store + .get_redis_conn() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error while making /tokenize/delete/token call to the locker")?; + .attach_printable("Failed to get redis connection")?; + + let response = redis_conn.delete_key(redis_key.as_str()).await; + match response { - Ok(r) => { - let delete_response = std::str::from_utf8(&r.response) + Ok(redis_interface::DelReply::KeyDeleted) => Ok(()), + Ok(redis_interface::DelReply::KeyNotDeleted) => { + Err(errors::ApiErrorResponse::InternalServerError) .into_report() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for basilisk delete response")?; - Ok(delete_response.to_string()) + .attach_printable("Token invalid or expired") } 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:?}")) + .attach_printable_lazy(|| format!("Failed to delete from redis locker: {err:?}")) } } } // ********************************************** PROCESS TRACKER ********************************************** -#[cfg(feature = "basilisk")] + pub async fn add_delete_tokenized_data_task( db: &dyn db::StorageInterface, lookup_key: &str, @@ -1195,7 +908,6 @@ 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, @@ -1213,23 +925,15 @@ pub async fn start_tokenize_data_workflow( ) })?; - 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(); - tokenize_tracker - .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, &[]); - } + match delete_tokenized_data(state, &delete_tokenize_data.lookup_key).await { + Ok(()) => { + logger::info!("Card From locker deleted Successfully"); + //mark task as finished + let id = tokenize_tracker.id.clone(); + tokenize_tracker + .clone() + .finish_with_status(db.as_scheduler(), format!("COMPLETED_BY_PT_{id}")) + .await?; } Err(err) => { logger::error!("Err: Deleting Card From Locker : {:?}", err); @@ -1241,7 +945,6 @@ 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, @@ -1265,7 +968,6 @@ 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, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 330ef6efe139..ac0bd29187eb 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use error_stack; +use error_stack::{self, IntoReport, ResultExt}; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -89,30 +89,67 @@ impl Feature for types::PaymentsAu metrics::PAYMENT_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics - let save_payment_result = tokenization::save_payment_method( - state, - connector, - resp.to_owned(), - maybe_customer, - merchant_account, - self.request.payment_method_type, - key_store, - ) - .await; - - 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(mandate::mandate_procedure(state, resp, maybe_customer, pm_id).await?) + if resp.request.setup_mandate_details.clone().is_some() { + let payment_method_id = tokenization::save_payment_method( + state, + connector, + resp.to_owned(), + maybe_customer, + merchant_account, + 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 + ); + } + }) + }); + + Ok(resp) + } } else { Ok(self.clone()) } diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index f7831465e1ce..794180e2112e 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -1,6 +1,7 @@ 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::{ @@ -20,6 +21,7 @@ use crate::{ utils::OptionExt, }; +#[instrument(skip_all)] pub async fn save_payment_method( state: &AppState, connector: &api::ConnectorData, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 7f18220438c4..8b6034c5f2c2 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -121,6 +121,11 @@ 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(), } .decrypt_inner(kms_client) .await @@ -128,6 +133,7 @@ impl AppState { #[cfg(feature = "email")] let email_client = Arc::new(AwsSes::new(&conf.email).await); + Self { flow_name: String::from("default"), store, diff --git a/crates/router/src/workflows/tokenized_data.rs b/crates/router/src/workflows/tokenized_data.rs index 2f5d33173276..0674982f92fe 100644 --- a/crates/router/src/workflows/tokenized_data.rs +++ b/crates/router/src/workflows/tokenized_data.rs @@ -1,14 +1,13 @@ use scheduler::consumer::workflows::ProcessTrackerWorkflow; -#[cfg(feature = "basilisk")] -use crate::core::payment_methods::vault; -use crate::{errors, logger::error, routes::AppState, types::storage}; +use crate::{ + core::payment_methods::vault, errors, logger::error, routes::AppState, types::storage, +}; pub struct DeleteTokenizeDataWorkflow; #[async_trait::async_trait] impl ProcessTrackerWorkflow for DeleteTokenizeDataWorkflow { - #[cfg(feature = "basilisk")] async fn execute_workflow<'a>( &'a self, state: &'a AppState, @@ -17,15 +16,6 @@ impl ProcessTrackerWorkflow 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, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index bee79de458d6..7d62d8db3f25 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -32,6 +32,7 @@ jwt_secret = "secret" host = "" mock_locker = true basilisk_host = "" +redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" [eph_key] validity = 1