mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
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:
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user