Bug: [FEATURE] add permissions for operations in recon module



### Feature Description

Reconciliation module in HyperSwitch provides various operations. Every operation needs to permitted for the end user to use it.

As of today, recon has a single permission - which gives access to the entire recon module, this is not granular. Recon module's permission suite needs to be extended to be able to assign granular access for different operations.

### Possible Implementation

Add permissions for below operations

- Upload files - RW
- Run recon - RW
- View and update recon configs - RW
- View and add file processing - RW
- View Reports - R
- View Analytics - R

These are to be included in JWT in the response of `/recon/verify_token`

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index c00afac6462..4af3f855d77 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -446,3 +446,20 @@ pub enum StripeChargeType {
 pub fn convert_frm_connector(connector_name: &str) -> Option<FrmConnectors> {
     FrmConnectors::from_str(connector_name).ok()
 }
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)]
+pub enum ReconPermissionScope {
+    #[serde(rename = "R")]
+    Read = 0,
+    #[serde(rename = "RW")]
+    Write = 1,
+}
+
+impl From<PermissionScope> for ReconPermissionScope {
+    fn from(scope: PermissionScope) -> Self {
+        match scope {
+            PermissionScope::Read => Self::Read,
+            PermissionScope::Write => Self::Write,
+        }
+    }
+}
diff --git a/crates/api_models/src/events/recon.rs b/crates/api_models/src/events/recon.rs
index aed648f4c86..596b0541282 100644
--- a/crates/api_models/src/events/recon.rs
+++ b/crates/api_models/src/events/recon.rs
@@ -1,6 +1,9 @@
 use common_utils::events::{ApiEventMetric, ApiEventsType};
+use masking::PeekInterface;
 
