diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index c2a8669030..2c4ec3f5e7 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 03db3987ea..8652f51451 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 { + 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 AuthenticateAndFetch 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::(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 7c1cbf27e1..b2f63f4927 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(&self, state: &A) -> RouterResult + where + A: AppStateInfo + Sync, + { + check_user_in_blacklist(state, &self.user_id, self.exp).await + } +}