Bug: feat(users): add support to check 2FA status



Add support to check whether the 2FA has been done recently or not. ( For now it will check for last five minutes)


Use case: The api will help to check for when was last TOTP done, if user has recently done sign in and trying to do some operation that asks for TOTP again from inside dashboard, we can ignore TOTP based on the result this api gives.
diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs
index a472b3a76e6..c41f52a93ea 100644
--- a/crates/api_models/src/events/user.rs
+++ b/crates/api_models/src/events/user.rs
@@ -16,8 +16,9 @@ use crate::user::{
     GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, ReInviteUserRequest,
     RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest,
     SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
-    TokenOrPayloadResponse, TokenResponse, UpdateUserAccountDetailsRequest, UserFromEmailRequest,
-    UserMerchantCreate, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
+    TokenOrPayloadResponse, TokenResponse, TwoFactorAuthStatusResponse,
+    UpdateUserAccountDetailsRequest, UserFromEmailRequest, UserMerchantCreate, VerifyEmailRequest,
+    VerifyRecoveryCodeRequest, VerifyTotpRequest,
 };
 
 impl ApiEventMetric for DashboardEntryResponse {
@@ -73,6 +74,7 @@ common_utils::impl_misc_api_event_type!(
     GetUserRoleDetailsRequest,
     GetUserRoleDetailsResponse,
     TokenResponse,
+    TwoFactorAuthStatusResponse,
     UserFromEmailRequest,
     BeginTotpResponse,
     VerifyRecoveryCodeRequest,
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 0c8678d2ef8..ee9498cfeee 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -235,6 +235,12 @@ pub struct TokenResponse {
     pub token_type: TokenPurpose,
 }
 
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct TwoFactorAuthStatusResponse {
+    pub totp: bool,
+    pub recovery_code: bool,
+}
+
 #[derive(Debug, serde::Serialize)]
 #[serde(untagged)]
 pub enum TokenOrPayloadResponse<T> {
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 7b6e8ebd365..0da381bd493 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1874,3 +1874,16 @@ pub async fn terminate_two_factor_auth(
         token,
     )
 }
+
+pub async fn check_two_factor_auth_status(
+    state: AppState,
+    user_token: auth::UserFromToken,
+) -> UserResponse<user_api::TwoFactorAuthStatusResponse> {
+    Ok(ApplicationResponse::Json(
+        user_api::TwoFactorAuthStatusResponse {
+            totp: tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await?,
+            recovery_code: tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id)
+                .await?,
+        },
+    ))
+}
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index b38479cd2b6..141e9f53af4 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1214,6 +1214,7 @@ impl User {
         // Two factor auth routes
         route = route.service(
             web::scope("/2fa")
+                .service(web::resource("").route(web::get().to(check_two_factor_auth_status)))
                 .service(
                     web::scope("/totp")
                         .service(web::resource("/begin").route(web::get().to(totp_begin)))
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 9e53eb35473..417a4360c51 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -218,7 +218,8 @@ impl From<Flow> for ApiIdentifier {
             | Flow::TotpUpdate
             | Flow::RecoveryCodeVerify
             | Flow::RecoveryCodesGenerate
-            | Flow::TerminateTwoFactorAuth => Self::User,
+            | Flow::TerminateTwoFactorAuth
+            | Flow::TwoFactorAuthStatus => Self::User,
 
             Flow::ListRoles
             | Flow::GetRole
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 5ba7ec8da25..f22e4aac521 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -735,3 +735,20 @@ pub async fn terminate_two_factor_auth(
     ))
     .await
 }
+
+pub async fn check_two_factor_auth_status(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+) -> HttpResponse {
+    let flow = Flow::TwoFactorAuthStatus;
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        (),
+        |state, user, _, _| user_core::check_two_factor_auth_status(state, user),
+        &auth::DashboardNoPermissionAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 1bfc20ff1ca..728f2ed206e 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -414,6 +414,8 @@ pub enum Flow {
     RecoveryCodesGenerate,
     // Terminate two factor authentication
     TerminateTwoFactorAuth,
+    // Check 2FA status
+    TwoFactorAuthStatus,
     /// List initial webhook delivery attempts
     WebhookEventInitialDeliveryAttemptList,
     /// List delivery attempts for a webhook event

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

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

## Description
The api checks whether the 2FA has been done recently or not.
It just checks the status in Redis and gives the response.

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


## How did you test it?
Use the curl to get the status
```
curl --location 'http://localhost:8080/user/2fa' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: Cookie_1=value'
```

Response will be something like
```
{
    "totp": true,
    "recovery_code": false
}
```
## 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