-use crate::recon::{ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest};
+use crate::recon::{
+    ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest, VerifyTokenResponse,
+};
 
 impl ApiEventMetric for ReconUpdateMerchantRequest {
     fn get_api_event_type(&self) -> Option<ApiEventsType> {
@@ -19,3 +22,11 @@ impl ApiEventMetric for ReconStatusResponse {
         Some(ApiEventsType::Recon)
     }
 }
+
+impl ApiEventMetric for VerifyTokenResponse {
+    fn get_api_event_type(&self) -> Option<ApiEventsType> {
+        Some(ApiEventsType::User {
+            user_id: self.user_email.peek().to_string(),
+        })
+    }
+}
diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs
index 4dc2a1a301a..baac14e8afc 100644
--- a/crates/api_models/src/events/user.rs
+++ b/crates/api_models/src/events/user.rs
@@ -1,11 +1,7 @@
 use common_utils::events::{ApiEventMetric, ApiEventsType};
-#[cfg(feature = "recon")]
-use masking::PeekInterface;
 
 #[cfg(feature = "dummy_connector")]
 use crate::user::sample_data::SampleDataRequest;
-#[cfg(feature = "recon")]
-use crate::user::VerifyTokenResponse;
 use crate::user::{
     dashboard_metadata::{
         GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
@@ -23,15 +19,6 @@ use crate::user::{
     VerifyTotpRequest,
 };
 
-#[cfg(feature = "recon")]
-impl ApiEventMetric for VerifyTokenResponse {
-    fn get_api_event_type(&self) -> Option<ApiEventsType> {
-        Some(ApiEventsType::User {
-            user_id: self.user_email.peek().to_string(),
-        })
-    }
-}
-
 common_utils::impl_api_event_type!(
     Miscellaneous,
     (
diff --git a/crates/api_models/src/recon.rs b/crates/api_models/src/recon.rs
index afee0fb5626..f73bcc5ae1f 100644
--- a/crates/api_models/src/recon.rs
+++ b/crates/api_models/src/recon.rs
@@ -1,4 +1,4 @@
-use common_utils::pii;
+use common_utils::{id_type, pii};
 use masking::Secret;
 
 use crate::enums;
@@ -18,3 +18,11 @@ pub struct ReconTokenResponse {
 pub struct ReconStatusResponse {
     pub recon_status: enums::ReconStatus,
 }
+
+#[derive(serde::Serialize, Debug)]
+pub struct VerifyTokenResponse {
+    pub merchant_id: id_type::MerchantId,
+    pub user_email: pii::Email,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acl: Option<String>,
+}
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 089426c68ba..9c70ea895ad 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -167,13 +167,6 @@ pub struct SendVerifyEmailRequest {
     pub email: pii::Email,
 }
 
-#[cfg(feature = "recon")]
-#[derive(serde::Serialize, Debug)]
-pub struct VerifyTokenResponse {
-    pub merchant_id: id_type::MerchantId,
-    pub user_email: pii::Email,
-}
-
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
 pub struct UpdateUserAccountDetailsRequest {
     pub name: Option<Secret<String>>,
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 781b5e3710a..cb4281ee45c 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -2819,9 +2819,15 @@ pub enum PermissionGroup {
     MerchantDetailsManage,
     // TODO: To be deprecated, make sure DB is migrated before removing
     OrganizationManage,
-    ReconOps,
     AccountView,
     AccountManage,
+    ReconReportsView,
+    ReconReportsManage,
+    ReconOpsView,
+    // Alias is added for backward compatibility with database
+    // TODO: Remove alias post migration
+    #[serde(alias = "recon_ops")]
+    ReconOpsManage,
 }
 
 #[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash, strum::EnumIter)]
@@ -2831,7 +2837,8 @@ pub enum ParentGroup {
     Workflows,
     Analytics,
     Users,
-    Recon,
+    ReconOps,
+    ReconReports,
     Account,
 }
 
@@ -2854,7 +2861,13 @@ pub enum Resource {
     WebhookEvent,
     Payout,
     Report,
-    Recon,
+    ReconToken,
+    ReconFiles,
+    ReconAndSettlementAnalytics,
+    ReconUpload,
+    ReconReports,
+    RunRecon,
+    ReconConfig,
 }
 
 #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)]
diff --git a/crates/router/src/core/recon.rs b/crates/router/src/core/recon.rs
index 521c978e3c2..13cf4c488ec 100644
--- a/crates/router/src/core/recon.rs
+++ b/crates/router/src/core/recon.rs
@@ -1,100 +1,113 @@
 use api_models::recon as recon_api;
+#[cfg(feature = "email")]
 use common_utils::ext_traits::AsyncExt;
 use error_stack::ResultExt;
+#[cfg(feature = "email")]
 use masking::{ExposeInterface, PeekInterface, Secret};
 
+#[cfg(feature = "email")]
+use crate::{consts, services::email::types as email_types, types::domain};
 use crate::{
-    consts,
-    core::errors::{self, RouterResponse, UserErrors},
-    services::{api as service_api, authentication, email::types as email_types},
+    core::errors::{self, RouterResponse, UserErrors, UserResponse},
+    services::{api as service_api, authentication},
     types::{
         api::{self as api_types, enums},
-        domain, storage,
+        storage,
         transformers::ForeignTryFrom,
     },
     SessionState,
 };
 
+#[allow(unused_variables)]
 pub async fn send_recon_request(
     state: SessionState,
     auth_data: authentication::AuthenticationDataWithUser,
 ) -> RouterResponse<recon_api::ReconStatusResponse> {
-    let user_in_db = &auth_data.user;
-    let merchant_id = auth_data.merchant_account.get_id().clone();
-
-    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())
+    #[cfg(not(feature = "email"))]
+    return Ok(service_api::ApplicationResponse::Json(
+        recon_api::ReconStatusResponse {
+            recon_status: enums::ReconStatus::NotRequested,
+        },
+    ));
+
+    #[cfg(feature = "email")]
+    {
+        let user_in_db = &auth_data.user;
+        let merchant_id = auth_data.merchant_account.get_id().clone();
+
+        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.email.recon_recipient_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.email.recon_recipient_email.clone(),
-        )
-        .change_context(errors::ApiErrorResponse::InternalServerError)
-        .attach_printable("Failed to convert recipient's email to UserEmail")?,
-        settings: state.conf.clone(),
-        subject: format!(
-            "{} {}",
-            consts::EMAIL_SUBJECT_DASHBOARD_FEATURE_REQUEST,
-            user_email.expose().peek()
-        ),
-    };
+            subject: format!(
+                "{} {}",
+                consts::EMAIL_SUBJECT_DASHBOARD_FEATURE_REQUEST,
+                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 updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate {
+                    recon_status: enums::ReconStatus::Requested,
+                };
+                let db = &*state.store;
+                let key_manager_state = &(&state).into();
 
-    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 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_data.merchant_account,
-                    updated_merchant_account,
-                    &auth_data.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
+                let response = db
+                    .update_merchant(
+                        key_manager_state,
+                        auth_data.merchant_account,
+                        updated_merchant_account,
+                        &auth_data.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,
+    user_with_role: authentication::UserFromTokenWithRoleInfo,
 ) -> RouterResponse<recon_api::ReconTokenResponse> {
-    let token = authentication::AuthToken::new_token(
+    let user = user_with_role.user;
+    let token = authentication::ReconToken::new_token(
         user.user_id.clone(),
         user.merchant_id.clone(),
-        user.role_id.clone(),
         &state.conf,
         user.org_id.clone(),
         user.profile_id.clone(),
         user.tenant_id,
+        user_with_role.role_info,
     )
     .await
     .change_context(errors::ApiErrorResponse::InternalServerError)
@@ -138,29 +151,37 @@ pub async fn recon_merchant_account_update(
             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: consts::EMAIL_SUBJECT_APPROVAL_RECON_REQUEST,
-    };
-
-    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();
+    #[cfg(feature = "email")]
+    {
+        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")?,
+            subject: consts::EMAIL_SUBJECT_APPROVAL_RECON_REQUEST,
+        };
+        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
+                .inspect_err(|err| {
+                    router_env::logger::error!(
+                        "Failed to compose and send email notifying them of recon activation: {}",
+                        err
+                    )
+                })
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("Failed to compose and send email for ReconActivation");
+        }
     }
 
     Ok(service_api::ApplicationResponse::Json(
@@ -170,3 +191,34 @@ pub async fn recon_merchant_account_update(
             })?,
     ))
 }
+
+pub async fn verify_recon_token(
+    state: SessionState,
+    user_with_role: authentication::UserFromTokenWithRoleInfo,
+) -> UserResponse<recon_api::VerifyTokenResponse> {
+    let user = user_with_role.user;
+    let user_in_db = user
+        .get_user_from_db(&state)
+        .await
+        .attach_printable_lazy(|| {
+            format!(
+                "Failed to fetch the user from DB for user_id - {}",
+                user.user_id
+            )
+        })?;
+
+    let acl = user_with_role.role_info.get_recon_acl();
+    let optional_acl_str = serde_json::to_string(&acl)
+        .inspect_err(|err| router_env::logger::error!("Failed to serialize acl to string: {}", err))
+        .change_context(UserErrors::InternalServerError)
+        .attach_printable("Failed to serialize acl to string. Using empty ACL")
+        .ok();
+
+    Ok(service_api::ApplicationResponse::Json(
+        recon_api::VerifyTokenResponse {
+            merchant_id: user.merchant_id.to_owned(),
+            user_email: user_in_db.0.email,
+            acl: optional_acl_str,
+        },
+    ))
+}
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 039e891e422..7ca4a127e85 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1593,27 +1593,6 @@ pub async fn send_verification_mail(
     Ok(ApplicationResponse::StatusOk)
 }
 
-#[cfg(feature = "recon")]
-pub async fn verify_token(
-    state: SessionState,
-    user: auth::UserFromToken,
-) -> UserResponse<user_api::VerifyTokenResponse> {
-    let user_in_db = user
-        .get_user_from_db(&state)
-        .await
-        .attach_printable_lazy(|| {
-            format!(
-                "Failed to fetch the user from DB for user_id - {}",
-                user.user_id
-            )
-        })?;
-
-    Ok(ApplicationResponse::Json(user_api::VerifyTokenResponse {
-        merchant_id: user.merchant_id.to_owned(),
-        user_email: user_in_db.0.email,
-    }))
-}
-
 pub async fn update_user_details(
     state: SessionState,
     user_token: auth::UserFromToken,
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index bb8d0d4f2bd..3d1474ee81a 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1216,7 +1216,10 @@ impl Recon {
             .service(
                 web::resource("/request").route(web::post().to(recon_routes::request_for_recon)),
             )
-            .service(web::resource("/verify_token").route(web::get().to(user::verify_recon_token)))
+            .service(
+                web::resource("/verify_token")
+                    .route(web::get().to(recon_routes::verify_recon_token)),
+            )
     }
 }
 
diff --git a/crates/router/src/routes/recon.rs b/crates/router/src/routes/recon.rs
index cdc2ae758e9..cfd076c7100 100644
--- a/crates/router/src/routes/recon.rs
+++ b/crates/router/src/routes/recon.rs
@@ -38,7 +38,7 @@ pub async fn request_for_recon(state: web::Data<AppState>, http_req: HttpRequest
         (),
         |state, user, _, _| recon::send_recon_request(state, user),
         &authentication::JWTAuth {
-            permission: Permission::MerchantReconWrite,
+            permission: Permission::MerchantAccountWrite,
         },
         api_locking::LockAction::NotApplicable,
     ))
@@ -54,7 +54,24 @@ pub async fn get_recon_token(state: web::Data<AppState>, req: HttpRequest) -> Ht
         (),
         |state, user, _, _| recon::generate_recon_token(state, user),
         &authentication::JWTAuth {
-            permission: Permission::MerchantReconWrite,
+            permission: Permission::MerchantReconTokenRead,
+        },
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+#[cfg(feature = "recon")]
+pub async fn verify_recon_token(state: web::Data<AppState>, http_req: HttpRequest) -> HttpResponse {
+    let flow = Flow::ReconVerifyToken;
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &http_req,
+        (),
+        |state, user, _req, _| recon::verify_recon_token(state, user),
+        &authentication::JWTAuth {
+            permission: Permission::MerchantReconTokenRead,
         },
         api_locking::LockAction::NotApplicable,
     ))
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 8fc0dad452a..af55f7f3055 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -487,23 +487,6 @@ pub async fn verify_email_request(
     .await
 }
 
-#[cfg(feature = "recon")]
-pub async fn verify_recon_token(state: web::Data<AppState>, http_req: HttpRequest) -> HttpResponse {
-    let flow = Flow::ReconVerifyToken;
-    Box::pin(api::server_wrap(
-        flow,
-        state.clone(),
-        &http_req,
-        (),
-        |state, user, _req, _| user_core::verify_token(state, user),
-        &auth::JWTAuth {
-            permission: Permission::MerchantReconWrite,
-        },
-        api_locking::LockAction::NotApplicable,
-    ))
-    .await
-}
-
 pub async fn update_user_account_details(
     state: web::Data<AppState>,
     req: HttpRequest,
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index 1967eafd180..58253684a27 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -90,6 +90,12 @@ pub struct AuthenticationDataWithUser {
     pub profile_id: id_type::ProfileId,
 }
 
+#[derive(Clone)]
+pub struct UserFromTokenWithRoleInfo {
+    pub user: UserFromToken,
+    pub role_info: authorization::roles::RoleInfo,
+}
+
 #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
 #[serde(
     tag = "api_auth_type",
@@ -3228,3 +3234,91 @@ where
         Ok((auth, auth_type))
     }
 }
+
+#[cfg(feature = "recon")]
+#[async_trait]
+impl<A> AuthenticateAndFetch<UserFromTokenWithRoleInfo, A> for JWTAuth
+where
+    A: SessionStateInfo + Sync,
+{
+    async fn authenticate_and_fetch(
+        &self,
+        request_headers: &HeaderMap,
+        state: &A,
+    ) -> RouterResult<(UserFromTokenWithRoleInfo, 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());
+        }
+        authorization::check_tenant(
+            payload.tenant_id.clone(),
+            &state.session_state().tenant.tenant_id,
+        )?;
+        let role_info = authorization::get_role_info(state, &payload).await?;
+        authorization::check_permission(&self.permission, &role_info)?;
+
+        let user = UserFromToken {
+            user_id: payload.user_id.clone(),
+            merchant_id: payload.merchant_id.clone(),
+            org_id: payload.org_id,
+            role_id: payload.role_id,
+            profile_id: payload.profile_id,
+            tenant_id: payload.tenant_id,
+        };
+
+        Ok((
+            UserFromTokenWithRoleInfo { user, role_info },
+            AuthenticationType::MerchantJwt {
+                merchant_id: payload.merchant_id,
+                user_id: Some(payload.user_id),
+            },
+        ))
+    }
+}
+
+#[cfg(feature = "recon")]
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct ReconToken {
+    pub user_id: String,
+    pub merchant_id: id_type::MerchantId,
+    pub role_id: String,
+    pub exp: u64,
+    pub org_id: id_type::OrganizationId,
+    pub profile_id: id_type::ProfileId,
+    pub tenant_id: Option<id_type::TenantId>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acl: Option<String>,
+}
+
+#[cfg(all(feature = "olap", feature = "recon"))]
+impl ReconToken {
+    pub async fn new_token(
+        user_id: String,
+        merchant_id: id_type::MerchantId,
+        settings: &Settings,
+        org_id: id_type::OrganizationId,
+        profile_id: id_type::ProfileId,
+        tenant_id: Option<id_type::TenantId>,
+        role_info: authorization::roles::RoleInfo,
+    ) -> UserResult<String> {
+        let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS);
+        let exp = jwt::generate_exp(exp_duration)?.as_secs();
+        let acl = role_info.get_recon_acl();
+        let optional_acl_str = serde_json::to_string(&acl)
+            .inspect_err(|err| logger::error!("Failed to serialize acl to string: {}", err))
+            .change_context(errors::UserErrors::InternalServerError)
+            .attach_printable("Failed to serialize acl to string. Using empty ACL")
+            .ok();
+        let token_payload = Self {
+            user_id,
+            merchant_id,
+            role_id: role_info.get_role_id().to_string(),
+            exp,
+            org_id,
+            profile_id,
+            tenant_id,
+            acl: optional_acl_str,
+        };
+        jwt::generate_jwt(&token_payload, settings).await
+    }
+}
diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs
index bd987e2fe9a..2d808a4377a 100644
--- a/crates/router/src/services/authorization/info.rs
+++ b/crates/router/src/services/authorization/info.rs
@@ -40,7 +40,10 @@ fn get_group_description(group: PermissionGroup) -> &'static str {
         PermissionGroup::MerchantDetailsView | PermissionGroup::AccountView => "View Merchant Details",
         PermissionGroup::MerchantDetailsManage | PermissionGroup::AccountManage => "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",
