Bug: Include single purpose token and single purpose JWT auth



Create single purpose token and single purpose JWT auth that will server as generic for intermediate tokens and can be used for different cases, during TOTP, change passwords, accept invite etc.
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index c2a8669030b..2c4ec3f5e70 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -68,6 +68,8 @@ 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
+
 pub const JWT_TOKEN_COOKIE_NAME: &str = "login_token";
 
 pub const USER_BLACKLIST_PREFIX: &str = "BU_";
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index 5203b20c55b..9ac7e6dd3a3 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -64,6 +64,10 @@ pub enum AuthenticationType {
     UserJwt {
         user_id: String,
     },
+    SinglePurposeJWT {
+        user_id: String,
+        purpose: Purpose,
+    },
     MerchantId {
         merchant_id: String,
     },
@@ -101,11 +105,51 @@ impl AuthenticationType {
                 user_id: _,
             }
             | Self::WebhookAuth { merchant_id } => Some(merchant_id.as_ref()),
-            Self::AdminApiKey | Self::UserJwt { .. } | Self::NoAuth => None,
+            Self::AdminApiKey
+            | Self::UserJwt { .. }
+            | Self::SinglePurposeJWT { .. }
+            | Self::NoAuth => None,
         }
     }
 }
 
+#[derive(Clone, Debug)]
+pub struct UserFromSinglePurposeToken {
+    pub user_id: String,
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct SinglePurposeToken {
+    pub user_id: String,
+    pub purpose: Purpose,
+    pub exp: u64,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, strum::Display, serde::Deserialize, serde::Serialize)]
+pub enum Purpose {
+    AcceptInvite,
+}
+
+#[cfg(feature = "olap")]
+impl SinglePurposeToken {
+    pub async fn new_token(
+        user_id: String,
+        purpose: Purpose,
+        settings: &Settings,
+    ) -> UserResult<String> {
+        let exp_duration =
+            std::time::Duration::from_secs(consts::SINGLE_PURPOSE_TOKEN_TIME_IN_SECS);
+        let exp = jwt::generate_exp(exp_duration)?.as_secs();
+        let token_payload = Self {
+            user_id,
+            purpose,
+            exp,
+        };
+        jwt::generate_jwt(&token_payload, settings).await
+    }
+}
+
+// TODO: This has to be removed once single purpose token is used as a intermediate token
 #[derive(Clone, Debug)]
 pub struct UserWithoutMerchantFromToken {
     pub user_id: String,
@@ -316,6 +360,42 @@ where
     }
 }
 
+#[allow(dead_code)]
+#[derive(Debug)]
+pub(crate) struct SinglePurposeJWTAuth(pub Purpose);
+
+#[cfg(feature = "olap")]
+#[async_trait]
+impl<A> AuthenticateAndFetch<UserFromSinglePurposeToken, A> for SinglePurposeJWTAuth
+where
+    A: AppStateInfo + Sync,
+{
+    async fn authenticate_and_fetch(
+        &self,
+        request_headers: &HeaderMap,
+        state: &A,
+    ) -> RouterResult<(UserFromSinglePurposeToken, AuthenticationType)> {
+        let payload = parse_jwt_payload::<A, SinglePurposeToken>(request_headers, state).await?;
+        if payload.check_in_blacklist(state).await? {
+            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
+        }
+
+        if self.0 != payload.purpose {
+            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
+        }
+
+        Ok((
+            UserFromSinglePurposeToken {
+                user_id: payload.user_id.clone(),
+            },
+            AuthenticationType::SinglePurposeJWT {
+                user_id: payload.user_id,
+                purpose: payload.purpose,
+            },
+        ))
+    }
+}
+
 #[derive(Debug)]
 pub struct AdminApiAuth;
 
diff --git a/crates/router/src/services/authentication/blacklist.rs b/crates/router/src/services/authentication/blacklist.rs
index 7c1cbf27e10..b2f63f4927e 100644
--- a/crates/router/src/services/authentication/blacklist.rs
+++ b/crates/router/src/services/authentication/blacklist.rs
@@ -5,7 +5,7 @@ use common_utils::date_time;
 use error_stack::ResultExt;
 use redis_interface::RedisConnectionPool;
 
-use super::{AuthToken, UserAuthToken};
+use super::{AuthToken, SinglePurposeToken, UserAuthToken};
 #[cfg(feature = "email")]
 use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS};
 use crate::{
@@ -163,3 +163,13 @@ impl BlackList for UserAuthToken {
         check_user_in_blacklist(state, &self.user_id, self.exp).await
     }
 }
+
+#[async_trait::async_trait]
+impl BlackList for SinglePurposeToken {
+    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
+    where
+        A: AppStateInfo + Sync,
+    {
+        check_user_in_blacklist(state, &self.user_id, self.exp).await
+    }
+}

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

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

## Description
Add single purpose token and authentication for single purpose token. This token will serves as a intermediate token and used to perform a certain action only.

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


## How did you test it?
This can be tested with upcoming PRs for reset password, TOTP, accept invite etc. Single purpose token and auth will be used for that. 
I also tested this locally, by changing auth type of  change passwor and it was working as expected.


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