Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(user): add single purpose token and auth #4470

Merged
merged 6 commits into from Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/router/src/consts.rs
Expand Up @@ -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_";
Expand Down
82 changes: 81 additions & 1 deletion crates/router/src/services/authentication.rs
Expand Up @@ -64,6 +64,10 @@ pub enum AuthenticationType {
UserJwt {
user_id: String,
},
SinglePurposeJWT {
user_id: String,
purpose: Purpose,
},
MerchantId {
merchant_id: String,
},
Expand Down Expand Up @@ -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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets make this Secret<String>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can cover this later, making this and other similar results as Secret, at present they all are same.

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,
Expand Down Expand Up @@ -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;

Expand Down
12 changes: 11 additions & 1 deletion crates/router/src/services/authentication/blacklist.rs
Expand Up @@ -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::{
Expand Down Expand Up @@ -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
}
}