+        PermissionGroup::ReconReportsView => "View and access reconciliation reports and analytics",
+        PermissionGroup::ReconReportsManage => "Manage reconciliation reports",
+        PermissionGroup::ReconOpsView => "View and access reconciliation operations",
+        PermissionGroup::ReconOpsManage => "Manage reconciliation operations",
     }
 }
 
@@ -52,6 +55,7 @@ pub fn get_parent_group_description(group: ParentGroup) -> &'static str {
         ParentGroup::Analytics => "View Analytics",
         ParentGroup::Users =>  "Manage and invite Users to the Team",
         ParentGroup::Account => "Create, modify and delete Merchant Details like api keys, webhooks, etc",
-        ParentGroup::Recon => "View and manage reconciliation reports",
+        ParentGroup::ReconOps => "View, manage reconciliation operations like upload and process files, run reconciliation etc",
+        ParentGroup::ReconReports => "View, manage reconciliation reports and analytics",
     }
 }
diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs
index 57c385565a8..7ca8442c1ce 100644
--- a/crates/router/src/services/authorization/permission_groups.rs
+++ b/crates/router/src/services/authorization/permission_groups.rs
@@ -21,7 +21,9 @@ impl PermissionGroupExt for PermissionGroup {
             | Self::AnalyticsView
             | Self::UsersView
             | Self::MerchantDetailsView
-            | Self::AccountView => PermissionScope::Read,
+            | Self::AccountView
+            | Self::ReconOpsView
+            | Self::ReconReportsView => PermissionScope::Read,
 
             Self::OperationsManage
             | Self::ConnectorsManage
@@ -29,8 +31,9 @@ impl PermissionGroupExt for PermissionGroup {
             | Self::UsersManage
             | Self::MerchantDetailsManage
             | Self::OrganizationManage
-            | Self::ReconOps
-            | Self::AccountManage => PermissionScope::Write,
+            | Self::AccountManage
+            | Self::ReconOpsManage
+            | Self::ReconReportsManage => PermissionScope::Write,
         }
     }
 
