Bug: fix: Fix bugs caused by token only flows



- Password without special characters is not throwing parsing failed error.
- Signup is not populating the `last_password_modified_at`.
- Verify email is blacklisting all email tokens.
- If the SPT token time is less than JWT time, some SPTs issued after that blacklist are not working.

diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index c40d284ee3a..610d8ef8b1f 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -70,7 +70,9 @@ pub const LOCKER_REDIS_EXPIRY_SECONDS: u32 = 60 * 15; // 15 minutes
 
 pub const JWT_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24 * 2; // 2 days
 
-pub const SINGLE_PURPOSE_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day
+// This should be one day, but it is causing issue while checking token in blacklist.
+// TODO: This should be fixed in future.
+pub const SINGLE_PURPOSE_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24 * 2; // 2 days
 
 pub const JWT_TOKEN_COOKIE_NAME: &str = "login_token";
 
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 83cdd1d318b..bfe01863550 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1421,9 +1421,13 @@ pub async fn verify_email_token_only_flow(
         .change_context(UserErrors::InternalServerError)?
         .into();
 
-    let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
-        .await
-        .map_err(|e| logger::error!(?e));
+    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 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 45f5d74d6f6..051e6ccf38e 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -1,4 +1,8 @@
-use std::{collections::HashSet, ops, str::FromStr};
+use std::{
+    collections::HashSet,
+    ops::{self, Not},
+    str::FromStr,
+};
 
 use api_models::{
     admin as admin_api, organization as api_org, user as user_api, user_role as user_role_api,
@@ -172,8 +176,7 @@ impl UserPassword {
             has_upper_case = has_upper_case || c.is_uppercase();
             has_lower_case = has_lower_case || c.is_lowercase();
             has_numeric_value = has_numeric_value || c.is_numeric();
-            has_special_character =
-                has_special_character || !(c.is_alphanumeric() && c.is_whitespace());
+            has_special_character = has_special_character || !c.is_alphanumeric();
             has_whitespace = has_whitespace || c.is_whitespace();
         }
 
@@ -510,6 +513,7 @@ pub struct NewUser {
     email: UserEmail,
     password: UserPassword,
     new_merchant: NewUserMerchant,
+    is_temporary_password: bool,
 }
 
 impl NewUser {
@@ -614,12 +618,20 @@ impl TryFrom<NewUser> for storage_user::UserNew {
 
     fn try_from(value: NewUser) -> UserResult<Self> {
         let hashed_password = password::generate_password_hash(value.password.get_secret())?;
+        let now = common_utils::date_time::now();
         Ok(Self {
             user_id: value.get_user_id(),
             name: value.get_name(),
             email: value.get_email().into_inner(),
             password: hashed_password,
-            ..Default::default()
+            is_verified: false,
+            created_at: Some(now),
+            last_modified_at: Some(now),
+            preferred_merchant_id: None,
+            totp_status: TotpStatus::NotSet,
+            totp_secret: None,
+            totp_recovery_codes: None,
+            last_password_modified_at: value.is_temporary_password.not().then_some(now),
         })
     }
 }
@@ -640,6 +652,7 @@ impl TryFrom<user_api::SignUpWithMerchantIdRequest> for NewUser {
             password,
             user_id,
             new_merchant,
+            is_temporary_password: false,
         })
     }
 }
@@ -660,6 +673,7 @@ impl TryFrom<user_api::SignUpRequest> for NewUser {
             email,
             password,
             new_merchant,
+            is_temporary_password: false,
         })
     }
 }
@@ -680,6 +694,7 @@ impl TryFrom<user_api::ConnectAccountRequest> for NewUser {
             email,
             password,
             new_merchant,
+            is_temporary_password: true,
         })
     }
 }
@@ -700,6 +715,7 @@ impl TryFrom<user_api::CreateInternalUserRequest> for NewUser {
             email,
             password,
             new_merchant,
+            is_temporary_password: false,
         })
     }
 }
@@ -717,6 +733,9 @@ impl TryFrom<UserMerchantCreateRequestWithToken> for NewUser {
             email: user.0.email.clone().try_into()?,
             password: UserPassword::new_password_without_validation(user.0.password)?,
             new_merchant,
+            // This is true because we are not creating a user with this request. And if it is set
+            // to false, last_password_modified_at will be overwritten if this user is inserted.
+            is_temporary_password: true,
         })
     }
 }
@@ -736,6 +755,7 @@ impl TryFrom<InviteeUserRequestWithInvitedUserToken> for NewUser {
             email,
             password,
             new_merchant,
+            is_temporary_password: true,
         })
     }
 }

## 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 -->
This PR fixes the following bugs:
- Password without special characters is not throwing parsing failed error.
- Signup is not populating the `last_password_modified_at`.
- Verify email is blacklisting all email tokens.
- If the SPT token time is less than JWT time, some SPTs issued after that blacklist are not working.


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

## 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)?
-->
> [!NOTE]  
> This only works when email feature flag is disabled.
- To check the password validation
```
curl --location 'http://localhost:8080/user/signup' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "unregistered email",
    "password": "password"
}'
```
- If the password is valid, then you will get the following response
```
{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiM2MwOTBiMDYtYWY2ZS00MDk0LTgwYzktMWEzOTlkOTQ2MjBjIiwicHVycG9zZSI6InRvdHAiLCJvcmlnaW4iOiJzaWduX2luIiwiZXhwIjoxNzE1MzMzNTE1fQ.v3fBLoTdf01RDWs24ukphvNFuVQ9AsqaVBuUfjviEuQ",
    "token_type": "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
