mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat: add profile_id authentication for business profile update and list (#5673)
This commit is contained in:
		| @ -3717,6 +3717,7 @@ pub async fn create_business_profile( | |||||||
| pub async fn list_business_profile( | pub async fn list_business_profile( | ||||||
|     state: SessionState, |     state: SessionState, | ||||||
|     merchant_id: id_type::MerchantId, |     merchant_id: id_type::MerchantId, | ||||||
|  |     profile_id_list: Option<Vec<id_type::ProfileId>>, | ||||||
| ) -> RouterResponse<Vec<api_models::admin::BusinessProfileResponse>> { | ) -> RouterResponse<Vec<api_models::admin::BusinessProfileResponse>> { | ||||||
|     let db = state.store.as_ref(); |     let db = state.store.as_ref(); | ||||||
|     let key_store = db |     let key_store = db | ||||||
| @ -3732,6 +3733,7 @@ pub async fn list_business_profile( | |||||||
|         .await |         .await | ||||||
|         .to_not_found_response(errors::ApiErrorResponse::InternalServerError)? |         .to_not_found_response(errors::ApiErrorResponse::InternalServerError)? | ||||||
|         .clone(); |         .clone(); | ||||||
|  |     let profiles = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, profiles); | ||||||
|     let mut business_profiles = Vec::new(); |     let mut business_profiles = Vec::new(); | ||||||
|     for profile in profiles { |     for profile in profiles { | ||||||
|         let business_profile = |         let business_profile = | ||||||
|  | |||||||
| @ -1334,7 +1334,7 @@ pub fn get_incremental_authorization_allowed_value( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(super) trait GetProfileId { | pub(crate) trait GetProfileId { | ||||||
|     fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId>; |     fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId>; | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -1381,6 +1381,12 @@ impl<T, F> GetProfileId for (storage::Payouts, T, F) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl GetProfileId for domain::BusinessProfile { | ||||||
|  |     fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> { | ||||||
|  |         Some(self.get_id()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Filter Objects based on profile ids | /// Filter Objects based on profile ids | ||||||
| pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>( | pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>( | ||||||
|     profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>, |     profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>, | ||||||
| @ -1406,7 +1412,7 @@ pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(super) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>( | pub(crate) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>( | ||||||
|     profile_id_auth_layer: Option<common_utils::id_type::ProfileId>, |     profile_id_auth_layer: Option<common_utils::id_type::ProfileId>, | ||||||
|     object: &T, |     object: &T, | ||||||
| ) -> RouterResult<()> { | ) -> RouterResult<()> { | ||||||
|  | |||||||
| @ -119,7 +119,9 @@ pub fn mk_app( | |||||||
|         { |         { | ||||||
|             // This is a more specific route as compared to `MerchantConnectorAccount` |             // This is a more specific route as compared to `MerchantConnectorAccount` | ||||||
|             // so it is registered before `MerchantConnectorAccount`. |             // so it is registered before `MerchantConnectorAccount`. | ||||||
|             server_app = server_app.service(routes::BusinessProfile::server(state.clone())) |             server_app = server_app | ||||||
|  |                 .service(routes::BusinessProfileNew::server(state.clone())) | ||||||
|  |                 .service(routes::BusinessProfile::server(state.clone())) | ||||||
|         } |         } | ||||||
|         server_app = server_app |         server_app = server_app | ||||||
|             .service(routes::Payments::server(state.clone())) |             .service(routes::Payments::server(state.clone())) | ||||||
|  | |||||||
| @ -59,10 +59,10 @@ pub use self::app::Forex; | |||||||
| #[cfg(all(feature = "olap", feature = "recon"))] | #[cfg(all(feature = "olap", feature = "recon"))] | ||||||
| pub use self::app::Recon; | pub use self::app::Recon; | ||||||
| pub use self::app::{ | pub use self::app::{ | ||||||
|     ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, Cache, Cards, Configs, |     ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, BusinessProfileNew, Cache, | ||||||
|     ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates, |     Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, | ||||||
|     MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, |     Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, | ||||||
|     Refunds, SessionState, User, Webhooks, |     Poll, Refunds, SessionState, User, Webhooks, | ||||||
| }; | }; | ||||||
| #[cfg(feature = "olap")] | #[cfg(feature = "olap")] | ||||||
| pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; | pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; | ||||||
|  | |||||||
| @ -892,8 +892,9 @@ pub async fn business_profile_update( | |||||||
|         }, |         }, | ||||||
|         auth::auth_type( |         auth::auth_type( | ||||||
|             &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()), |             &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()), | ||||||
|             &auth::JWTAuthMerchantFromRoute { |             &auth::JWTAuthMerchantAndProfileFromRoute { | ||||||
|                 merchant_id: merchant_id.clone(), |                 merchant_id: merchant_id.clone(), | ||||||
|  |                 profile_id: profile_id.clone(), | ||||||
|                 required_permission: Permission::MerchantAccountWrite, |                 required_permission: Permission::MerchantAccountWrite, | ||||||
|             }, |             }, | ||||||
|             req.headers(), |             req.headers(), | ||||||
| @ -971,9 +972,43 @@ pub async fn business_profiles_list( | |||||||
|         state, |         state, | ||||||
|         &req, |         &req, | ||||||
|         merchant_id.clone(), |         merchant_id.clone(), | ||||||
|         |state, _, merchant_id, _| list_business_profile(state, merchant_id), |         |state, _auth, merchant_id, _| list_business_profile(state, merchant_id, None), | ||||||
|         auth::auth_type( |         auth::auth_type( | ||||||
|             &auth::AdminApiAuth, |             &auth::AdminApiAuthWithMerchantIdFromHeader, | ||||||
|  |             &auth::JWTAuthMerchantFromRoute { | ||||||
|  |                 merchant_id, | ||||||
|  |                 required_permission: Permission::MerchantAccountRead, | ||||||
|  |             }, | ||||||
|  |             req.headers(), | ||||||
|  |         ), | ||||||
|  |         api_locking::LockAction::NotApplicable, | ||||||
|  |     )) | ||||||
|  |     .await | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[instrument(skip_all, fields(flow = ?Flow::BusinessProfileList))] | ||||||
|  | pub async fn business_profiles_list_at_profile_level( | ||||||
|  |     state: web::Data<AppState>, | ||||||
|  |     req: HttpRequest, | ||||||
|  |     path: web::Path<common_utils::id_type::MerchantId>, | ||||||
|  | ) -> HttpResponse { | ||||||
|  |     let flow = Flow::BusinessProfileList; | ||||||
|  |     let merchant_id = path.into_inner(); | ||||||
|  |  | ||||||
|  |     Box::pin(api::server_wrap( | ||||||
|  |         flow, | ||||||
|  |         state, | ||||||
|  |         &req, | ||||||
|  |         merchant_id.clone(), | ||||||
|  |         |state, auth, merchant_id, _| { | ||||||
|  |             list_business_profile( | ||||||
|  |                 state, | ||||||
|  |                 merchant_id, | ||||||
|  |                 auth.profile_id.map(|profile_id| vec![profile_id]), | ||||||
|  |             ) | ||||||
|  |         }, | ||||||
|  |         auth::auth_type( | ||||||
|  |             &auth::AdminApiAuthWithMerchantIdFromHeader, | ||||||
|             &auth::JWTAuthMerchantFromRoute { |             &auth::JWTAuthMerchantFromRoute { | ||||||
|                 merchant_id, |                 merchant_id, | ||||||
|                 required_permission: Permission::MerchantAccountRead, |                 required_permission: Permission::MerchantAccountRead, | ||||||
|  | |||||||
| @ -1639,6 +1639,19 @@ impl BusinessProfile { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub struct BusinessProfileNew; | ||||||
|  |  | ||||||
|  | #[cfg(feature = "olap")] | ||||||
|  | impl BusinessProfileNew { | ||||||
|  |     pub fn server(state: AppState) -> Scope { | ||||||
|  |         web::scope("/account/{account_id}/profile") | ||||||
|  |             .app_data(web::Data::new(state)) | ||||||
|  |             .service( | ||||||
|  |                 web::resource("").route(web::get().to(business_profiles_list_at_profile_level)), | ||||||
|  |             ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub struct Gsm; | pub struct Gsm; | ||||||
|  |  | ||||||
| #[cfg(feature = "olap")] | #[cfg(feature = "olap")] | ||||||
|  | |||||||
| @ -88,6 +88,11 @@ pub enum AuthenticationType { | |||||||
|         merchant_id: id_type::MerchantId, |         merchant_id: id_type::MerchantId, | ||||||
|         user_id: Option<String>, |         user_id: Option<String>, | ||||||
|     }, |     }, | ||||||
|  |     MerchantJwtWithProfileId { | ||||||
|  |         merchant_id: id_type::MerchantId, | ||||||
|  |         profile_id: Option<id_type::ProfileId>, | ||||||
|  |         user_id: String, | ||||||
|  |     }, | ||||||
|     UserJwt { |     UserJwt { | ||||||
|         user_id: String, |         user_id: String, | ||||||
|     }, |     }, | ||||||
| @ -137,6 +142,7 @@ impl AuthenticationType { | |||||||
|                 merchant_id, |                 merchant_id, | ||||||
|                 user_id: _, |                 user_id: _, | ||||||
|             } |             } | ||||||
|  |             | Self::MerchantJwtWithProfileId { merchant_id, .. } | ||||||
|             | Self::WebhookAuth { merchant_id } => Some(merchant_id), |             | Self::WebhookAuth { merchant_id } => Some(merchant_id), | ||||||
|             Self::AdminApiKey |             Self::AdminApiKey | ||||||
|             | Self::UserJwt { .. } |             | Self::UserJwt { .. } | ||||||
| @ -1324,6 +1330,80 @@ where | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub struct JWTAuthMerchantAndProfileFromRoute { | ||||||
|  |     pub merchant_id: id_type::MerchantId, | ||||||
|  |     pub profile_id: id_type::ProfileId, | ||||||
|  |     pub required_permission: Permission, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait] | ||||||
|  | impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantAndProfileFromRoute | ||||||
|  | where | ||||||
|  |     A: SessionStateInfo + Sync, | ||||||
|  | { | ||||||
|  |     async fn authenticate_and_fetch( | ||||||
|  |         &self, | ||||||
|  |         request_headers: &HeaderMap, | ||||||
|  |         state: &A, | ||||||
|  |     ) -> RouterResult<(AuthenticationData, AuthenticationType)> { | ||||||
|  |         let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; | ||||||
|  |         if payload.check_in_blacklist(state).await? { | ||||||
|  |             return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if payload.merchant_id != self.merchant_id { | ||||||
|  |             return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if payload | ||||||
|  |             .profile_id | ||||||
|  |             .as_ref() | ||||||
|  |             .is_some_and(|profile_id| *profile_id != self.profile_id) | ||||||
|  |         { | ||||||
|  |             return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let permissions = authorization::get_permissions(state, &payload).await?; | ||||||
|  |         authorization::check_authorization(&self.required_permission, &permissions)?; | ||||||
|  |         let key_manager_state = &(&state.session_state()).into(); | ||||||
|  |         let key_store = state | ||||||
|  |             .store() | ||||||
|  |             .get_merchant_key_store_by_merchant_id( | ||||||
|  |                 key_manager_state, | ||||||
|  |                 &payload.merchant_id, | ||||||
|  |                 &state.store().get_master_key().to_vec().into(), | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) | ||||||
|  |             .attach_printable("Failed to fetch merchant key store for the merchant id")?; | ||||||
|  |  | ||||||
|  |         let merchant = state | ||||||
|  |             .store() | ||||||
|  |             .find_merchant_account_by_merchant_id( | ||||||
|  |                 key_manager_state, | ||||||
|  |                 &payload.merchant_id, | ||||||
|  |                 &key_store, | ||||||
|  |             ) | ||||||
|  |             .await | ||||||
|  |             .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) | ||||||
|  |             .attach_printable("Failed to fetch merchant account for the merchant id")?; | ||||||
|  |  | ||||||
|  |         let auth = AuthenticationData { | ||||||
|  |             merchant_account: merchant, | ||||||
|  |             key_store, | ||||||
|  |             profile_id: payload.profile_id, | ||||||
|  |         }; | ||||||
|  |         Ok(( | ||||||
|  |             auth.clone(), | ||||||
|  |             AuthenticationType::MerchantJwtWithProfileId { | ||||||
|  |                 merchant_id: auth.merchant_account.get_id().clone(), | ||||||
|  |                 profile_id: auth.profile_id.clone(), | ||||||
|  |                 user_id: payload.user_id, | ||||||
|  |             }, | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn parse_jwt_payload<A, T>(headers: &HeaderMap, state: &A) -> RouterResult<T> | pub async fn parse_jwt_payload<A, T>(headers: &HeaderMap, state: &A) -> RouterResult<T> | ||||||
| where | where | ||||||
|     T: serde::de::DeserializeOwned, |     T: serde::de::DeserializeOwned, | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Hrithikesh
					Hrithikesh