@@ -41,12 +44,13 @@ impl PermissionGroupExt for PermissionGroup {
             Self::WorkflowsView | Self::WorkflowsManage => ParentGroup::Workflows,
             Self::AnalyticsView => ParentGroup::Analytics,
             Self::UsersView | Self::UsersManage => ParentGroup::Users,
-            Self::ReconOps => ParentGroup::Recon,
             Self::MerchantDetailsView
             | Self::OrganizationManage
             | Self::MerchantDetailsManage
             | Self::AccountView
             | Self::AccountManage => ParentGroup::Account,
+            Self::ReconOpsView | Self::ReconOpsManage => ParentGroup::ReconOps,
+            Self::ReconReportsView | Self::ReconReportsManage => ParentGroup::ReconReports,
         }
     }
 
@@ -76,7 +80,11 @@ impl PermissionGroupExt for PermissionGroup {
                 vec![Self::UsersView, Self::UsersManage]
             }
 
-            Self::ReconOps => vec![Self::ReconOps],
+            Self::ReconOpsView => vec![Self::ReconOpsView],
+            Self::ReconOpsManage => vec![Self::ReconOpsView, Self::ReconOpsManage],
+
+            Self::ReconReportsView => vec![Self::ReconReportsView],
+            Self::ReconReportsManage => vec![Self::ReconReportsView, Self::ReconReportsManage],
 
             Self::MerchantDetailsView => vec![Self::MerchantDetailsView],
             Self::MerchantDetailsManage => {
@@ -108,7 +116,8 @@ impl ParentGroupExt for ParentGroup {
             Self::Analytics => ANALYTICS.to_vec(),
             Self::Users => USERS.to_vec(),
             Self::Account => ACCOUNT.to_vec(),
-            Self::Recon => RECON.to_vec(),
+            Self::ReconOps => RECON_OPS.to_vec(),
+            Self::ReconReports => RECON_REPORTS.to_vec(),
         }
     }
 
