Bug: fix: Ability to change TOTP issuer name depending on the env



As of now, the issuer name in TOTP is always "Hyperswitch" independent of env.

As TOTP will be present in multiple environments, we need to have different issuer names for all the environments, so that user can easily identify the TOTP.
diff --git a/config/config.example.toml b/config/config.example.toml
index 8aa0ca9e52f..ac428298e73 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -351,8 +351,9 @@ email_role_arn = ""        # The amazon resource name ( arn ) of the role which
 sts_role_session_name = "" # An identifier for the assumed role session, used to uniquely identify a session.
 
 [user]
-password_validity_in_days = 90       # Number of days after which password should be updated
-two_factor_auth_expiry_in_secs = 300 # Number of seconds after which 2FA should be done again if doing update/change from inside
+password_validity_in_days = 90             # Number of days after which password should be updated
+two_factor_auth_expiry_in_secs = 300       # Number of seconds after which 2FA should be done again if doing update/change from inside
+totp_issuer_name = "Hyperswitch"           # Name of the issuer for TOTP
 
 #tokenization configuration which describe token lifetime and payment method for specific connector
 [tokenization]
diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml
index f6ed3ca5ea5..8d76771f897 100644
--- a/config/deployments/integration_test.toml
+++ b/config/deployments/integration_test.toml
@@ -114,6 +114,7 @@ slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2aw
 [user]
 password_validity_in_days = 90
 two_factor_auth_expiry_in_secs = 300
+totp_issuer_name = "Hyperswitch Integ"
 
 [frm]
 enabled = true
diff --git a/config/deployments/production.toml b/config/deployments/production.toml
index 37b35eb47e7..65f8d87215a 100644
--- a/config/deployments/production.toml
+++ b/config/deployments/production.toml
@@ -121,6 +121,7 @@ slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2aw
 [user]
 password_validity_in_days = 90
 two_factor_auth_expiry_in_secs = 300
+totp_issuer_name = "Hyperswitch Production"
 
 [frm]
 enabled = false
diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml
index fc496e34fc3..94f6d4b3430 100644
--- a/config/deployments/sandbox.toml
+++ b/config/deployments/sandbox.toml
@@ -121,6 +121,7 @@ slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2aw
 [user]
 password_validity_in_days = 90
 two_factor_auth_expiry_in_secs = 300
+totp_issuer_name = "Hyperswitch Sandbox"
 
 [frm]
 enabled = true
diff --git a/config/development.toml b/config/development.toml
index d12a667b631..25fe55e6057 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -270,6 +270,7 @@ sts_role_session_name = ""
 [user]
 password_validity_in_days = 90
 two_factor_auth_expiry_in_secs = 300
+totp_issuer_name = "Hyperswitch Dev"
 
 [bank_config.eps]
 stripe = { banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" }
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index 3f6c9523e93..f43ceca893d 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -54,6 +54,7 @@ recon_admin_api_key = "recon_test_admin"
 [user]
 password_validity_in_days = 90
 two_factor_auth_expiry_in_secs = 300
+totp_issuer_name = "Hyperswitch"
 
 [locker]
 host = ""
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index c71ed4496b0..9aa879a4387 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -436,6 +436,7 @@ pub struct Secrets {
 pub struct UserSettings {
     pub password_validity_in_days: u16,
     pub two_factor_auth_expiry_in_secs: i64,
+    pub totp_issuer_name: String,
 }
 
 #[derive(Debug, Deserialize, Clone)]
diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs
index 3c6cd8b6ccd..c7615aa4be4 100644
--- a/crates/router/src/consts/user.rs
+++ b/crates/router/src/consts/user.rs
@@ -1,15 +1,17 @@
 pub const MAX_NAME_LENGTH: usize = 70;
 pub const MAX_COMPANY_NAME_LENGTH: usize = 70;
 pub const BUSINESS_EMAIL: &str = "biz@hyperswitch.io";
+
 pub const RECOVERY_CODES_COUNT: usize = 8;
 pub const RECOVERY_CODE_LENGTH: usize = 8; // This is without counting the hyphen in between
-pub const TOTP_ISSUER_NAME: &str = "Hyperswitch";
+
 /// The number of digits composing the auth code.
 pub const TOTP_DIGITS: usize = 6;
 /// Duration in seconds of a step.
 pub const TOTP_VALIDITY_DURATION_IN_SECONDS: u64 = 30;
 /// Number of totps allowed as network delay. 1 would mean one totp before current totp and one totp after are valids.
 pub const TOTP_TOLERANCE: u8 = 1;
+
 pub const MAX_PASSWORD_LENGTH: usize = 70;
 pub const MIN_PASSWORD_LENGTH: usize = 8;
 
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index c259e87c93d..f4751c3a55a 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1635,7 +1635,11 @@ pub async fn begin_totp(
         }));
     }
 
