Bug: feat(users): add endpoint to reset totp



Add support to reset TOTP from dashboard!
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 7b6e8ebd365..9931d450ab9 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1634,7 +1634,39 @@ pub async fn begin_totp(
 
     let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), None)?;
     let secret = totp.get_secret_base32().into();
+    tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?;
+
+    Ok(ApplicationResponse::Json(user_api::BeginTotpResponse {
+        secret: Some(user_api::TotpSecret {
+            secret,
+            totp_url: totp.get_url().into(),
+        }),
+    }))
+}
+
+pub async fn reset_totp(
+    state: AppState,
+    user_token: auth::UserFromToken,
+) -> UserResponse<user_api::BeginTotpResponse> {
+    let user_from_db: domain::UserFromStorage = state
+        .store
+        .find_user_by_id(&user_token.user_id)
+        .await
+        .change_context(UserErrors::InternalServerError)?
+        .into();
+
+    if user_from_db.get_totp_status() != TotpStatus::Set {
+        return Err(UserErrors::TotpNotSetup.into());
+    }
 
+    if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await?
+        && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await?
+    {
+        return Err(UserErrors::TwoFactorAuthRequired.into());
+    }
+
+    let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), None)?;
+    let secret = totp.get_secret_base32().into();
     tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?;
 
     Ok(ApplicationResponse::Json(user_api::BeginTotpResponse {
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index b38479cd2b6..6d790d81873 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1217,6 +1217,7 @@ impl User {
                 .service(
                     web::scope("/totp")
                         .service(web::resource("/begin").route(web::get().to(totp_begin)))
+                        .service(web::resource("/reset").route(web::get().to(totp_reset)))
                         .service(
                             web::resource("/verify")
                                 .route(web::post().to(totp_verify))
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 9e53eb35473..ee466a7e09a 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -214,6 +214,7 @@ impl From<Flow> for ApiIdentifier {
             | Flow::VerifyEmailRequest
             | Flow::UpdateUserAccountDetails
             | Flow::TotpBegin
+            | Flow::TotpReset
             | Flow::TotpVerify
             | Flow::TotpUpdate
             | Flow::RecoveryCodeVerify
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 5ba7ec8da25..6b73ea03b62 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -648,6 +648,20 @@ pub async fn totp_begin(state: web::Data<AppState>, req: HttpRequest) -> HttpRes
     .await
 }
 
+pub async fn totp_reset(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
+    let flow = Flow::TotpReset;
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        (),
+        |state, user, _, _| user_core::reset_totp(state, user),
+        &auth::DashboardNoPermissionAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 pub async fn totp_verify(
     state: web::Data<AppState>,
     req: HttpRequest,
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 1bfc20ff1ca..ddbc64e81f8 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -404,6 +404,8 @@ pub enum Flow {
     UserFromEmail,
     /// Begin TOTP
     TotpBegin,
+    // Reset TOTP
+    TotpReset,
     /// Verify TOTP
     TotpVerify,
     /// Update TOTP secret

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

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

## Description
Add support to reset TOTP

### Additional Changes

- [x] 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
Closes [#4820](https://github.com/juspay/hyperswitch/issues/4820)


## How did you test it?
Use the curl to reset TOTP when it is already setup (from inside dashboard):
```
curl --location 'http://localhost:8080/user/2fa/totp/reset' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: Cookie_1=value'
```

Response:
```
{
    "secret": {
        "secret": "VERUDBNHLYZDGFTKNXY2VVP4EQ5GOBUB",
        "totp_url": "otpauth://totp/Hyperswitch:ver%40gmail.com?secret=VERUDBNHLYZDGFTKNXY2VVP4EQ5GOBUB&issuer=Hyperswitch"
    }
}
```
The Response can be used to generate new QR and reset TOTP.

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