@@ -167,4 +176,18 @@ pub static USERS: [Resource; 2] = [Resource::User, Resource::Account];
 
 pub static ACCOUNT: [Resource; 3] = [Resource::Account, Resource::ApiKey, Resource::WebhookEvent];
 
-pub static RECON: [Resource; 1] = [Resource::Recon];
+pub static RECON_OPS: [Resource; 7] = [
+    Resource::ReconToken,
+    Resource::ReconFiles,
+    Resource::ReconUpload,
+    Resource::RunRecon,
+    Resource::ReconConfig,
+    Resource::ReconAndSettlementAnalytics,
+    Resource::ReconReports,
+];
+
+pub static RECON_REPORTS: [Resource; 3] = [
+    Resource::ReconToken,
+    Resource::ReconAndSettlementAnalytics,
+    Resource::ReconReports,
+];
diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs
index 6e472d55623..6f612007425 100644
--- a/crates/router/src/services/authorization/permissions.rs
+++ b/crates/router/src/services/authorization/permissions.rs
@@ -67,8 +67,32 @@ generate_permissions! {
             scopes: [Read, Write],
             entities: [Merchant]
         },
-        Recon: {
-            scopes: [Write],
+        ReconToken: {
+            scopes: [Read],
+            entities: [Merchant]
+        },
+        ReconFiles: {
+            scopes: [Read, Write],
+            entities: [Merchant]
+        },
+        ReconAndSettlementAnalytics: {
+            scopes: [Read],
+            entities: [Merchant]
+        },
+        ReconUpload: {
+            scopes: [Read, Write],
+            entities: [Merchant]
+        },
+        ReconReports: {
+            scopes: [Read, Write],
+            entities: [Merchant]
+        },
+        RunRecon: {
+            scopes: [Read, Write],
+            entities: [Merchant]
+        },
+        ReconConfig: {
+            scopes: [Read, Write],
             entities: [Merchant]
         },
     ]
