diff --git a/config/config.example.toml b/config/config.example.toml index c46f23dab4..3ce1e02f00 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -162,6 +162,11 @@ max_age = 365 # Max age of a refund in days. outgoing_enabled = true redis_lock_expiry_seconds = 180 +# Controls whether merchant ID authentication is enabled. +# When enabled, payment endpoints will accept and require a x-merchant-id header in the request. +[merchant_id_auth] +merchant_id_auth_enabled = false + # Validity of an Ephemeral Key in Hours [eph_key] validity = 1 diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 022ba701e8..22fde3b995 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -155,6 +155,11 @@ bg_metrics_collection_interval_in_secs = 15 # Interval for collecting delay_between_retries_in_milliseconds = 500 # Delay between retries in milliseconds redis_lock_expiry_seconds = 180 # Seconds before the redis lock expires +# Controls whether merchant ID authentication is enabled. +# When enabled, payment endpoints will accept and require a x-merchant-id header in the request. +[merchant_id_auth] +merchant_id_auth_enabled = false + # Main SQL data store credentials [master_database] username = "db_user" # DB Username diff --git a/config/development.toml b/config/development.toml index c686a93994..03862761e4 100644 --- a/config/development.toml +++ b/config/development.toml @@ -197,6 +197,9 @@ max_age = 365 outgoing_enabled = true redis_lock_expiry_seconds = 180 # 3 * 60 seconds +[merchant_id_auth] +merchant_id_auth_enabled = false + [eph_key] validity = 1 diff --git a/config/docker_compose.toml b/config/docker_compose.toml index f217c2df4a..e68dcea836 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -893,6 +893,9 @@ delay_between_retries_in_milliseconds = 500 outgoing_enabled = true redis_lock_expiry_seconds = 180 # 3 * 60 seconds +[merchant_id_auth] +merchant_id_auth_enabled = false + [events.kafka] brokers = ["localhost:9092"] fraud_check_analytics_topic = "hyperswitch-fraud-check-events" diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index bc664cca95..b4c579a07a 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -540,6 +540,7 @@ pub(crate) async fn fetch_raw_secrets( revenue_recovery: conf.revenue_recovery, debit_routing_config: conf.debit_routing_config, clone_connector_allowlist: conf.clone_connector_allowlist, + merchant_id_auth: conf.merchant_id_auth, infra_values: conf.infra_values, } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 8bd826dc33..9ce9106ce2 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -158,6 +158,7 @@ pub struct Settings { #[cfg(feature = "v2")] pub revenue_recovery: revenue_recovery::RevenueRecoverySettings, pub clone_connector_allowlist: Option, + pub merchant_id_auth: MerchantIdAuthSettings, #[serde(default)] pub infra_values: Option>, } @@ -817,6 +818,12 @@ pub struct DrainerSettings { pub loop_interval: u32, // in milliseconds } +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] +pub struct MerchantIdAuthSettings { + pub merchant_id_auth_enabled: bool, +} + #[derive(Debug, Clone, Default, Deserialize)] #[serde(default)] pub struct WebhooksSettings { diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 12930b680e..25f916dc8a 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -262,6 +262,27 @@ pub async fn payments_create_and_confirm_intent( } }; + let auth_type = if state.conf.merchant_id_auth.merchant_id_auth_enabled { + &auth::MerchantIdAuth + } else { + match env::which() { + env::Env::Production => &auth::V2ApiKeyAuth { + is_connected_allowed: false, + is_platform_allowed: false, + }, + _ => auth::auth_type( + &auth::V2ApiKeyAuth { + is_connected_allowed: false, + is_platform_allowed: false, + }, + &auth::JWTAuth { + permission: Permission::ProfilePaymentWrite, + }, + req.headers(), + ), + } + }; + Box::pin(api::server_wrap( flow, state, @@ -280,22 +301,7 @@ pub async fn payments_create_and_confirm_intent( header_payload.clone(), ) }, - match env::which() { - env::Env::Production => &auth::V2ApiKeyAuth { - is_connected_allowed: false, - is_platform_allowed: false, - }, - _ => auth::auth_type( - &auth::V2ApiKeyAuth { - is_connected_allowed: false, - is_platform_allowed: false, - }, - &auth::JWTAuth { - permission: Permission::ProfilePaymentWrite, - }, - req.headers(), - ), - }, + auth_type, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 5fd65a22be..35b7395038 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -2184,6 +2184,7 @@ where } #[derive(Debug)] +#[cfg(feature = "v1")] pub struct MerchantIdAuth(pub id_type::MerchantId); #[cfg(feature = "v1")] @@ -2233,6 +2234,10 @@ where } } +#[derive(Debug)] +#[cfg(feature = "v2")] +pub struct MerchantIdAuth; + #[cfg(feature = "v2")] #[async_trait] impl AuthenticateAndFetch for MerchantIdAuth @@ -2249,6 +2254,8 @@ where } let key_manager_state = &(&state.session_state()).into(); + let merchant_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; let profile_id = get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? .get_required_value(headers::X_PROFILE_ID)?; @@ -2256,7 +2263,7 @@ where .store() .get_merchant_key_store_by_merchant_id( key_manager_state, - &self.0, + &merchant_id, &state.store().get_master_key().to_vec().into(), ) .await @@ -2267,14 +2274,14 @@ where .find_business_profile_by_merchant_id_profile_id( key_manager_state, &key_store, - &self.0, + &merchant_id, &profile_id, ) .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let merchant = state .store() - .find_merchant_account_by_merchant_id(key_manager_state, &self.0, &key_store) + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;