-    let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), None)?;
+    let totp = tfa_utils::generate_default_totp(
+        user_from_db.get_email(),
+        None,
+        state.conf.user.totp_issuer_name.clone(),
+    )?;
     let secret = totp.get_secret_base32().into();
     tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?;
 
@@ -1668,7 +1672,12 @@ pub async fn reset_totp(
         return Err(UserErrors::TwoFactorAuthRequired.into());
     }
 
-    let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), None)?;
+    let totp = tfa_utils::generate_default_totp(
+        user_from_db.get_email(),
+        None,
+        state.conf.user.totp_issuer_name.clone(),
+    )?;
+
     let secret = totp.get_secret_base32().into();
     tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?;
 
@@ -1701,7 +1710,11 @@ pub async fn verify_totp(
         .await?
         .ok_or(UserErrors::InternalServerError)?;
 
-    let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), Some(user_totp_secret))?;
+    let totp = tfa_utils::generate_default_totp(
+        user_from_db.get_email(),
+        Some(user_totp_secret),
+        state.conf.user.totp_issuer_name.clone(),
+    )?;
 
     if totp
         .generate_current()
@@ -1732,7 +1745,11 @@ pub async fn update_totp(
         .await?
         .ok_or(UserErrors::TotpSecretNotFound)?;
 
-    let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), Some(new_totp_secret))?;
+    let totp = tfa_utils::generate_default_totp(
+        user_from_db.get_email(),
+        Some(new_totp_secret),
+        state.conf.user.totp_issuer_name.clone(),
+    )?;
 
     if totp
         .generate_current()
diff --git a/crates/router/src/utils/user/two_factor_auth.rs b/crates/router/src/utils/user/two_factor_auth.rs
index a0915f3ed86..f64eda4dc5a 100644
--- a/crates/router/src/utils/user/two_factor_auth.rs
+++ b/crates/router/src/utils/user/two_factor_auth.rs
@@ -12,6 +12,7 @@ use crate::{
 pub fn generate_default_totp(
     email: pii::Email,
     secret: Option<masking::Secret<String>>,
+    issuer: String,
 ) -> UserResult<TOTP> {
     let secret = secret
         .map(|sec| totp_rs::Secret::Encoded(sec.expose()))
@@ -25,7 +26,7 @@ pub fn generate_default_totp(
         consts::user::TOTP_TOLERANCE,
         consts::user::TOTP_VALIDITY_DURATION_IN_SECONDS,
         secret,
-        Some(consts::user::TOTP_ISSUER_NAME.to_string()),
+        Some(issuer),
         email.expose().expose(),
     )
     .change_context(UserErrors::InternalServerError)
diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml
index 2abe90f6086..e46a710054e 100644
--- a/loadtest/config/development.toml
+++ b/loadtest/config/development.toml
@@ -31,6 +31,7 @@ jwt_secret = "secret"
 [user]
 password_validity_in_days = 90
 two_factor_auth_expiry_in_secs = 300
+totp_issuer_name = "Hyperswitch"
 
 [locker]
 host = ""

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

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

## Description
<!-- Describe your changes in detail -->
This PR adds configs for TOTP Issuer (to easily change issuer name of TOTP)

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [x] 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`
3. `crates/router/src/configs`
4. `loadtest/config`
-->
1. `config/config.example.toml`
2. `config/deployments/integration_test.toml`
3. `config/deployments/production.toml`
4. `config/deployments/sandbox.toml`
5. `config/development.toml`
6. `config/docker_compose.toml`
7. `loadtest/config/development.toml`

## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Closes #4775.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
```
curl --location 'http://localhost:8080/user/2fa/totp/begin' \
--header 'Authorization: Bearer SPT with Purpose as TOTP' \
```

```
{
    "secret": {
        "secret": "INY6LEAFGX3OWJFVBXBUM7OMH4KLNI76",
        "totp_url": "otpauth://totp/Hyper:mani.dchandra%40juspay.in?secret=INY6LEAFGX3OWJFVBXBUM7OMH4KLNI76&issuer=Hyper"
    }
}
```
The `totp_url` field in the above response contains issuer as `Hyper` and this is coming from config.
- In integ, issuer name will be: `Hyperswitch Integ`
- In sandbox, issuer name will be: `Hyperswitch Sandbox`
- In production, issuer name will be: `Hyperswitch Prod`
- In local, issuer name will be: `Hyperswitch Deb`


## 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