@@ -91,7 +115,13 @@ pub fn get_resource_name(resource: &Resource, entity_type: &EntityType) -> &'sta
         (Resource::Report, _) => "Operation Reports",
         (Resource::User, _) => "Users",
         (Resource::WebhookEvent, _) => "Webhook Events",
-        (Resource::Recon, _) => "Reconciliation Reports",
+        (Resource::ReconUpload, _) => "Reconciliation File Upload",
+        (Resource::RunRecon, _) => "Run Reconciliation Process",
+        (Resource::ReconConfig, _) => "Reconciliation Configurations",
+        (Resource::ReconToken, _) => "Generate & Verify Reconciliation Token",
+        (Resource::ReconFiles, _) => "Reconciliation Process Manager",
+        (Resource::ReconReports, _) => "Reconciliation Reports",
+        (Resource::ReconAndSettlementAnalytics, _) => "Reconciliation Analytics",
         (Resource::Account, EntityType::Profile) => "Business Profile Account",
         (Resource::Account, EntityType::Merchant) => "Merchant Account",
         (Resource::Account, EntityType::Organization) => "Organization Account",
diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs
index bf66eb92466..f6c4f4b9ef2 100644
--- a/crates/router/src/services/authorization/roles.rs
+++ b/crates/router/src/services/authorization/roles.rs
@@ -1,8 +1,14 @@
+#[cfg(feature = "recon")]
+use std::collections::HashMap;
 use std::collections::HashSet;
 
