mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +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