refactor(euclid): check the authenticity of profile_id being used (#5647)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Prajjwal Kumar
2024-09-03 18:28:15 +05:30
committed by GitHub
parent 8ed942c6cd
commit 0fb8e85ee8
6 changed files with 330 additions and 60 deletions

View File

@ -57,18 +57,18 @@ pub struct ProfileDefaultRoutingConfig {
pub connectors: Vec<RoutableConnectorChoice>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingRetrieveQuery {
pub limit: Option<u16>,
pub offset: Option<u8>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingRetrieveLinkQuery {
pub profile_id: Option<common_utils::id_type::ProfileId>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingRetrieveLinkQueryWrapper {
pub routing_query: RoutingRetrieveQuery,
pub profile_id: common_utils::id_type::ProfileId,

View File

@ -84,12 +84,13 @@ impl RoutingAlgorithmUpdate {
pub async fn retrieve_merchant_routing_dictionary(
state: SessionState,
merchant_account: domain::MerchantAccount,
profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
query_params: RoutingRetrieveQuery,
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::RoutingKind> {
metrics::ROUTING_MERCHANT_DICTIONARY_RETRIEVE.add(&metrics::CONTEXT, 1, &[]);
let routing_metadata = state
let routing_metadata: Vec<diesel_models::routing_algorithm::RoutingProfileMetadata> = state
.store
.list_routing_algorithm_metadata_by_merchant_id_transaction_type(
merchant_account.get_id(),
@ -99,6 +100,9 @@ pub async fn retrieve_merchant_routing_dictionary(
)
.await
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
let routing_metadata =
super::utils::filter_objects_based_on_profile_id_list(profile_id_list, routing_metadata);
let result = routing_metadata
.into_iter()
.map(ForeignInto::foreign_into)
@ -115,6 +119,7 @@ pub async fn create_routing_algorithm_under_profile(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
request: routing_types::RoutingConfigRequest,
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
@ -132,6 +137,8 @@ pub async fn create_routing_algorithm_under_profile(
.await?
.get_required_value("BusinessProfile")?;
core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;
let all_mcas = helpers::MerchantConnectorAccounts::get_all_mcas(
merchant_account.get_id(),
&key_store,
@ -182,6 +189,7 @@ pub async fn create_routing_algorithm_under_profile(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
request: routing_types::RoutingConfigRequest,
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
@ -221,14 +229,17 @@ pub async fn create_routing_algorithm_under_profile(
})
.attach_printable("Profile_id not provided")?;
core_utils::validate_and_get_business_profile(
let business_profile = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
&key_store,
Some(&profile_id),
merchant_account.get_id(),
)
.await?;
.await?
.get_required_value("BusinessProfile")?;
core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;
helpers::validate_connectors_in_routing_config(
&state,
@ -345,6 +356,7 @@ pub async fn link_routing_config(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
algorithm_id: common_utils::id_type::RoutingId,
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
@ -373,6 +385,8 @@ pub async fn link_routing_config(
id: routing_algorithm.profile_id.get_string_repr().to_owned(),
})?;
core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;
let mut routing_ref: routing_types::RoutingAlgorithmRef = business_profile
.routing_algorithm
.clone()
@ -421,6 +435,7 @@ pub async fn retrieve_routing_algorithm_from_algorithm_id(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
algorithm_id: common_utils::id_type::RoutingId,
) -> RouterResponse<routing_types::MerchantRoutingAlgorithm> {
metrics::ROUTING_RETRIEVE_CONFIG.add(&metrics::CONTEXT, 1, &[]);
@ -430,7 +445,7 @@ pub async fn retrieve_routing_algorithm_from_algorithm_id(
let routing_algorithm =
RoutingAlgorithmUpdate::fetch_routing_algo(merchant_account.get_id(), &algorithm_id, db)
.await?;
core_utils::validate_and_get_business_profile(
let business_profile = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
&key_store,
@ -441,6 +456,8 @@ pub async fn retrieve_routing_algorithm_from_algorithm_id(
.get_required_value("BusinessProfile")
.change_context(errors::ApiErrorResponse::ResourceIdNotFound)?;
core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;
let response = routing_types::MerchantRoutingAlgorithm::foreign_try_from(routing_algorithm.0)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to parse routing algorithm")?;
@ -454,6 +471,7 @@ pub async fn retrieve_routing_algorithm_from_algorithm_id(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
algorithm_id: common_utils::id_type::RoutingId,
) -> RouterResponse<routing_types::MerchantRoutingAlgorithm> {
metrics::ROUTING_RETRIEVE_CONFIG.add(&metrics::CONTEXT, 1, &[]);
@ -468,7 +486,7 @@ pub async fn retrieve_routing_algorithm_from_algorithm_id(
.await
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
core_utils::validate_and_get_business_profile(
let business_profile = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
&key_store,
@ -479,6 +497,8 @@ pub async fn retrieve_routing_algorithm_from_algorithm_id(
.get_required_value("BusinessProfile")
.change_context(errors::ApiErrorResponse::ResourceIdNotFound)?;
core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;
let response = routing_types::MerchantRoutingAlgorithm::foreign_try_from(routing_algorithm)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to parse routing algorithm")?;
@ -554,9 +574,11 @@ pub async fn unlink_routing_config(
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
request: routing_types::RoutingConfigRequest,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
metrics::ROUTING_UNLINK_CONFIG.add(&metrics::CONTEXT, 1, &[]);
let db = state.store.as_ref();
let key_manager_state = &(&state).into();
@ -567,6 +589,7 @@ pub async fn unlink_routing_config(
field_name: "profile_id",
})
.attach_printable("Profile_id not provided")?;
let business_profile = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
@ -575,8 +598,13 @@ pub async fn unlink_routing_config(
merchant_account.get_id(),
)
.await?;
match business_profile {
Some(business_profile) => {
core_utils::validate_profile_id_from_auth_layer(
authentication_profile_id,
&business_profile,
)?;
let routing_algo_ref: routing_types::RoutingAlgorithmRef = match transaction_type {
enums::TransactionType::Payment => business_profile.routing_algorithm.clone(),
#[cfg(feature = "payouts")]
@ -880,6 +908,7 @@ pub async fn retrieve_linked_routing_config(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
authentication_profile_id: Option<common_utils::id_type::ProfileId>,
query_params: routing_types::RoutingRetrieveLinkQuery,
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::LinkedRoutingConfigRetrieveResponse> {
@ -902,13 +931,18 @@ pub async fn retrieve_linked_routing_config(
id: profile_id.get_string_repr().to_owned(),
})?
} else {
db.list_business_profile_by_merchant_id(
let business_profile = db
.list_business_profile_by_merchant_id(
key_manager_state,
&key_store,
merchant_account.get_id(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
core_utils::filter_objects_based_on_profile_id_list(
authentication_profile_id.map(|profile_id| vec![profile_id]),
business_profile.clone(),
)
};
let mut active_algorithms = Vec::new();
@ -1007,6 +1041,7 @@ pub async fn update_default_routing_config_for_profile(
transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::ProfileDefaultRoutingConfig> {
metrics::ROUTING_UPDATE_CONFIG_FOR_PROFILE.add(&metrics::CONTEXT, 1, &[]);
let db = state.store.as_ref();
let key_manager_state = &(&state).into();

View File

@ -1368,6 +1368,38 @@ impl GetProfileId for diesel_models::Refund {
}
}
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "routing_v2")))]
impl GetProfileId for api_models::routing::RoutingConfigRequest {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
self.profile_id.as_ref()
}
}
#[cfg(all(feature = "v2", feature = "routing_v2"))]
impl GetProfileId for api_models::routing::RoutingConfigRequest {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
Some(&self.profile_id)
}
}
impl GetProfileId for api_models::routing::RoutingRetrieveLinkQuery {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
self.profile_id.as_ref()
}
}
impl GetProfileId for diesel_models::routing_algorithm::RoutingProfileMetadata {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
Some(&self.profile_id)
}
}
impl GetProfileId for domain::BusinessProfile {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
Some(self.get_id())
}
}
#[cfg(feature = "payouts")]
impl GetProfileId for storage::Payouts {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
@ -1381,12 +1413,6 @@ 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>>,

View File

@ -711,6 +711,17 @@ impl Routing {
)
})),
)
.service(web::resource("/list/{profile_id}").route(web::get().to(
|state, req, path, query: web::Query<RoutingRetrieveQuery>| {
routing::list_routing_configs_for_profile(
state,
req,
query,
path,
&TransactionType::Payment,
)
},
)))
.service(
web::resource("/default")
.route(web::get().to(|state, req| {

View File

@ -34,6 +34,7 @@ pub async fn routing_create_config(
state,
auth.merchant_account,
auth.key_store,
auth.profile_id,
payload,
transaction_type,
)
@ -74,6 +75,7 @@ pub async fn routing_link_config(
state,
auth.merchant_account,
auth.key_store,
auth.profile_id,
algorithm,
transaction_type,
)
@ -110,7 +112,7 @@ pub async fn routing_link_config(
flow,
state,
&req,
wrapper,
wrapper.clone(),
|state, auth: auth::AuthenticationData, wrapper, _| {
routing::link_routing_config_under_profile(
state,
@ -124,11 +126,17 @@ pub async fn routing_link_config(
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::ApiKeyAuth,
&auth::JWTAuth(Permission::RoutingWrite),
&auth::JWTAuthProfileFromRoute {
profile_id: routing_payload_wrapper.profile_id,
required_permission: Permission::RoutingWrite,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuth(Permission::RoutingWrite),
&auth::JWTAuthProfileFromRoute {
profile_id: wrapper.profile_id,
required_permission: Permission::RoutingWrite,
},
api_locking::LockAction::NotApplicable,
))
.await
@ -153,6 +161,7 @@ pub async fn routing_retrieve_config(
state,
auth.merchant_account,
auth.key_store,
auth.profile_id,
algorithm_id,
)
},
@ -187,6 +196,7 @@ pub async fn list_routing_configs(
routing::retrieve_merchant_routing_dictionary(
state,
auth.merchant_account,
None,
query_params,
transaction_type,
)
@ -204,6 +214,50 @@ pub async fn list_routing_configs(
.await
}
#[cfg(feature = "olap")]
#[instrument(skip_all)]
pub async fn list_routing_configs_for_profile(
state: web::Data<AppState>,
req: HttpRequest,
query: web::Query<RoutingRetrieveQuery>,
path: web::Path<common_utils::id_type::ProfileId>,
transaction_type: &enums::TransactionType,
) -> impl Responder {
let flow = Flow::RoutingRetrieveDictionary;
let path = path.into_inner();
Box::pin(oss_api::server_wrap(
flow,
state,
&req,
query.into_inner(),
|state, auth: auth::AuthenticationData, query_params, _| {
routing::retrieve_merchant_routing_dictionary(
state,
auth.merchant_account,
auth.profile_id.map(|profile_id| vec![profile_id]),
query_params,
transaction_type,
)
},
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::HeaderAuth(auth::ApiKeyAuth),
&auth::JWTAuthProfileFromRoute {
profile_id: path,
required_permission: Permission::RoutingRead,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuthProfileFromRoute {
profile_id: path,
required_permission: Permission::RoutingRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(all(feature = "olap", feature = "v2", feature = "routing_v2"))]
#[instrument(skip_all)]
pub async fn routing_unlink_config(
@ -213,11 +267,12 @@ pub async fn routing_unlink_config(
transaction_type: &enums::TransactionType,
) -> impl Responder {
let flow = Flow::RoutingUnlinkConfig;
let path = path.into_inner();
Box::pin(oss_api::server_wrap(
flow,
state,
&req,
path.into_inner(),
path.clone(),
|state, auth: auth::AuthenticationData, path, _| {
routing::unlink_routing_config_under_profile(
state,
@ -230,11 +285,17 @@ pub async fn routing_unlink_config(
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::ApiKeyAuth,
&auth::JWTAuth(Permission::RoutingWrite),
&auth::JWTAuthProfileFromRoute {
profile_id: path,
required_permission: Permission::RoutingWrite,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuth(Permission::RoutingWrite),
&auth::JWTAuthProfileFromRoute {
profile_id: path,
required_permission: Permission::RoutingWrite,
},
api_locking::LockAction::NotApplicable,
))
.await
@ -264,6 +325,7 @@ pub async fn routing_unlink_config(
auth.merchant_account,
auth.key_store,
payload_req,
auth.profile_id,
transaction_type,
)
},
@ -374,11 +436,12 @@ pub async fn routing_retrieve_default_config(
req: HttpRequest,
path: web::Path<common_utils::id_type::ProfileId>,
) -> impl Responder {
let path = path.into_inner();
Box::pin(oss_api::server_wrap(
Flow::RoutingRetrieveDefaultConfig,
state,
&req,
path.into_inner(),
path.clone(),
|state, auth: auth::AuthenticationData, profile_id, _| {
routing::retrieve_default_fallback_algorithm_for_profile(
state,
@ -390,11 +453,17 @@ pub async fn routing_retrieve_default_config(
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::HeaderAuth(auth::ApiKeyAuth),
&auth::JWTAuth(Permission::RoutingRead),
&auth::JWTAuthProfileFromRoute {
profile_id: path,
required_permission: Permission::RoutingRead,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuth(Permission::RoutingRead),
&auth::JWTAuthProfileFromRoute {
profile_id: path,
required_permission: Permission::RoutingRead,
},
api_locking::LockAction::NotApplicable,
))
.await
@ -636,16 +705,52 @@ pub async fn routing_retrieve_linked_config(
) -> impl Responder {
use crate::services::authentication::AuthenticationData;
let flow = Flow::RoutingRetrieveActiveConfig;
let query = query.into_inner();
if let Some(profile_id) = query.profile_id.clone() {
Box::pin(oss_api::server_wrap(
flow,
state,
&req,
query.into_inner(),
query.clone(),
|state, auth: AuthenticationData, query_params, _| {
routing::retrieve_linked_routing_config(
state,
auth.merchant_account,
auth.key_store,
auth.profile_id,
query_params,
transaction_type,
)
},
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::HeaderAuth(auth::ApiKeyAuth),
&auth::JWTAuthProfileFromRoute {
profile_id,
required_permission: Permission::RoutingRead,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuthProfileFromRoute {
profile_id,
required_permission: Permission::RoutingRead,
},
api_locking::LockAction::NotApplicable,
))
.await
} else {
Box::pin(oss_api::server_wrap(
flow,
state,
&req,
query.clone(),
|state, auth: AuthenticationData, query_params, _| {
routing::retrieve_linked_routing_config(
state,
auth.merchant_account,
auth.key_store,
auth.profile_id,
query_params,
transaction_type,
)
@ -661,6 +766,7 @@ pub async fn routing_retrieve_linked_config(
api_locking::LockAction::NotApplicable,
))
.await
}
}
#[cfg(all(
@ -687,7 +793,7 @@ pub async fn routing_retrieve_linked_config(
flow,
state,
&req,
wrapper,
wrapper.clone(),
|state, auth: AuthenticationData, wrapper, _| {
routing::retrieve_routing_config_under_profile(
state,
@ -701,11 +807,17 @@ pub async fn routing_retrieve_linked_config(
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::HeaderAuth(auth::ApiKeyAuth),
&auth::JWTAuth(Permission::RoutingRead),
&auth::JWTAuthProfileFromRoute {
profile_id: wrapper.profile_id,
required_permission: Permission::RoutingRead,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuth(Permission::RoutingRead),
&auth::JWTAuthProfileFromRoute {
profile_id: wrapper.profile_id,
required_permission: Permission::RoutingRead,
},
api_locking::LockAction::NotApplicable,
))
.await
@ -765,7 +877,7 @@ pub async fn routing_update_default_config_for_profile(
Flow::RoutingUpdateDefaultConfig,
state,
&req,
routing_payload_wrapper,
routing_payload_wrapper.clone(),
|state, auth: auth::AuthenticationData, wrapper, _| {
routing::update_default_routing_config_for_profile(
state,
@ -779,11 +891,17 @@ pub async fn routing_update_default_config_for_profile(
#[cfg(not(feature = "release"))]
auth::auth_type(
&auth::HeaderAuth(auth::ApiKeyAuth),
&auth::JWTAuth(Permission::RoutingWrite),
&auth::JWTAuthProfileFromRoute {
profile_id: routing_payload_wrapper.profile_id,
required_permission: Permission::RoutingWrite,
},
req.headers(),
),
#[cfg(feature = "release")]
&auth::JWTAuth(Permission::RoutingWrite),
&auth::JWTAuthProfileFromRoute {
profile_id: routing_payload_wrapper.profile_id,
required_permission: Permission::RoutingWrite,
},
api_locking::LockAction::NotApplicable,
))
.await

View File

@ -1329,7 +1329,6 @@ where
))
}
}
pub struct JWTAuthMerchantAndProfileFromRoute {
pub merchant_id: id_type::MerchantId,
pub profile_id: id_type::ProfileId,
@ -1404,6 +1403,87 @@ where
}
}
pub struct JWTAuthProfileFromRoute {
pub profile_id: id_type::ProfileId,
pub required_permission: Permission,
}
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthProfileFromRoute
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());
}
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")?;
if let Some(ref payload_profile_id) = payload.profile_id {
if *payload_profile_id != self.profile_id {
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
} else {
// if both of them are same then proceed with the profile id present in the request
let auth = AuthenticationData {
merchant_account: merchant,
key_store,
profile_id: Some(self.profile_id.clone()),
};
Ok((
auth.clone(),
AuthenticationType::MerchantJwt {
merchant_id: auth.merchant_account.get_id().clone(),
user_id: Some(payload.user_id),
},
))
}
} else {
// if profile_id is not present in the auth_layer itself then no change in behaviour
let auth = AuthenticationData {
merchant_account: merchant,
key_store,
profile_id: payload.profile_id,
};
Ok((
auth.clone(),
AuthenticationType::MerchantJwt {
merchant_id: auth.merchant_account.get_id().clone(),
user_id: Some(payload.user_id),
},
))
}
}
}
pub async fn parse_jwt_payload<A, T>(headers: &HeaderMap, state: &A) -> RouterResult<T>
where
T: serde::de::DeserializeOwned,