+#[cfg(feature = "recon")]
+use api_models::enums::ReconPermissionScope;
 use common_enums::{EntityType, PermissionGroup, Resource, RoleScope};
 use common_utils::{errors::CustomResult, id_type};
 
+#[cfg(feature = "recon")]
+use super::permission_groups::{RECON_OPS, RECON_REPORTS};
 use super::{permission_groups::PermissionGroupExt, permissions::Permission};
 use crate::{core::errors, routes::SessionState};
 
@@ -78,6 +84,38 @@ impl RoleInfo {
             })
     }
 
+    #[cfg(feature = "recon")]
+    pub fn get_recon_acl(&self) -> HashMap<Resource, ReconPermissionScope> {
+        let mut acl: HashMap<Resource, ReconPermissionScope> = HashMap::new();
+        let mut recon_resources = RECON_OPS.to_vec();
+        recon_resources.extend(RECON_REPORTS);
+        let recon_internal_resources = [Resource::ReconToken];
+        self.get_permission_groups()
+            .iter()
+            .for_each(|permission_group| {
+                permission_group.resources().iter().for_each(|resource| {
+                    if recon_resources.contains(resource)
+                        && !recon_internal_resources.contains(resource)
+                    {
+                        let scope = match resource {
+                            Resource::ReconAndSettlementAnalytics => ReconPermissionScope::Read,
+                            _ => ReconPermissionScope::from(permission_group.scope()),
+                        };
+                        acl.entry(*resource)
+                            .and_modify(|curr_scope| {
+                                *curr_scope = if (*curr_scope) < scope {
+                                    scope
+                                } else {
+                                    *curr_scope
+                                }
+                            })
+                            .or_insert(scope);
+                    }
+                })
+            });
+        acl
+    }
+
     pub async fn from_role_id_in_merchant_scope(
         state: &SessionState,
         role_id: &str,
diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs
index 39f6d47f824..9c67c12f527 100644
--- a/crates/router/src/services/authorization/roles/predefined_roles.rs
+++ b/crates/router/src/services/authorization/roles/predefined_roles.rs
@@ -28,7 +28,10 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::MerchantDetailsManage,
                 PermissionGroup::AccountManage,
                 PermissionGroup::OrganizationManage,
-                PermissionGroup::ReconOps,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconOpsManage,
+                PermissionGroup::ReconReportsView,
+                PermissionGroup::ReconReportsManage,
             ],
             role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(),
             role_name: "internal_admin".to_string(),
@@ -51,6 +54,8 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::UsersView,
                 PermissionGroup::MerchantDetailsView,
                 PermissionGroup::AccountView,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconReportsView,
             ],
             role_id: common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
             role_name: "internal_view_only".to_string(),
@@ -82,7 +87,10 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::MerchantDetailsManage,
                 PermissionGroup::AccountManage,
                 PermissionGroup::OrganizationManage,
-                PermissionGroup::ReconOps,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconOpsManage,
+                PermissionGroup::ReconReportsView,
+                PermissionGroup::ReconReportsManage,
             ],
             role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             role_name: "organization_admin".to_string(),
