Bug: bug(user): Magic link is not expiring after being used once



When a verified user is using magic link, that magic link is not blacklisted after being used once. User is able to use the same magic link to login to dashboard multiple times.
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index c78f2c8080f..7e79e9e2e89 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -505,10 +505,8 @@ pub async fn reset_password_token_only_flow(
 
     let user = state
         .global_store
-        .update_user_by_email(
-            &email_token
-                .get_email()
-                .change_context(UserErrors::InternalServerError)?,
+        .update_user_by_user_id(
+            user_from_db.get_user_id(),
             storage_user::UserUpdate::PasswordUpdate {
                 password: hash_password,
             },
@@ -516,6 +514,17 @@ pub async fn reset_password_token_only_flow(
         .await
         .change_context(UserErrors::InternalServerError)?;
 
+    if !user_from_db.is_verified() {
+        let _ = state
+            .global_store
+            .update_user_by_user_id(
+                user_from_db.get_user_id(),
+                storage_user::UserUpdate::VerifyUser,
+            )
+            .await
+            .map_err(|e| logger::error!(?e));
+    }
+
     let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
         .await
         .map_err(|e| logger::error!(?e));
@@ -1021,6 +1030,17 @@ pub async fn accept_invite_from_email_token_only_flow(
         .await
         .change_context(UserErrors::InternalServerError)?;
 
+    if !user_from_db.is_verified() {
+        let _ = state
+            .global_store
+            .update_user_by_user_id(
+                user_from_db.get_user_id(),
+                storage_user::UserUpdate::VerifyUser,
+            )
+            .await
+            .map_err(|e| logger::error!(?e));
+    }
+
     let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
         .await
         .map_err(|e| logger::error!(?e));
@@ -1476,13 +1496,9 @@ pub async fn verify_email_token_only_flow(
         .change_context(UserErrors::InternalServerError)?
         .into();
 
-    if matches!(user_token.origin, domain::Origin::VerifyEmail)
-        || matches!(user_token.origin, domain::Origin::MagicLink)
-    {
-        let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
-            .await
-            .map_err(|e| logger::error!(?e));
-    }
+    let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
+        .await
+        .map_err(|e| logger::error!(?e));
 
     let current_flow =
         domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::VerifyEmail.into())?;
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index 47f26ce04e5..5ff79b7feee 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -837,6 +837,10 @@ impl UserFromStorage {
         Ok(Some(days_left_for_verification.whole_days()))
     }
 
+    pub fn is_verified(&self) -> bool {
+        self.0.is_verified
+    }
+
     pub fn is_password_rotate_required(&self, state: &SessionState) -> UserResult<bool> {
         let last_password_modified_at =
             if let Some(last_password_modified_at) = self.0.last_password_modified_at {
diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs
index 41ae12350fb..2ee9b389788 100644
--- a/crates/router/src/types/domain/user/decision_manager.rs
+++ b/crates/router/src/types/domain/user/decision_manager.rs
@@ -42,7 +42,7 @@ impl SPTFlow {
             Self::TOTP => Ok(true),
             // Main email APIs
             Self::AcceptInvitationFromEmail | Self::ResetPassword => Ok(true),
-            Self::VerifyEmail => Ok(!user.0.is_verified),
+            Self::VerifyEmail => Ok(true),
             // Final Checks
             Self::ForceSetPassword => user.is_password_rotate_required(state),
             Self::MerchantSelect => user
@@ -154,17 +154,15 @@ const VERIFY_EMAIL_FLOW: [UserFlow; 5] = [
     UserFlow::JWTFlow(JWTFlow::UserInfo),
 ];
 
-const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 5] = [
+const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 4] = [
     UserFlow::SPTFlow(SPTFlow::TOTP),
-    UserFlow::SPTFlow(SPTFlow::VerifyEmail),
     UserFlow::SPTFlow(SPTFlow::AcceptInvitationFromEmail),
     UserFlow::SPTFlow(SPTFlow::ForceSetPassword),
     UserFlow::JWTFlow(JWTFlow::UserInfo),
 ];
 
-const RESET_PASSWORD_FLOW: [UserFlow; 3] = [
+const RESET_PASSWORD_FLOW: [UserFlow; 2] = [
     UserFlow::SPTFlow(SPTFlow::TOTP),
-    UserFlow::SPTFlow(SPTFlow::VerifyEmail),
     UserFlow::SPTFlow(SPTFlow::ResetPassword),
 ];
 

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

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

## Description
<!-- Describe your changes in detail -->
Currently verify email API will be picked by the decision manager only if the user is not verified.

But this API should be called always in case of verify email and magic link flow. This PR fixes it.

### 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
<!--
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 #4970.

## 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/connect_account' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "user email"
}'
```
You will receive a magic link to the email provided.

Once the link in the email is used to login to dashboard, the link should not work anymore.

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