diff --git a/config/config.example.toml b/config/config.example.toml index db562ffe7e66..5d95566f1689 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -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 diff --git a/config/development.toml b/config/development.toml index 701805ffe0ad..7cb2d0f86991 100644 --- a/config/development.toml +++ b/config/development.toml @@ -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 = "" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index de984597d055..c01888ea5bba 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -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 = "" diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 8c1d00e21261..3bb1c31d180b 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -51,7 +51,6 @@ 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 122bc53e70a5..365b64843ffa 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -52,7 +52,6 @@ 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)] @@ -413,7 +412,6 @@ 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 d090ca00b8dc..569262d0d210 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -62,15 +62,6 @@ 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 93918432f50b..2161ab69222e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2010,13 +2010,9 @@ 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_temp_locker( - state, - payment_token, - card, - pm, - ) - .await?; + let resp = + BasiliskCardSupport::create_payment_method_data_in_locker(state, payment_token, card, pm) + .await?; Ok(resp) } @@ -2064,9 +2060,104 @@ 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_temp_locker( + async fn create_payment_method_data_in_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 945745ef38c1..d16269deb9b2 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -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, @@ -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, 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( @@ -704,42 +893,49 @@ impl Vault { lookup_key: &Option, ) { 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------------------------------------------------ +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 + } +} -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")?; +#[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(secret) + Ok((public_key.to_string(), private_key.to_string())) } -#[instrument(skip(state, value1, value2))] +#[cfg(feature = "basilisk")] pub async fn create_tokenize( state: &routes::AppState, value1: String, @@ -747,125 +943,216 @@ 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.to_owned(), + lookup_key, 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 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 - .map(|_| lookup_key) - .map_err(|err| { + .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) => { metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); - err - }) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error from redis locker") + 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 { 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::(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 { - message: "Token is invalid or expired".into(), - }) + 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 { 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() + 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("Failed to get redis connection")?; - - let response = redis_conn.delete_key(redis_key.as_str()).await; + .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(redis_interface::DelReply::KeyDeleted) => Ok(()), - Ok(redis_interface::DelReply::KeyNotDeleted) => { - Err(errors::ApiErrorResponse::InternalServerError) + Ok(r) => { + let delete_response = std::str::from_utf8(&r.response) .into_report() - .attach_printable("Token invalid or expired") + .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, @@ -908,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, @@ -925,15 +1213,23 @@ pub async fn start_tokenize_data_workflow( ) })?; - 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?; + 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, &[]); + } } Err(err) => { logger::error!("Err: Deleting Card From Locker : {:?}", err); @@ -945,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, @@ -968,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, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 0199339e86c6..3164e74dcddc 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::{self, IntoReport, ResultExt}; +use error_stack; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -95,67 +95,30 @@ impl Feature 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( - 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) - } + 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?) } else { Ok(self.clone()) } diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 794180e2112e..f7831465e1ce 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -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( state: &AppState, connector: &api::ConnectorData, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 3d9295794886..5b16e93404ae 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -128,11 +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(), } .decrypt_inner(kms_client) .await @@ -140,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, diff --git a/crates/router/src/workflows/tokenized_data.rs b/crates/router/src/workflows/tokenized_data.rs index 0674982f92fe..2f5d33173276 100644 --- a/crates/router/src/workflows/tokenized_data.rs +++ b/crates/router/src/workflows/tokenized_data.rs @@ -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 for DeleteTokenizeDataWorkflow { + #[cfg(feature = "basilisk")] async fn execute_workflow<'a>( &'a self, state: &'a AppState, @@ -16,6 +17,15 @@ 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 47235601cb0e..c2ecd68dc841 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -32,7 +32,6 @@ jwt_secret = "secret" host = "" mock_locker = true basilisk_host = "" -redis_temp_locker_encryption_key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" [eph_key] validity = 1