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( | ||||
|     state: SessionState, | ||||
|     merchant_id: id_type::MerchantId, | ||||
|     profile_id_list: Option<Vec<id_type::ProfileId>>, | ||||
| ) -> RouterResponse<Vec<api_models::admin::BusinessProfileResponse>> { | ||||
|     let db = state.store.as_ref(); | ||||
|     let key_store = db | ||||
| @ -3732,6 +3733,7 @@ pub async fn list_business_profile( | ||||
|         .await | ||||
|         .to_not_found_response(errors::ApiErrorResponse::InternalServerError)? | ||||
|         .clone(); | ||||
|     let profiles = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, profiles); | ||||
|     let mut business_profiles = Vec::new(); | ||||
|     for profile in profiles { | ||||
|         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>; | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>( | ||||
|     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>, | ||||
|     object: &T, | ||||
| ) -> RouterResult<()> { | ||||
|  | ||||
| @ -119,7 +119,9 @@ pub fn mk_app( | ||||
|         { | ||||
|             // This is a more specific route as compared to `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 | ||||
|             .service(routes::Payments::server(state.clone())) | ||||
|  | ||||
| @ -59,10 +59,10 @@ pub use self::app::Forex; | ||||
| #[cfg(all(feature = "olap", feature = "recon"))] | ||||
| pub use self::app::Recon; | ||||
| pub use self::app::{ | ||||
|     ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, Cache, Cards, Configs, | ||||
|     ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates, | ||||
|     MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, | ||||
|     Refunds, SessionState, User, Webhooks, | ||||
|     ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, BusinessProfileNew, Cache, | ||||
|     Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, | ||||
|     Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, | ||||
|     Poll, Refunds, SessionState, User, Webhooks, | ||||
| }; | ||||
| #[cfg(feature = "olap")] | ||||
| pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; | ||||
|  | ||||
| @ -892,8 +892,9 @@ pub async fn business_profile_update( | ||||
|         }, | ||||
|         auth::auth_type( | ||||
|             &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()), | ||||
|             &auth::JWTAuthMerchantFromRoute { | ||||
|             &auth::JWTAuthMerchantAndProfileFromRoute { | ||||
|                 merchant_id: merchant_id.clone(), | ||||
|                 profile_id: profile_id.clone(), | ||||
|                 required_permission: Permission::MerchantAccountWrite, | ||||
|             }, | ||||
|             req.headers(), | ||||
| @ -971,9 +972,43 @@ pub async fn business_profiles_list( | ||||
|         state, | ||||
|         &req, | ||||
|         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::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 { | ||||
|                 merchant_id, | ||||
|                 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; | ||||
|  | ||||
| #[cfg(feature = "olap")] | ||||
|  | ||||
| @ -88,6 +88,11 @@ pub enum AuthenticationType { | ||||
|         merchant_id: id_type::MerchantId, | ||||
|         user_id: Option<String>, | ||||
|     }, | ||||
|     MerchantJwtWithProfileId { | ||||
|         merchant_id: id_type::MerchantId, | ||||
|         profile_id: Option<id_type::ProfileId>, | ||||
|         user_id: String, | ||||
|     }, | ||||
|     UserJwt { | ||||
|         user_id: String, | ||||
|     }, | ||||
| @ -137,6 +142,7 @@ impl AuthenticationType { | ||||
|                 merchant_id, | ||||
|                 user_id: _, | ||||
|             } | ||||
|             | Self::MerchantJwtWithProfileId { merchant_id, .. } | ||||
|             | Self::WebhookAuth { merchant_id } => Some(merchant_id), | ||||
|             Self::AdminApiKey | ||||
|             | 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> | ||||
| where | ||||
|     T: serde::de::DeserializeOwned, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Hrithikesh
					Hrithikesh