mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(recon): add merchant and profile IDs in auth tokens (#5643)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -133,7 +133,6 @@ bg_metrics_collection_interval_in_secs = 15 # Interval for collecting
|
||||
master_enc_key = "sample_key" # Master Encryption key used to encrypt merchant wise encryption key. Should be 32-byte long.
|
||||
admin_api_key = "test_admin" # admin API key for admin authentication.
|
||||
jwt_secret = "secret" # JWT secret used for user authentication.
|
||||
recon_admin_api_key = "recon_test_admin" # recon_admin API key for recon authentication.
|
||||
|
||||
# Locker settings contain details for accessing a card locker, a
|
||||
# PCI Compliant storage entity which stores payment method information
|
||||
@ -722,4 +721,7 @@ public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "p
|
||||
encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table
|
||||
|
||||
[locker_based_open_banking_connectors]
|
||||
connector_list = ""
|
||||
connector_list = ""
|
||||
|
||||
[recipient_emails]
|
||||
recon = "test@example.com"
|
||||
|
||||
@ -256,7 +256,6 @@ url = "http://localhost:5000" # URL of the encryption service
|
||||
master_enc_key = "sample_key" # Master Encryption key used to encrypt merchant wise encryption key. Should be 32-byte long.
|
||||
admin_api_key = "test_admin" # admin API key for admin authentication.
|
||||
jwt_secret = "secret" # JWT secret used for user authentication.
|
||||
recon_admin_api_key = "recon_test_admin" # recon_admin API key for recon authentication.
|
||||
|
||||
# Server configuration
|
||||
[server]
|
||||
@ -300,3 +299,6 @@ public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "p
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table
|
||||
|
||||
[recipient_emails]
|
||||
recon = "recon@example.com"
|
||||
|
||||
@ -369,4 +369,4 @@ keys = "accept-language,user-agent"
|
||||
sdk_eligible_payment_methods = "card"
|
||||
|
||||
[locker_based_open_banking_connectors]
|
||||
connector_list = ""
|
||||
connector_list = ""
|
||||
@ -382,4 +382,4 @@ keys = "accept-language,user-agent"
|
||||
sdk_eligible_payment_methods = "card"
|
||||
|
||||
[locker_based_open_banking_connectors]
|
||||
connector_list = ""
|
||||
connector_list = ""
|
||||
@ -386,4 +386,4 @@ keys = "accept-language,user-agent"
|
||||
sdk_eligible_payment_methods = "card"
|
||||
|
||||
[locker_based_open_banking_connectors]
|
||||
connector_list = ""
|
||||
connector_list = ""
|
||||
@ -61,7 +61,6 @@ request_body_limit = 32768
|
||||
admin_api_key = "test_admin"
|
||||
master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"
|
||||
jwt_secret = "secret"
|
||||
recon_admin_api_key = "recon_test_admin"
|
||||
|
||||
[applepay_merchant_configs]
|
||||
merchant_cert_key = "MERCHANT CERTIFICATE KEY"
|
||||
@ -727,3 +726,6 @@ encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC69
|
||||
|
||||
[locker_based_open_banking_connectors]
|
||||
connector_list = ""
|
||||
|
||||
[recipient_emails]
|
||||
recon = "recon@example.com"
|
||||
|
||||
@ -50,7 +50,6 @@ pool_size = 5
|
||||
admin_api_key = "test_admin"
|
||||
jwt_secret = "secret"
|
||||
master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"
|
||||
recon_admin_api_key = "recon_test_admin"
|
||||
|
||||
[user]
|
||||
password_validity_in_days = 90
|
||||
@ -586,3 +585,6 @@ ach = { country = "US", currency = "USD" }
|
||||
|
||||
[locker_based_open_banking_connectors]
|
||||
connector_list = ""
|
||||
|
||||
[recipient_emails]
|
||||
recon = "recon@example.com"
|
||||
|
||||
@ -5,7 +5,6 @@ use crate::enums;
|
||||
|
||||
#[derive(serde::Deserialize, Debug, serde::Serialize)]
|
||||
pub struct ReconUpdateMerchantRequest {
|
||||
pub merchant_id: common_utils::id_type::MerchantId,
|
||||
pub recon_status: enums::ReconStatus,
|
||||
pub user_email: pii::Email,
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ pub enum Permission {
|
||||
PayoutRead,
|
||||
WebhookEventWrite,
|
||||
GenerateReport,
|
||||
ReconAdmin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash)]
|
||||
@ -50,6 +51,7 @@ pub enum ParentGroup {
|
||||
Merchant,
|
||||
#[serde(rename = "OrganizationAccess")]
|
||||
Organization,
|
||||
Recon,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
@ -67,6 +69,7 @@ pub enum PermissionModule {
|
||||
SurchargeDecisionManager,
|
||||
AccountCreate,
|
||||
Payouts,
|
||||
Recon,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
|
||||
@ -2795,6 +2795,7 @@ pub enum PermissionGroup {
|
||||
MerchantDetailsView,
|
||||
MerchantDetailsManage,
|
||||
OrganizationManage,
|
||||
ReconOps,
|
||||
}
|
||||
|
||||
/// Name of banks supported by Hyperswitch
|
||||
|
||||
@ -252,17 +252,15 @@ impl SecretsHandler for settings::Secrets {
|
||||
secret_management_client: &dyn SecretManagementInterface,
|
||||
) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError> {
|
||||
let secrets = value.get_inner();
|
||||
let (jwt_secret, admin_api_key, recon_admin_api_key, master_enc_key) = tokio::try_join!(
|
||||
let (jwt_secret, admin_api_key, master_enc_key) = tokio::try_join!(
|
||||
secret_management_client.get_secret(secrets.jwt_secret.clone()),
|
||||
secret_management_client.get_secret(secrets.admin_api_key.clone()),
|
||||
secret_management_client.get_secret(secrets.recon_admin_api_key.clone()),
|
||||
secret_management_client.get_secret(secrets.master_enc_key.clone())
|
||||
)?;
|
||||
|
||||
Ok(value.transition_state(|_| Self {
|
||||
jwt_secret,
|
||||
admin_api_key,
|
||||
recon_admin_api_key,
|
||||
master_enc_key,
|
||||
}))
|
||||
}
|
||||
@ -454,5 +452,6 @@ pub(crate) async fn fetch_raw_secrets(
|
||||
user_auth_methods,
|
||||
decision: conf.decision,
|
||||
locker_based_open_banking_connectors: conf.locker_based_open_banking_connectors,
|
||||
recipient_emails: conf.recipient_emails,
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use std::{
|
||||
#[cfg(feature = "olap")]
|
||||
use analytics::{opensearch::OpenSearchConfig, ReportConfig};
|
||||
use api_models::{enums, payment_methods::RequiredFieldInfo};
|
||||
use common_utils::ext_traits::ConfigExt;
|
||||
use common_utils::{ext_traits::ConfigExt, pii::Email};
|
||||
use config::{Environment, File};
|
||||
use error_stack::ResultExt;
|
||||
#[cfg(feature = "email")]
|
||||
@ -120,6 +120,7 @@ pub struct Settings<S: SecretState> {
|
||||
pub user_auth_methods: SecretStateContainer<UserAuthMethodSettings, S>,
|
||||
pub decision: Option<DecisionConfig>,
|
||||
pub locker_based_open_banking_connectors: LockerBasedRecipientConnectorList,
|
||||
pub recipient_emails: RecipientMails,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
@ -513,7 +514,6 @@ pub struct RequiredFieldFinal {
|
||||
pub struct Secrets {
|
||||
pub jwt_secret: Secret<String>,
|
||||
pub admin_api_key: Secret<String>,
|
||||
pub recon_admin_api_key: Secret<String>,
|
||||
pub master_enc_key: Secret<String>,
|
||||
}
|
||||
|
||||
@ -900,6 +900,11 @@ pub struct ServerTls {
|
||||
pub certificate: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
pub struct RecipientMails {
|
||||
pub recon: Email,
|
||||
}
|
||||
|
||||
fn deserialize_hashmap_inner<K, V>(
|
||||
value: HashMap<String, String>,
|
||||
) -> Result<HashMap<K, HashSet<V>>, String>
|
||||
|
||||
@ -136,3 +136,6 @@ pub const MAX_ALLOWED_AMOUNT: i64 = 999999999;
|
||||
//payment attempt default unified error code and unified error message
|
||||
pub const DEFAULT_UNIFIED_ERROR_CODE: &str = "UE_000";
|
||||
pub const DEFAULT_UNIFIED_ERROR_MESSAGE: &str = "Something went wrong";
|
||||
|
||||
// Recon's feature tag
|
||||
pub const RECON_FEATURE_TAG: &str = "RECONCILIATION AND SETTLEMENT";
|
||||
|
||||
@ -33,6 +33,8 @@ pub mod payout_link;
|
||||
pub mod payouts;
|
||||
pub mod pm_auth;
|
||||
pub mod poll;
|
||||
#[cfg(feature = "recon")]
|
||||
pub mod recon;
|
||||
pub mod refunds;
|
||||
pub mod routing;
|
||||
pub mod surcharge_decision_config;
|
||||
|
||||
172
crates/router/src/core/recon.rs
Normal file
172
crates/router/src/core/recon.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use api_models::recon as recon_api;
|
||||
use common_utils::ext_traits::AsyncExt;
|
||||
use error_stack::ResultExt;
|
||||
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||
|
||||
use crate::{
|
||||
consts,
|
||||
core::errors::{self, RouterResponse, UserErrors},
|
||||
services::{api as service_api, authentication, email::types as email_types},
|
||||
types::{
|
||||
api::{self as api_types, enums},
|
||||
domain, storage,
|
||||
transformers::ForeignTryFrom,
|
||||
},
|
||||
SessionState,
|
||||
};
|
||||
|
||||
pub async fn send_recon_request(
|
||||
state: SessionState,
|
||||
user_with_auth_data: authentication::UserFromTokenWithAuthData,
|
||||
) -> RouterResponse<recon_api::ReconStatusResponse> {
|
||||
let user = user_with_auth_data.0;
|
||||
let user_in_db = &user_with_auth_data.1.user;
|
||||
let merchant_id = user.merchant_id;
|
||||
|
||||
let user_email = user_in_db.email.clone();
|
||||
let email_contents = email_types::ProFeatureRequest {
|
||||
feature_name: consts::RECON_FEATURE_TAG.to_string(),
|
||||
merchant_id: merchant_id.clone(),
|
||||
user_name: domain::UserName::new(user_in_db.name.clone())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to form username")?,
|
||||
user_email: domain::UserEmail::from_pii_email(user_email.clone())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to convert recipient's email to UserEmail")?,
|
||||
recipient_email: domain::UserEmail::from_pii_email(
|
||||
state.conf.recipient_emails.recon.clone(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to convert recipient's email to UserEmail")?,
|
||||
settings: state.conf.clone(),
|
||||
subject: format!(
|
||||
"Dashboard Pro Feature Request by {}",
|
||||
user_email.expose().peek()
|
||||
),
|
||||
};
|
||||
|
||||
state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to compose and send email for ProFeatureRequest [Recon]")
|
||||
.async_and_then(|_| async {
|
||||
let auth = user_with_auth_data.1;
|
||||
let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate {
|
||||
recon_status: enums::ReconStatus::Requested,
|
||||
};
|
||||
let db = &*state.store;
|
||||
let key_manager_state = &(&state).into();
|
||||
|
||||
let response = db
|
||||
.update_merchant(
|
||||
key_manager_state,
|
||||
auth.merchant_account,
|
||||
updated_merchant_account,
|
||||
&auth.key_store,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable_lazy(|| {
|
||||
format!("Failed while updating merchant's recon status: {merchant_id:?}")
|
||||
})?;
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
recon_api::ReconStatusResponse {
|
||||
recon_status: response.recon_status,
|
||||
},
|
||||
))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn generate_recon_token(
|
||||
state: SessionState,
|
||||
user: authentication::UserFromToken,
|
||||
) -> RouterResponse<recon_api::ReconTokenResponse> {
|
||||
let token = authentication::AuthToken::new_token(
|
||||
user.user_id.clone(),
|
||||
user.merchant_id.clone(),
|
||||
user.role_id.clone(),
|
||||
&state.conf,
|
||||
user.org_id.clone(),
|
||||
user.profile_id.clone(),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Failed to create recon token for params [user_id, org_id, mid, pid] [{}, {:?}, {:?}, {:?}]",
|
||||
user.user_id, user.org_id, user.merchant_id, user.profile_id,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
recon_api::ReconTokenResponse {
|
||||
token: token.into(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn recon_merchant_account_update(
|
||||
state: SessionState,
|
||||
auth: authentication::AuthenticationData,
|
||||
req: recon_api::ReconUpdateMerchantRequest,
|
||||
) -> RouterResponse<api_types::MerchantAccountResponse> {
|
||||
let db = &*state.store;
|
||||
let key_manager_state = &(&state).into();
|
||||
|
||||
let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate {
|
||||
recon_status: req.recon_status,
|
||||
};
|
||||
let merchant_id = auth.merchant_account.get_id().clone();
|
||||
|
||||
let updated_merchant_account = db
|
||||
.update_merchant(
|
||||
key_manager_state,
|
||||
auth.merchant_account,
|
||||
updated_merchant_account,
|
||||
&auth.key_store,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable_lazy(|| {
|
||||
format!("Failed while updating merchant's recon status: {merchant_id:?}")
|
||||
})?;
|
||||
|
||||
let user_email = &req.user_email.clone();
|
||||
let email_contents = email_types::ReconActivation {
|
||||
recipient_email: domain::UserEmail::from_pii_email(user_email.clone())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to convert recipient's email to UserEmail from pii::Email")?,
|
||||
user_name: domain::UserName::new(Secret::new("HyperSwitch User".to_string()))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to form username")?,
|
||||
settings: state.conf.clone(),
|
||||
subject: "Approval of Recon Request - Access Granted to Recon Dashboard",
|
||||
};
|
||||
|
||||
if req.recon_status == enums::ReconStatus::Active {
|
||||
let _ = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to compose and send email for ReconActivation")
|
||||
.is_ok();
|
||||
}
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
api_types::MerchantAccountResponse::foreign_try_from(updated_merchant_account)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "merchant_account",
|
||||
})?,
|
||||
))
|
||||
}
|
||||
@ -1821,30 +1821,23 @@ pub async fn send_verification_mail(
|
||||
#[cfg(feature = "recon")]
|
||||
pub async fn verify_token(
|
||||
state: SessionState,
|
||||
req: auth::ReconUser,
|
||||
user: auth::UserFromToken,
|
||||
) -> UserResponse<user_api::VerifyTokenResponse> {
|
||||
let user = state
|
||||
let user_in_db = state
|
||||
.global_store
|
||||
.find_user_by_id(&req.user_id)
|
||||
.find_user_by_id(&user.user_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.current_context().is_db_not_found() {
|
||||
e.change_context(UserErrors::UserNotFound)
|
||||
} else {
|
||||
e.change_context(UserErrors::InternalServerError)
|
||||
}
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Failed to fetch the user from DB for user_id - {}",
|
||||
user.user_id
|
||||
)
|
||||
})?;
|
||||
let merchant_id = state
|
||||
.store
|
||||
.find_user_role_by_user_id(&req.user_id, UserRoleVersion::V1)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.merchant_id
|
||||
.ok_or(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::Json(user_api::VerifyTokenResponse {
|
||||
merchant_id: merchant_id.to_owned(),
|
||||
user_email: user.email,
|
||||
merchant_id: user.merchant_id.to_owned(),
|
||||
user_email: user_in_db.email,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -1130,7 +1130,7 @@ impl Recon {
|
||||
web::scope("/recon")
|
||||
.app_data(web::Data::new(state))
|
||||
.service(
|
||||
web::resource("/update_merchant")
|
||||
web::resource("/{merchant_id}/update")
|
||||
.route(web::post().to(recon_routes::update_merchant)),
|
||||
)
|
||||
.service(web::resource("/token").route(web::get().to(recon_routes::get_recon_token)))
|
||||
|
||||
@ -1,43 +1,29 @@
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use api_models::recon as recon_api;
|
||||
use diesel_models::enums::UserRoleVersion;
|
||||
use error_stack::ResultExt;
|
||||
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||
use api_models::{enums::EntityType, recon as recon_api};
|
||||
use router_env::Flow;
|
||||
|
||||
use super::{AppState, SessionState};
|
||||
use super::AppState;
|
||||
use crate::{
|
||||
core::{
|
||||
api_locking,
|
||||
errors::{self, RouterResponse, RouterResult, StorageErrorExt, UserErrors},
|
||||
},
|
||||
services::{
|
||||
api as service_api, api,
|
||||
authentication::{self as auth, ReconUser, UserFromToken},
|
||||
email::types as email_types,
|
||||
recon::ReconToken,
|
||||
},
|
||||
types::{
|
||||
api::{self as api_types, enums},
|
||||
domain::{UserEmail, UserFromStorage, UserName},
|
||||
storage,
|
||||
transformers::ForeignTryFrom,
|
||||
},
|
||||
core::{api_locking, recon},
|
||||
services::{api, authentication, authorization::permissions::Permission},
|
||||
};
|
||||
|
||||
pub async fn update_merchant(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<common_utils::id_type::MerchantId>,
|
||||
json_payload: web::Json<recon_api::ReconUpdateMerchantRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::ReconMerchantUpdate;
|
||||
let merchant_id = path.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, _user, req, _| recon_merchant_account_update(state, req),
|
||||
&auth::ReconAdmin,
|
||||
|state, auth, req, _| recon::recon_merchant_account_update(state, auth, req),
|
||||
&authentication::AdminApiAuthWithMerchantIdFromRoute(merchant_id),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
@ -50,8 +36,11 @@ pub async fn request_for_recon(state: web::Data<AppState>, http_req: HttpRequest
|
||||
state,
|
||||
&http_req,
|
||||
(),
|
||||
|state, user: UserFromToken, _req, _| send_recon_request(state, user),
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
|state, user, _, _| recon::send_recon_request(state, user),
|
||||
&authentication::JWTAuth {
|
||||
permission: Permission::ReconAdmin,
|
||||
minimum_entity_level: EntityType::Merchant,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
@ -64,203 +53,12 @@ pub async fn get_recon_token(state: web::Data<AppState>, req: HttpRequest) -> Ht
|
||||
state,
|
||||
&req,
|
||||
(),
|
||||
|state, user: ReconUser, _, _| generate_recon_token(state, user),
|
||||
&auth::ReconJWT,
|
||||
|state, user, _, _| recon::generate_recon_token(state, user),
|
||||
&authentication::JWTAuth {
|
||||
permission: Permission::ReconAdmin,
|
||||
minimum_entity_level: EntityType::Merchant,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_recon_request(
|
||||
state: SessionState,
|
||||
user: UserFromToken,
|
||||
) -> RouterResponse<recon_api::ReconStatusResponse> {
|
||||
let global_db = &*state.global_store;
|
||||
let db = &*state.store;
|
||||
let user_from_db = global_db
|
||||
.find_user_by_id(&user.user_id)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let merchant_id = db
|
||||
.find_user_role_by_user_id(&user.user_id, UserRoleVersion::V1)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?
|
||||
.merchant_id
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let key_manager_state = &(&state).into();
|
||||
let key_store = db
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
key_manager_state,
|
||||
&merchant_id,
|
||||
&db.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
let merchant_account = db
|
||||
.find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
|
||||
let email_contents = email_types::ProFeatureRequest {
|
||||
feature_name: "RECONCILIATION & SETTLEMENT".to_string(),
|
||||
merchant_id: merchant_id.clone(),
|
||||
user_name: UserName::new(user_from_db.name)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to form username")?,
|
||||
recipient_email: UserEmail::new(Secret::new("biz@hyperswitch.io".to_string()))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to convert recipient's email to UserEmail")?,
|
||||
settings: state.conf.clone(),
|
||||
subject: format!(
|
||||
"Dashboard Pro Feature Request by {}",
|
||||
user_from_db.email.expose().peek()
|
||||
),
|
||||
};
|
||||
|
||||
let is_email_sent = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to compose and send email for ProFeatureRequest")
|
||||
.is_ok();
|
||||
|
||||
if is_email_sent {
|
||||
let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate {
|
||||
recon_status: enums::ReconStatus::Requested,
|
||||
};
|
||||
|
||||
let response = db
|
||||
.update_merchant(
|
||||
key_manager_state,
|
||||
merchant_account,
|
||||
updated_merchant_account,
|
||||
&key_store,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable_lazy(|| {
|
||||
format!("Failed while updating merchant's recon status: {merchant_id:?}")
|
||||
})?;
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
recon_api::ReconStatusResponse {
|
||||
recon_status: response.recon_status,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
recon_api::ReconStatusResponse {
|
||||
recon_status: enums::ReconStatus::NotRequested,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recon_merchant_account_update(
|
||||
state: SessionState,
|
||||
req: recon_api::ReconUpdateMerchantRequest,
|
||||
) -> RouterResponse<api_types::MerchantAccountResponse> {
|
||||
let merchant_id = &req.merchant_id.clone();
|
||||
let user_email = &req.user_email.clone();
|
||||
|
||||
let db = &*state.store;
|
||||
|
||||
let key_store = db
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
&(&state).into(),
|
||||
&req.merchant_id,
|
||||
&db.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
|
||||
let merchant_account = db
|
||||
.find_merchant_account_by_merchant_id(&(&state).into(), merchant_id, &key_store)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
|
||||
let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate {
|
||||
recon_status: req.recon_status,
|
||||
};
|
||||
|
||||
let response = db
|
||||
.update_merchant(
|
||||
&(&state).into(),
|
||||
merchant_account,
|
||||
updated_merchant_account,
|
||||
&key_store,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable_lazy(|| {
|
||||
format!("Failed while updating merchant's recon status: {merchant_id:?}")
|
||||
})?;
|
||||
|
||||
let email_contents = email_types::ReconActivation {
|
||||
recipient_email: UserEmail::from_pii_email(user_email.clone())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to convert recipient's email to UserEmail from pii::Email")?,
|
||||
user_name: UserName::new(Secret::new("HyperSwitch User".to_string()))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to form username")?,
|
||||
settings: state.conf.clone(),
|
||||
subject: "Approval of Recon Request - Access Granted to Recon Dashboard",
|
||||
};
|
||||
|
||||
if req.recon_status == enums::ReconStatus::Active {
|
||||
let _is_email_sent = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to compose and send email for ReconActivation")
|
||||
.is_ok();
|
||||
}
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
api_types::MerchantAccountResponse::foreign_try_from(response).change_context(
|
||||
errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "merchant_account",
|
||||
},
|
||||
)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn generate_recon_token(
|
||||
state: SessionState,
|
||||
req: ReconUser,
|
||||
) -> RouterResponse<recon_api::ReconTokenResponse> {
|
||||
let db = &*state.global_store;
|
||||
let user = db
|
||||
.find_user_by_id(&req.user_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.current_context().is_db_not_found() {
|
||||
e.change_context(errors::ApiErrorResponse::InvalidJwtToken)
|
||||
} else {
|
||||
e.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
}
|
||||
})?
|
||||
.into();
|
||||
|
||||
let token = Box::pin(get_recon_auth_token(user, state))
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
recon_api::ReconTokenResponse { token },
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_recon_auth_token(
|
||||
user: UserFromStorage,
|
||||
state: SessionState,
|
||||
) -> RouterResult<Secret<String>> {
|
||||
ReconToken::new_token(user.0.user_id.clone(), &state.conf).await
|
||||
}
|
||||
|
||||
@ -575,7 +575,7 @@ pub async fn verify_recon_token(state: web::Data<AppState>, http_req: HttpReques
|
||||
&http_req,
|
||||
(),
|
||||
|state, user, _req, _| user_core::verify_token(state, user),
|
||||
&auth::ReconJWT,
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
|
||||
@ -11,8 +11,6 @@ pub mod jwt;
|
||||
pub mod kafka;
|
||||
pub mod logger;
|
||||
pub mod pm_auth;
|
||||
#[cfg(feature = "recon")]
|
||||
pub mod recon;
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod openidconnect;
|
||||
|
||||
@ -24,8 +24,6 @@ use self::detached::{ExtractedPayload, GetAuthType};
|
||||
use super::authorization::{self, permissions::Permission};
|
||||
#[cfg(feature = "olap")]
|
||||
use super::jwt;
|
||||
#[cfg(feature = "recon")]
|
||||
use super::recon::ReconToken;
|
||||
#[cfg(feature = "olap")]
|
||||
use crate::configs::Settings;
|
||||
#[cfg(feature = "olap")]
|
||||
@ -34,8 +32,6 @@ use crate::consts;
|
||||
use crate::core::errors::UserResult;
|
||||
#[cfg(feature = "partial-auth")]
|
||||
use crate::core::metrics;
|
||||
#[cfg(feature = "recon")]
|
||||
use crate::routes::SessionState;
|
||||
use crate::{
|
||||
core::{
|
||||
api_keys,
|
||||
@ -44,7 +40,7 @@ use crate::{
|
||||
headers,
|
||||
routes::app::SessionStateInfo,
|
||||
services::api,
|
||||
types::domain,
|
||||
types::{domain, storage},
|
||||
utils::OptionExt,
|
||||
};
|
||||
|
||||
@ -69,6 +65,14 @@ pub struct AuthenticationDataWithMultipleProfiles {
|
||||
pub profile_id_list: Option<Vec<id_type::ProfileId>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AuthenticationDataWithUser {
|
||||
pub merchant_account: domain::MerchantAccount,
|
||||
pub key_store: domain::MerchantKeyStore,
|
||||
pub user: storage::User,
|
||||
pub profile_id: Option<id_type::ProfileId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
#[serde(
|
||||
tag = "api_auth_type",
|
||||
@ -1980,12 +1984,13 @@ where
|
||||
default_auth
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(feature = "recon")]
|
||||
pub struct ReconAdmin;
|
||||
pub struct UserFromTokenWithAuthData(pub UserFromToken, pub AuthenticationDataWithUser);
|
||||
|
||||
#[async_trait]
|
||||
#[cfg(feature = "recon")]
|
||||
impl<A> AuthenticateAndFetch<(), A> for ReconAdmin
|
||||
#[async_trait]
|
||||
impl<A> AuthenticateAndFetch<UserFromTokenWithAuthData, A> for JWTAuth
|
||||
where
|
||||
A: SessionStateInfo + Sync,
|
||||
{
|
||||
@ -1993,49 +1998,68 @@ where
|
||||
&self,
|
||||
request_headers: &HeaderMap,
|
||||
state: &A,
|
||||
) -> RouterResult<((), AuthenticationType)> {
|
||||
let request_admin_api_key =
|
||||
get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
||||
let conf = state.conf();
|
||||
|
||||
let admin_api_key = conf.secrets.get_inner().recon_admin_api_key.peek();
|
||||
|
||||
if request_admin_api_key != admin_api_key {
|
||||
Err(report!(errors::ApiErrorResponse::Unauthorized)
|
||||
.attach_printable("Recon Admin Authentication Failure"))?;
|
||||
) -> RouterResult<(UserFromTokenWithAuthData, 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 role_info = authorization::get_role_info(state, &payload).await?;
|
||||
authorization::check_permission(&self.permission, &role_info)?;
|
||||
authorization::check_entity(self.minimum_entity_level, &role_info)?;
|
||||
|
||||
Ok(((), AuthenticationType::NoAuth))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "recon")]
|
||||
pub struct ReconJWT;
|
||||
#[cfg(feature = "recon")]
|
||||
pub struct ReconUser {
|
||||
pub user_id: String,
|
||||
}
|
||||
#[cfg(feature = "recon")]
|
||||
impl AuthInfo for ReconUser {
|
||||
fn get_merchant_id(&self) -> Option<&id_type::MerchantId> {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "olap", feature = "recon"))]
|
||||
#[async_trait]
|
||||
impl AuthenticateAndFetch<ReconUser, SessionState> for ReconJWT {
|
||||
async fn authenticate_and_fetch(
|
||||
&self,
|
||||
request_headers: &HeaderMap,
|
||||
state: &SessionState,
|
||||
) -> RouterResult<(ReconUser, AuthenticationType)> {
|
||||
let payload = parse_jwt_payload::<SessionState, ReconToken>(request_headers, state).await?;
|
||||
|
||||
Ok((
|
||||
ReconUser {
|
||||
user_id: payload.user_id,
|
||||
},
|
||||
AuthenticationType::NoAuth,
|
||||
))
|
||||
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 user_id = payload.user_id;
|
||||
|
||||
let user = state
|
||||
.session_state()
|
||||
.global_store
|
||||
.find_user_by_id(&user_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
|
||||
.attach_printable("Failed to fetch user for the user id")?;
|
||||
|
||||
let auth = AuthenticationDataWithUser {
|
||||
merchant_account: merchant,
|
||||
key_store,
|
||||
profile_id: payload.profile_id.clone(),
|
||||
user,
|
||||
};
|
||||
|
||||
let auth_type = AuthenticationType::MerchantJwt {
|
||||
merchant_id: auth.merchant_account.get_id().clone(),
|
||||
user_id: Some(user_id.clone()),
|
||||
};
|
||||
|
||||
let user = UserFromToken {
|
||||
user_id,
|
||||
merchant_id: payload.merchant_id.clone(),
|
||||
org_id: payload.org_id,
|
||||
role_id: payload.role_id,
|
||||
profile_id: payload.profile_id,
|
||||
};
|
||||
|
||||
Ok((UserFromTokenWithAuthData(user, auth), auth_type))
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ pub enum PermissionModule {
|
||||
SurchargeDecisionManager,
|
||||
AccountCreate,
|
||||
Payouts,
|
||||
Recon,
|
||||
}
|
||||
|
||||
impl PermissionModule {
|
||||
@ -59,7 +60,8 @@ impl PermissionModule {
|
||||
Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant",
|
||||
Self::SurchargeDecisionManager =>"View and configure surcharge decision rules configured for a merchant",
|
||||
Self::AccountCreate => "Create new account within your organization",
|
||||
Self::Payouts => "Everything related to payouts - like creating and viewing payout related information are within this module"
|
||||
Self::Payouts => "Everything related to payouts - like creating and viewing payout related information are within this module",
|
||||
Self::Recon => "Everything related to recon - raise requests for activating recon and generate recon auth tokens",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,6 +180,11 @@ impl ModuleInfo {
|
||||
Permission::PayoutWrite,
|
||||
]),
|
||||
},
|
||||
PermissionModule::Recon => Self {
|
||||
module: module_name,
|
||||
description,
|
||||
permissions: get_permission_info_from_permissions(&[Permission::ReconAdmin]),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -215,6 +222,7 @@ fn get_group_description(group: PermissionGroup) -> &'static str {
|
||||
PermissionGroup::MerchantDetailsView => "View Merchant Details",
|
||||
PermissionGroup::MerchantDetailsManage => "Create, modify and delete Merchant Details like api keys, webhooks, etc",
|
||||
PermissionGroup::OrganizationManage => "Manage organization level tasks like create new Merchant accounts, Organization level roles, etc",
|
||||
PermissionGroup::ReconOps => "View and manage reconciliation reports",
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +241,7 @@ pub fn get_parent_name(group: PermissionGroup) -> ParentGroup {
|
||||
ParentGroup::Merchant
|
||||
}
|
||||
PermissionGroup::OrganizationManage => ParentGroup::Organization,
|
||||
PermissionGroup::ReconOps => ParentGroup::Recon,
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,5 +254,6 @@ pub fn get_parent_group_description(group: ParentGroup) -> &'static str {
|
||||
ParentGroup::Users => "Manage and invite Users to the Team",
|
||||
ParentGroup::Merchant => "Create, modify and delete Merchant Details like api keys, webhooks, etc",
|
||||
ParentGroup::Organization =>"Manage organization level tasks like create new Merchant accounts, Organization level roles, etc",
|
||||
ParentGroup::Recon => "View and manage reconciliation reports",
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ pub fn get_permissions_vec(permission_group: &PermissionGroup) -> &[Permission]
|
||||
PermissionGroup::MerchantDetailsView => &MERCHANT_DETAILS_VIEW,
|
||||
PermissionGroup::MerchantDetailsManage => &MERCHANT_DETAILS_MANAGE,
|
||||
PermissionGroup::OrganizationManage => &ORGANIZATION_MANAGE,
|
||||
PermissionGroup::ReconOps => &RECON,
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,3 +93,5 @@ pub static ORGANIZATION_MANAGE: [Permission; 2] = [
|
||||
Permission::MerchantAccountCreate,
|
||||
Permission::MerchantAccountRead,
|
||||
];
|
||||
|
||||
pub static RECON: [Permission; 1] = [Permission::ReconAdmin];
|
||||
|
||||
@ -35,6 +35,7 @@ pub enum Permission {
|
||||
PayoutRead,
|
||||
PayoutWrite,
|
||||
GenerateReport,
|
||||
ReconAdmin,
|
||||
}
|
||||
|
||||
impl Permission {
|
||||
@ -77,6 +78,7 @@ impl Permission {
|
||||
Self::PayoutRead => "View all payouts",
|
||||
Self::PayoutWrite => "Create payout, download payout data",
|
||||
Self::GenerateReport => "Generate reports for payments, refunds and disputes",
|
||||
Self::ReconAdmin => "View and manage reconciliation reports",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
|
||||
PermissionGroup::MerchantDetailsView,
|
||||
PermissionGroup::MerchantDetailsManage,
|
||||
PermissionGroup::OrganizationManage,
|
||||
PermissionGroup::ReconOps,
|
||||
],
|
||||
role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(),
|
||||
role_name: "internal_admin".to_string(),
|
||||
@ -73,6 +74,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
|
||||
PermissionGroup::MerchantDetailsView,
|
||||
PermissionGroup::MerchantDetailsManage,
|
||||
PermissionGroup::OrganizationManage,
|
||||
PermissionGroup::ReconOps,
|
||||
],
|
||||
role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
|
||||
role_name: "organization_admin".to_string(),
|
||||
@ -101,6 +103,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
|
||||
PermissionGroup::UsersManage,
|
||||
PermissionGroup::MerchantDetailsView,
|
||||
PermissionGroup::MerchantDetailsManage,
|
||||
PermissionGroup::ReconOps,
|
||||
],
|
||||
role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
|
||||
role_name: "admin".to_string(),
|
||||
|
||||
@ -6,7 +6,7 @@ use common_utils::{
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use external_services::email::{EmailContents, EmailData, EmailError};
|
||||
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||
use masking::{ExposeInterface, Secret};
|
||||
|
||||
use crate::{configs, consts, routes::SessionState};
|
||||
#[cfg(feature = "olap")]
|
||||
@ -454,6 +454,7 @@ pub struct ProFeatureRequest {
|
||||
pub feature_name: String,
|
||||
pub merchant_id: common_utils::id_type::MerchantId,
|
||||
pub user_name: domain::UserName,
|
||||
pub user_email: domain::UserEmail,
|
||||
pub settings: std::sync::Arc<configs::Settings>,
|
||||
pub subject: String,
|
||||
}
|
||||
@ -467,7 +468,7 @@ impl EmailData for ProFeatureRequest {
|
||||
user_name: self.user_name.clone().get_secret().expose(),
|
||||
feature_name: self.feature_name.clone(),
|
||||
merchant_id: self.merchant_id.clone(),
|
||||
user_email: recipient.peek().to_string(),
|
||||
user_email: self.user_email.clone().get_secret().expose(),
|
||||
});
|
||||
|
||||
Ok(EmailContents {
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
use error_stack::ResultExt;
|
||||
use masking::Secret;
|
||||
|
||||
use super::jwt;
|
||||
use crate::{
|
||||
configs::Settings,
|
||||
consts,
|
||||
core::{self, errors::RouterResult},
|
||||
};
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct ReconToken {
|
||||
pub user_id: String,
|
||||
pub exp: u64,
|
||||
}
|
||||
|
||||
impl ReconToken {
|
||||
pub async fn new_token(user_id: String, settings: &Settings) -> RouterResult<Secret<String>> {
|
||||
let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS);
|
||||
let exp = jwt::generate_exp(exp_duration)
|
||||
.change_context(core::errors::ApiErrorResponse::InternalServerError)?
|
||||
.as_secs();
|
||||
let token_payload = Self { user_id, exp };
|
||||
let token = jwt::generate_jwt(&token_payload, settings)
|
||||
.await
|
||||
.change_context(core::errors::ApiErrorResponse::InternalServerError)?;
|
||||
Ok(Secret::new(token))
|
||||
}
|
||||
}
|
||||
@ -1045,6 +1045,7 @@ impl From<info::PermissionModule> for user_role_api::PermissionModule {
|
||||
info::PermissionModule::SurchargeDecisionManager => Self::SurchargeDecisionManager,
|
||||
info::PermissionModule::AccountCreate => Self::AccountCreate,
|
||||
info::PermissionModule::Payouts => Self::Payouts,
|
||||
info::PermissionModule::Recon => Self::Recon,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +54,7 @@ impl From<Permission> for user_role_api::Permission {
|
||||
Permission::PayoutRead => Self::PayoutRead,
|
||||
Permission::PayoutWrite => Self::PayoutWrite,
|
||||
Permission::GenerateReport => Self::GenerateReport,
|
||||
Permission::ReconAdmin => Self::ReconAdmin,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,3 +364,6 @@ global_tenant = { schema = "public", redis_key_prefix = "" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[recipient_emails]
|
||||
recon = "recon@example.com"
|
||||
|
||||
Reference in New Issue
Block a user