feat(roles): Add blacklist for roles (#3794)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Mani Chandra
2024-02-23 16:25:36 +05:30
committed by GitHub
parent 2c95dcd197
commit 734327a957
4 changed files with 77 additions and 10 deletions

View File

@ -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 USER_BLACKLIST_PREFIX: &str = "BU_";
pub const ROLE_BLACKLIST_PREFIX: &str = "BR_";
#[cfg(feature = "email")] #[cfg(feature = "email")]
pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day

View File

@ -12,7 +12,7 @@ use crate::{
core::errors::{StorageErrorExt, UserErrors, UserResponse}, core::errors::{StorageErrorExt, UserErrors, UserResponse},
routes::AppState, routes::AppState,
services::{ services::{
authentication::UserFromToken, authentication::{blacklist, UserFromToken},
authorization::roles::{self, predefined_roles::PREDEFINED_ROLES}, authorization::roles::{self, predefined_roles::PREDEFINED_ROLES},
ApplicationResponse, ApplicationResponse,
}, },
@ -219,5 +219,7 @@ pub async fn update_role(
.await .await
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?; .to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
blacklist::insert_role_in_blacklist(&state, role_id).await?;
Ok(ApplicationResponse::StatusOk) Ok(ApplicationResponse::StatusOk)
} }

View File

@ -13,6 +13,7 @@ use masking::ExposeInterface;
use masking::{PeekInterface, StrongSecret}; use masking::{PeekInterface, StrongSecret};
use serde::Serialize; use serde::Serialize;
use self::blacklist::BlackList;
use super::authorization::{self, permissions::Permission}; use super::authorization::{self, permissions::Permission};
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
use super::jwt; use super::jwt;
@ -334,7 +335,7 @@ where
state: &A, state: &A,
) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> { ) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> {
let payload = parse_jwt_payload::<A, UserAuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -499,7 +500,7 @@ where
state: &A, state: &A,
) -> RouterResult<((), AuthenticationType)> { ) -> RouterResult<((), AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -528,7 +529,7 @@ where
state: &A, state: &A,
) -> RouterResult<(UserFromToken, AuthenticationType)> { ) -> RouterResult<(UserFromToken, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -566,7 +567,7 @@ where
state: &A, state: &A,
) -> RouterResult<((), AuthenticationType)> { ) -> RouterResult<((), AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -609,7 +610,7 @@ where
state: &A, state: &A,
) -> RouterResult<(AuthenticationData, AuthenticationType)> { ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -659,7 +660,7 @@ where
state: &A, state: &A,
) -> RouterResult<(AuthenticationDataWithUserId, AuthenticationType)> { ) -> RouterResult<(AuthenticationDataWithUserId, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -710,7 +711,7 @@ where
state: &A, state: &A,
) -> RouterResult<(UserFromToken, AuthenticationType)> { ) -> RouterResult<(UserFromToken, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }
@ -741,7 +742,7 @@ where
state: &A, state: &A,
) -> RouterResult<((), AuthenticationType)> { ) -> RouterResult<((), AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?; 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()); return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
} }

View File

@ -5,10 +5,11 @@ use common_utils::date_time;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use redis_interface::RedisConnectionPool; use redis_interface::RedisConnectionPool;
use super::{AuthToken, UserAuthToken};
#[cfg(feature = "email")] #[cfg(feature = "email")]
use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS}; use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS};
use crate::{ 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}, core::errors::{ApiErrorResponse, RouterResult},
routes::app::AppStateInfo, routes::app::AppStateInfo,
}; };
@ -34,6 +35,22 @@ pub async fn insert_user_in_blacklist(state: &AppState, user_id: &str) -> UserRe
.change_context(UserErrors::InternalServerError) .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>( pub async fn check_user_in_blacklist<A: AppStateInfo>(
state: &A, state: &A,
user_id: &str, user_id: &str,
@ -49,6 +66,21 @@ pub async fn check_user_in_blacklist<A: AppStateInfo>(
.map(|timestamp| timestamp.map_or(false, |timestamp| timestamp > token_issued_at)) .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")] #[cfg(feature = "email")]
pub async fn insert_email_token_in_blacklist(state: &AppState, token: &str) -> UserResult<()> { pub async fn insert_email_token_in_blacklist(state: &AppState, token: &str) -> UserResult<()> {
let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?; let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?;
@ -90,3 +122,33 @@ fn expiry_to_i64(expiry: u64) -> RouterResult<i64> {
.into_report() .into_report()
.change_context(ApiErrorResponse::InternalServerError) .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
}
}