diff --git a/crates/api_models/src/blocklist.rs b/crates/api_models/src/blocklist.rs index 888b9106ccc..9672b6de6ee 100644 --- a/crates/api_models/src/blocklist.rs +++ b/crates/api_models/src/blocklist.rs @@ -22,6 +22,11 @@ pub struct BlocklistResponse { pub created_at: time::PrimitiveDateTime, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct ToggleBlocklistResponse { + pub blocklist_guard_status: String, +} + pub type AddToBlocklistResponse = BlocklistResponse; pub type DeleteFromBlocklistResponse = BlocklistResponse; @@ -39,6 +44,14 @@ fn default_list_limit() -> u16 { 10 } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct ToggleBlocklistQuery { + #[schema(value_type = BlocklistDataKind)] + pub status: bool, +} + impl ApiEventMetric for BlocklistRequest {} impl ApiEventMetric for BlocklistResponse {} +impl ApiEventMetric for ToggleBlocklistResponse {} impl ApiEventMetric for ListBlocklistQuery {} +impl ApiEventMetric for ToggleBlocklistQuery {} diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 982072e8c09..1936300ee14 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -151,6 +151,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::blocklist::remove_entry_from_blocklist, routes::blocklist::list_blocked_payment_methods, routes::blocklist::add_entry_to_blocklist, + routes::blocklist::toggle_blocklist_guard, // Routes for payouts routes::payouts::payouts_create, @@ -450,6 +451,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentLinkStatus, api_models::blocklist::BlocklistRequest, api_models::blocklist::BlocklistResponse, + api_models::blocklist::ToggleBlocklistResponse, api_models::blocklist::ListBlocklistQuery, api_models::enums::BlocklistDataKind )), diff --git a/crates/openapi/src/routes/blocklist.rs b/crates/openapi/src/routes/blocklist.rs index bf683259e08..741925b93d2 100644 --- a/crates/openapi/src/routes/blocklist.rs +++ b/crates/openapi/src/routes/blocklist.rs @@ -1,3 +1,19 @@ +#[utoipa::path( + post, + path = "/blocklist/toggle", + params ( + ("status" = bool, Query, description = "Boolean value to enable/disable blocklist"), + ), + responses( + (status = 200, description = "Blocklist guard enabled/disabled", body = ToggleBlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "Toggle blocklist guard for a particular merchant", + security(("api_key" = [])) +)] +pub async fn toggle_blocklist_guard() {} + #[utoipa::path( post, path = "/blocklist", diff --git a/crates/router/src/core/blocklist.rs b/crates/router/src/core/blocklist.rs index 85845602449..12a0802517c 100644 --- a/crates/router/src/core/blocklist.rs +++ b/crates/router/src/core/blocklist.rs @@ -39,3 +39,13 @@ pub async fn list_blocklist_entries( .await .map(services::ApplicationResponse::Json) } + +pub async fn toggle_blocklist_guard( + state: AppState, + merchant_account: domain::MerchantAccount, + query: api_blocklist::ToggleBlocklistQuery, +) -> RouterResponse { + utils::toggle_blocklist_guard_for_merchant(&state, merchant_account.merchant_id, query) + .await + .map(services::ApplicationResponse::Json) +} diff --git a/crates/router/src/core/blocklist/utils.rs b/crates/router/src/core/blocklist/utils.rs index bc0a7335f1e..43701c80786 100644 --- a/crates/router/src/core/blocklist/utils.rs +++ b/crates/router/src/core/blocklist/utils.rs @@ -4,6 +4,7 @@ use common_utils::{ crypto::{self, SignMessage}, errors::CustomResult, }; +use diesel_models::configs; use error_stack::{IntoReport, ResultExt}; #[cfg(feature = "aws_kms")] use external_services::aws_kms; @@ -85,6 +86,56 @@ pub async fn delete_entry_from_blocklist( Ok(blocklist_entry.foreign_into()) } +pub async fn toggle_blocklist_guard_for_merchant( + state: &AppState, + merchant_id: String, + query: api_blocklist::ToggleBlocklistQuery, +) -> CustomResult { + let key = get_blocklist_guard_key(merchant_id.as_str()); + let maybe_guard = state.store.find_config_by_key(&key).await; + let new_config = configs::ConfigNew { + key: key.clone(), + config: query.status.to_string(), + }; + match maybe_guard { + Ok(_config) => { + let updated_config = configs::ConfigUpdate::Update { + config: Some(query.status.to_string()), + }; + state + .store + .update_config_by_key(&key, updated_config) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error enabling the blocklist guard")?; + } + Err(e) if e.current_context().is_db_not_found() => { + state + .store + .insert_config(new_config) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error enabling the blocklist guard")?; + } + Err(e) => { + logger::error!(error=?e); + Err(e) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error enabling the blocklist guard")?; + } + }; + let guard_status = if query.status { "enabled" } else { "disabled" }; + Ok(api_blocklist::ToggleBlocklistResponse { + blocklist_guard_status: guard_status.to_string(), + }) +} + +/// Provides the identifier for the specific merchant's blocklist guard config +#[inline(always)] +pub fn get_blocklist_guard_key(merchant_id: &str) -> String { + format!("guard_blocklist_for_{merchant_id}") +} + pub async fn list_blocklist_entries_for_merchant( state: &AppState, merchant_id: String, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index f71592c5448..651d3c0026f 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -658,6 +658,9 @@ impl Blocklist { .route(web::post().to(blocklist::add_entry_to_blocklist)) .route(web::delete().to(blocklist::remove_entry_from_blocklist)), ) + .service( + web::resource("/toggle").route(web::post().to(blocklist::toggle_blocklist_guard)), + ) } } diff --git a/crates/router/src/routes/blocklist.rs b/crates/router/src/routes/blocklist.rs index 9c93f49ab83..87072d2c777 100644 --- a/crates/router/src/routes/blocklist.rs +++ b/crates/router/src/routes/blocklist.rs @@ -117,3 +117,41 @@ pub async fn list_blocked_payment_methods( )) .await } + +#[utoipa::path( + post, + path = "/blocklist/toggle", + params ( + ("status" = bool, Query, description = "Boolean value to enable/disable blocklist"), + ), + responses( + (status = 200, description = "Blocklist guard enabled/disabled", body = ToggleBlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "Toggle blocklist guard for a particular merchant", + security(("api_key" = [])) +)] +pub async fn toggle_blocklist_guard( + state: web::Data, + req: HttpRequest, + query_payload: web::Query, +) -> HttpResponse { + let flow = Flow::ListBlocklist; + Box::pin(api::server_wrap( + flow, + state, + &req, + query_payload.into_inner(), + |state, auth: auth::AuthenticationData, query| { + blocklist::toggle_blocklist_guard(state, auth.merchant_account, query) + }, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MerchantAccountWrite), + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index c05dd3afd7c..2bb8eadb7e6 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -63,6 +63,7 @@ impl From for ApiIdentifier { Flow::AddToBlocklist => Self::Blocklist, Flow::DeleteFromBlocklist => Self::Blocklist, Flow::ListBlocklist => Self::Blocklist, + Flow::ToggleBlocklistGuard => Self::Blocklist, Flow::MerchantConnectorsCreate | Flow::MerchantConnectorsRetrieve diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 3e4fd698ced..1422599b220 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -205,6 +205,8 @@ pub enum Flow { DeleteFromBlocklist, /// List entries from blocklist ListBlocklist, + /// Toggle blocklist for merchant + ToggleBlocklistGuard, /// Incoming Webhook Receive IncomingWebhookReceive, /// Validate payment method flow diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 7f234661b51..b6531771e11 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -1233,6 +1233,45 @@ ] } }, + "/blocklist/toggle": { + "post": { + "tags": [ + "Blocklist" + ], + "operationId": "Toggle blocklist guard for a particular merchant", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Boolean value to enable/disable blocklist", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Blocklist guard enabled/disabled", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToggleBlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/customers": { "post": { "tags": [ @@ -16114,6 +16153,17 @@ } } }, + "ToggleBlocklistResponse": { + "type": "object", + "required": [ + "blocklist_guard_status" + ], + "properties": { + "blocklist_guard_status": { + "type": "string" + } + } + }, "TouchNGoRedirection": { "type": "object" },