Skip to content

Commit

Permalink
feat(roles): Add blacklist for roles (#3794)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
ThisIsMani and hyperswitch-bot[bot] authored Feb 23, 2024
1 parent 2c95dcd commit 734327a
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 10 deletions.
2 changes: 2 additions & 0 deletions crates/router/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub const JWT_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24 * 2; // 2 days

pub const USER_BLACKLIST_PREFIX: &str = "BU_";

pub const ROLE_BLACKLIST_PREFIX: &str = "BR_";

#[cfg(feature = "email")]
pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day

Expand Down
4 changes: 3 additions & 1 deletion crates/router/src/core/user_role/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
core::errors::{StorageErrorExt, UserErrors, UserResponse},
routes::AppState,
services::{
authentication::UserFromToken,
authentication::{blacklist, UserFromToken},
authorization::roles::{self, predefined_roles::PREDEFINED_ROLES},
ApplicationResponse,
},
Expand Down Expand Up @@ -219,5 +219,7 @@ pub async fn update_role(
.await
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;

blacklist::insert_role_in_blacklist(&state, role_id).await?;

Ok(ApplicationResponse::StatusOk)
}
17 changes: 9 additions & 8 deletions crates/router/src/services/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use masking::ExposeInterface;
use masking::{PeekInterface, StrongSecret};
use serde::Serialize;

use self::blacklist::BlackList;
use super::authorization::{self, permissions::Permission};
#[cfg(feature = "olap")]
use super::jwt;
Expand Down Expand Up @@ -334,7 +335,7 @@ where
state: &A,
) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> {
let payload = parse_jwt_payload::<A, UserAuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -499,7 +500,7 @@ where
state: &A,
) -> RouterResult<((), AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -528,7 +529,7 @@ where
state: &A,
) -> RouterResult<(UserFromToken, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -566,7 +567,7 @@ where
state: &A,
) -> RouterResult<((), AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -609,7 +610,7 @@ where
state: &A,
) -> RouterResult<(AuthenticationData, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -659,7 +660,7 @@ where
state: &A,
) -> RouterResult<(AuthenticationDataWithUserId, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -710,7 +711,7 @@ where
state: &A,
) -> RouterResult<(UserFromToken, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down Expand Up @@ -741,7 +742,7 @@ where
state: &A,
) -> RouterResult<((), AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if blacklist::check_user_in_blacklist(state, &payload.user_id, payload.exp).await? {
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}

Expand Down
64 changes: 63 additions & 1 deletion crates/router/src/services/authentication/blacklist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ use common_utils::date_time;
use error_stack::{IntoReport, ResultExt};
use redis_interface::RedisConnectionPool;

use super::{AuthToken, UserAuthToken};
#[cfg(feature = "email")]
use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS};
use crate::{
consts::{JWT_TOKEN_TIME_IN_SECS, USER_BLACKLIST_PREFIX},
consts::{JWT_TOKEN_TIME_IN_SECS, ROLE_BLACKLIST_PREFIX, USER_BLACKLIST_PREFIX},
core::errors::{ApiErrorResponse, RouterResult},
routes::app::AppStateInfo,
};
Expand All @@ -34,6 +35,22 @@ pub async fn insert_user_in_blacklist(state: &AppState, user_id: &str) -> UserRe
.change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
pub async fn insert_role_in_blacklist(state: &AppState, role_id: &str) -> UserResult<()> {
let role_blacklist_key = format!("{}{}", ROLE_BLACKLIST_PREFIX, role_id);
let expiry =
expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?;
redis_conn
.set_key_with_expiry(
role_blacklist_key.as_str(),
date_time::now_unix_timestamp(),
expiry,
)
.await
.change_context(UserErrors::InternalServerError)
}

pub async fn check_user_in_blacklist<A: AppStateInfo>(
state: &A,
user_id: &str,
Expand All @@ -49,6 +66,21 @@ pub async fn check_user_in_blacklist<A: AppStateInfo>(
.map(|timestamp| timestamp.map_or(false, |timestamp| timestamp > token_issued_at))
}

pub async fn check_role_in_blacklist<A: AppStateInfo>(
state: &A,
role_id: &str,
token_expiry: u64,
) -> RouterResult<bool> {
let token = format!("{}{}", ROLE_BLACKLIST_PREFIX, role_id);
let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
let redis_conn = get_redis_connection(state)?;
redis_conn
.get_key::<Option<i64>>(token.as_str())
.await
.change_context(ApiErrorResponse::InternalServerError)
.map(|timestamp| timestamp.map_or(false, |timestamp| timestamp > token_issued_at))
}

#[cfg(feature = "email")]
pub async fn insert_email_token_in_blacklist(state: &AppState, token: &str) -> UserResult<()> {
let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?;
Expand Down Expand Up @@ -90,3 +122,33 @@ fn expiry_to_i64(expiry: u64) -> RouterResult<i64> {
.into_report()
.change_context(ApiErrorResponse::InternalServerError)
}

#[async_trait::async_trait]
pub trait BlackList {
async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
where
A: AppStateInfo + Sync;
}

#[async_trait::async_trait]
impl BlackList for AuthToken {
async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
where
A: AppStateInfo + Sync,
{
Ok(
check_user_in_blacklist(state, &self.user_id, self.exp).await?
|| check_role_in_blacklist(state, &self.role_id, self.exp).await?,
)
}
}

#[async_trait::async_trait]
impl BlackList for UserAuthToken {
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
}
}

0 comments on commit 734327a

Please sign in to comment.