From 734327a957c216511b182151a2f0b27819e7e3bb Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:25:36 +0530 Subject: [PATCH] feat(roles): Add blacklist for roles (#3794) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/router/src/consts.rs | 2 + crates/router/src/core/user_role/role.rs | 4 +- crates/router/src/services/authentication.rs | 17 ++--- .../src/services/authentication/blacklist.rs | 64 ++++++++++++++++++- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 3c3f01dc5f97..72f160990e58 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -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 diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index 7ce72779bbb5..6edbda85bfd6 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -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, }, @@ -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) } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 34153ef6e8f4..455dc97d03e4 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -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; @@ -334,7 +335,7 @@ where state: &A, ) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -499,7 +500,7 @@ where state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -528,7 +529,7 @@ where state: &A, ) -> RouterResult<(UserFromToken, AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -566,7 +567,7 @@ where state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -609,7 +610,7 @@ where state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -659,7 +660,7 @@ where state: &A, ) -> RouterResult<(AuthenticationDataWithUserId, AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -710,7 +711,7 @@ where state: &A, ) -> RouterResult<(UserFromToken, AuthenticationType)> { let payload = parse_jwt_payload::(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()); } @@ -741,7 +742,7 @@ where state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(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()); } diff --git a/crates/router/src/services/authentication/blacklist.rs b/crates/router/src/services/authentication/blacklist.rs index 325ef29bad3e..346e563ee327 100644 --- a/crates/router/src/services/authentication/blacklist.rs +++ b/crates/router/src/services/authentication/blacklist.rs @@ -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, }; @@ -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( state: &A, user_id: &str, @@ -49,6 +66,21 @@ pub async fn check_user_in_blacklist( .map(|timestamp| timestamp.map_or(false, |timestamp| timestamp > token_issued_at)) } +pub async fn check_role_in_blacklist( + state: &A, + role_id: &str, + token_expiry: u64, +) -> RouterResult { + 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::>(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)?; @@ -90,3 +122,33 @@ fn expiry_to_i64(expiry: u64) -> RouterResult { .into_report() .change_context(ApiErrorResponse::InternalServerError) } + +#[async_trait::async_trait] +pub trait BlackList { + async fn check_in_blacklist(&self, state: &A) -> RouterResult + where + A: AppStateInfo + Sync; +} + +#[async_trait::async_trait] +impl BlackList for AuthToken { + async fn check_in_blacklist(&self, state: &A) -> RouterResult + 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(&self, state: &A) -> RouterResult + where + A: AppStateInfo + Sync, + { + check_user_in_blacklist(state, &self.user_id, self.exp).await + } +}