mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(router): add merchantId authentication for Payments v2 (#8239)
Co-authored-by: Aishwariyaa Anand <aishwariyaa.anand@Aishwariyaa-Anand-C3PGW02T6Y.local>
This commit is contained in:
		 Aishwariyaa Anand
					Aishwariyaa Anand
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							4fa7b12400
						
					
				
				
					commit
					45d4ebfde3
				
			| @ -162,6 +162,11 @@ max_age = 365     # Max age of a refund in days. | |||||||
| outgoing_enabled = true | outgoing_enabled = true | ||||||
| redis_lock_expiry_seconds = 180 | 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 | # Validity of an Ephemeral Key in Hours | ||||||
| [eph_key] | [eph_key] | ||||||
| validity = 1 | validity = 1 | ||||||
|  | |||||||
| @ -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 | delay_between_retries_in_milliseconds = 500 # Delay between retries in milliseconds | ||||||
| redis_lock_expiry_seconds = 180             # Seconds before the redis lock expires | 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 | # Main SQL data store credentials | ||||||
| [master_database] | [master_database] | ||||||
| username = "db_user"      # DB Username | username = "db_user"      # DB Username | ||||||
|  | |||||||
| @ -197,6 +197,9 @@ max_age = 365 | |||||||
| outgoing_enabled = true | outgoing_enabled = true | ||||||
| redis_lock_expiry_seconds = 180             # 3 * 60 seconds | redis_lock_expiry_seconds = 180             # 3 * 60 seconds | ||||||
|  |  | ||||||
|  | [merchant_id_auth] | ||||||
|  | merchant_id_auth_enabled = false | ||||||
|  |  | ||||||
| [eph_key] | [eph_key] | ||||||
| validity = 1 | validity = 1 | ||||||
|  |  | ||||||
|  | |||||||
| @ -893,6 +893,9 @@ delay_between_retries_in_milliseconds = 500 | |||||||
| outgoing_enabled = true | outgoing_enabled = true | ||||||
| redis_lock_expiry_seconds = 180             # 3 * 60 seconds | redis_lock_expiry_seconds = 180             # 3 * 60 seconds | ||||||
|  |  | ||||||
|  | [merchant_id_auth] | ||||||
|  | merchant_id_auth_enabled = false | ||||||
|  |  | ||||||
| [events.kafka] | [events.kafka] | ||||||
| brokers = ["localhost:9092"] | brokers = ["localhost:9092"] | ||||||
| fraud_check_analytics_topic = "hyperswitch-fraud-check-events" | fraud_check_analytics_topic = "hyperswitch-fraud-check-events" | ||||||
|  | |||||||
| @ -540,6 +540,7 @@ pub(crate) async fn fetch_raw_secrets( | |||||||
|         revenue_recovery: conf.revenue_recovery, |         revenue_recovery: conf.revenue_recovery, | ||||||
|         debit_routing_config: conf.debit_routing_config, |         debit_routing_config: conf.debit_routing_config, | ||||||
|         clone_connector_allowlist: conf.clone_connector_allowlist, |         clone_connector_allowlist: conf.clone_connector_allowlist, | ||||||
|  |         merchant_id_auth: conf.merchant_id_auth, | ||||||
|         infra_values: conf.infra_values, |         infra_values: conf.infra_values, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -158,6 +158,7 @@ pub struct Settings<S: SecretState> { | |||||||
|     #[cfg(feature = "v2")] |     #[cfg(feature = "v2")] | ||||||
|     pub revenue_recovery: revenue_recovery::RevenueRecoverySettings, |     pub revenue_recovery: revenue_recovery::RevenueRecoverySettings, | ||||||
|     pub clone_connector_allowlist: Option<CloneConnectorAllowlistConfig>, |     pub clone_connector_allowlist: Option<CloneConnectorAllowlistConfig>, | ||||||
|  |     pub merchant_id_auth: MerchantIdAuthSettings, | ||||||
|     #[serde(default)] |     #[serde(default)] | ||||||
|     pub infra_values: Option<HashMap<String, String>>, |     pub infra_values: Option<HashMap<String, String>>, | ||||||
| } | } | ||||||
| @ -817,6 +818,12 @@ pub struct DrainerSettings { | |||||||
|     pub loop_interval: u32,     // in milliseconds |     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)] | #[derive(Debug, Clone, Default, Deserialize)] | ||||||
| #[serde(default)] | #[serde(default)] | ||||||
| pub struct WebhooksSettings { | pub struct WebhooksSettings { | ||||||
|  | |||||||
| @ -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( |     Box::pin(api::server_wrap( | ||||||
|         flow, |         flow, | ||||||
|         state, |         state, | ||||||
| @ -280,22 +301,7 @@ pub async fn payments_create_and_confirm_intent( | |||||||
|                 header_payload.clone(), |                 header_payload.clone(), | ||||||
|             ) |             ) | ||||||
|         }, |         }, | ||||||
|         match env::which() { |         auth_type, | ||||||
|             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(), |  | ||||||
|             ), |  | ||||||
|         }, |  | ||||||
|         api_locking::LockAction::NotApplicable, |         api_locking::LockAction::NotApplicable, | ||||||
|     )) |     )) | ||||||
|     .await |     .await | ||||||
|  | |||||||
| @ -2184,6 +2184,7 @@ where | |||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
|  | #[cfg(feature = "v1")] | ||||||
| pub struct MerchantIdAuth(pub id_type::MerchantId); | pub struct MerchantIdAuth(pub id_type::MerchantId); | ||||||
|  |  | ||||||
| #[cfg(feature = "v1")] | #[cfg(feature = "v1")] | ||||||
| @ -2233,6 +2234,10 @@ where | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | #[cfg(feature = "v2")] | ||||||
|  | pub struct MerchantIdAuth; | ||||||
|  |  | ||||||
| #[cfg(feature = "v2")] | #[cfg(feature = "v2")] | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl<A> AuthenticateAndFetch<AuthenticationData, A> for MerchantIdAuth | impl<A> AuthenticateAndFetch<AuthenticationData, A> for MerchantIdAuth | ||||||
| @ -2249,6 +2254,8 @@ where | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         let key_manager_state = &(&state.session_state()).into(); |         let key_manager_state = &(&state.session_state()).into(); | ||||||
|  |         let merchant_id = HeaderMapStruct::new(request_headers) | ||||||
|  |             .get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?; | ||||||
|         let profile_id = |         let profile_id = | ||||||
|             get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? |             get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? | ||||||
|                 .get_required_value(headers::X_PROFILE_ID)?; |                 .get_required_value(headers::X_PROFILE_ID)?; | ||||||
| @ -2256,7 +2263,7 @@ where | |||||||
|             .store() |             .store() | ||||||
|             .get_merchant_key_store_by_merchant_id( |             .get_merchant_key_store_by_merchant_id( | ||||||
|                 key_manager_state, |                 key_manager_state, | ||||||
|                 &self.0, |                 &merchant_id, | ||||||
|                 &state.store().get_master_key().to_vec().into(), |                 &state.store().get_master_key().to_vec().into(), | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
| @ -2267,14 +2274,14 @@ where | |||||||
|             .find_business_profile_by_merchant_id_profile_id( |             .find_business_profile_by_merchant_id_profile_id( | ||||||
|                 key_manager_state, |                 key_manager_state, | ||||||
|                 &key_store, |                 &key_store, | ||||||
|                 &self.0, |                 &merchant_id, | ||||||
|                 &profile_id, |                 &profile_id, | ||||||
|             ) |             ) | ||||||
|             .await |             .await | ||||||
|             .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; |             .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; | ||||||
|         let merchant = state |         let merchant = state | ||||||
|             .store() |             .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 |             .await | ||||||
|             .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; |             .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user