@@ -113,7 +121,10 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::AccountView,
                 PermissionGroup::MerchantDetailsManage,
                 PermissionGroup::AccountManage,
-                PermissionGroup::ReconOps,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconOpsManage,
+                PermissionGroup::ReconReportsView,
+                PermissionGroup::ReconReportsManage,
             ],
             role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
             role_name: "merchant_admin".to_string(),
@@ -136,6 +147,8 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::UsersView,
                 PermissionGroup::MerchantDetailsView,
                 PermissionGroup::AccountView,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconReportsView,
             ],
             role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(),
             role_name: "merchant_view_only".to_string(),
@@ -180,6 +193,8 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::AccountView,
                 PermissionGroup::MerchantDetailsManage,
                 PermissionGroup::AccountManage,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconReportsView,
             ],
             role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(),
             role_name: "merchant_developer".to_string(),
@@ -203,6 +218,9 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::UsersView,
                 PermissionGroup::MerchantDetailsView,
                 PermissionGroup::AccountView,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconOpsManage,
+                PermissionGroup::ReconReportsView,
             ],
             role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(),
             role_name: "merchant_operator".to_string(),
@@ -223,6 +241,8 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::UsersView,
                 PermissionGroup::MerchantDetailsView,
                 PermissionGroup::AccountView,
+                PermissionGroup::ReconOpsView,
+                PermissionGroup::ReconReportsView,
             ],
             role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(),
             role_name: "customer_support".to_string(),
diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs
index 8c966d79d80..66e730f0824 100644
--- a/crates/router/src/services/email/types.rs
+++ b/crates/router/src/services/email/types.rs
@@ -383,7 +383,6 @@ impl EmailData for InviteUser {
 pub struct ReconActivation {
     pub recipient_email: domain::UserEmail,
     pub user_name: domain::UserName,
-    pub settings: std::sync::Arc<configs::Settings>,
     pub subject: &'static str,
 }
 
@@ -458,7 +457,6 @@ pub struct ProFeatureRequest {
     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,
 }
 

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Described in #6524 

This PR includes below changes
- Extending resources enum (for specifying the resource being consumed from the dashboard)
- Extending granular permission groups for recon
- Populating `acl` field in the recon token

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
Helps maintain and configure permissions for recon module granularly.


## How did you test it?
- Verify the generated recon token has `acl`
- `GET /verify_token` response to have `acl`

<details>
    <summary>Check /verify_token response</summary>

cURL

    curl --location --request GET 'http://localhost:8080/recon/verify_token' \
        --header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZGJjMzJmMmYtYmI5Ni00MDI0LTliYTUtNjhkOTc3MzM0N2U5IiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzMxOTk2NjQ4Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTczMjE4MzU4Miwib3JnX2lkIjoib3JnX0htMmY5dERZM2lGN0ZRckZUYUxKIiwicHJvZmlsZV9pZCI6InByb19GY0NaN1c4MllZVmNXMU1DSjZIOCIsInRlbmFudF9pZCI6InB1YmxpYyIsImFjbCI6IntcInJlY29uX2ZpbGVzXCI6XCJSV1wiLFwicmVjb25fY29uZmlnXCI6XCJSV1wiLFwicmVjb25fdG9rZW5cIjpcIlJXXCIsXCJyZWNvbl9yZXBvcnRzXCI6XCJSV1wiLFwicmVjb25fdXBsb2FkXCI6XCJSV1wiLFwicnVuX3JlY29uXCI6XCJSV1wiLFwicmVjb25fYW5kX3NldHRsZW1lbnRfYW5hbHl0aWNzXCI6XCJSXCJ9In0.wvS97F0dXwY7Y5uKSk8PEqHOhoAAPlR0coX_kIGyM2I'

Response

    {
        "merchant_id": "merchant_1731996648",
        "user_email": "kashif2@juspay.in",
        "acl": "{\"recon_reports\":\"RW\",\"recon_files\":\"RW\",\"run_recon\":\"RW\",\"recon_and_settlement_analytics\":\"R\",\"recon_token\":\"RW\",\"recon_upload\":\"RW\",\"recon_config\":\"RW\"}"
    }

</details>

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
