mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-31 01:57:45 +08:00
feat(users): Email JWT blacklist (#3659)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -311,6 +311,10 @@ pub async fn change_password(
|
|||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
let _ = auth::blacklist::insert_user_in_blacklist(&state, user.get_user_id())
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
|
|
||||||
#[cfg(not(feature = "email"))]
|
#[cfg(not(feature = "email"))]
|
||||||
{
|
{
|
||||||
state
|
state
|
||||||
@ -372,11 +376,13 @@ pub async fn reset_password(
|
|||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::ResetPasswordRequest,
|
request: user_api::ResetPasswordRequest,
|
||||||
) -> UserResponse<()> {
|
) -> UserResponse<()> {
|
||||||
let token =
|
let token = request.token.expose();
|
||||||
auth::decode_jwt::<email_types::EmailToken>(request.token.expose().as_str(), &state)
|
let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::LinkInvalid)?;
|
.change_context(UserErrors::LinkInvalid)?;
|
||||||
|
|
||||||
|
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
|
||||||
|
|
||||||
let password = domain::UserPassword::new(request.password)?;
|
let password = domain::UserPassword::new(request.password)?;
|
||||||
|
|
||||||
let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;
|
let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;
|
||||||
@ -384,7 +390,7 @@ pub async fn reset_password(
|
|||||||
let user = state
|
let user = state
|
||||||
.store
|
.store
|
||||||
.update_user_by_email(
|
.update_user_by_email(
|
||||||
token.get_email(),
|
email_token.get_email(),
|
||||||
storage_user::UserUpdate::AccountUpdate {
|
storage_user::UserUpdate::AccountUpdate {
|
||||||
name: None,
|
name: None,
|
||||||
password: Some(hash_password),
|
password: Some(hash_password),
|
||||||
@ -395,7 +401,7 @@ pub async fn reset_password(
|
|||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
if let Some(inviter_merchant_id) = token.get_merchant_id() {
|
if let Some(inviter_merchant_id) = email_token.get_merchant_id() {
|
||||||
let update_status_result = state
|
let update_status_result = state
|
||||||
.store
|
.store
|
||||||
.update_user_role_by_user_id_merchant_id(
|
.update_user_role_by_user_id_merchant_id(
|
||||||
@ -403,13 +409,20 @@ pub async fn reset_password(
|
|||||||
inviter_merchant_id,
|
inviter_merchant_id,
|
||||||
UserRoleUpdate::UpdateStatus {
|
UserRoleUpdate::UpdateStatus {
|
||||||
status: UserStatus::Active,
|
status: UserStatus::Active,
|
||||||
modified_by: user.user_id,
|
modified_by: user.user_id.clone(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
logger::info!(?update_status_result);
|
logger::info!(?update_status_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
|
let _ = auth::blacklist::insert_user_in_blacklist(&state, &user.user_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
|
|
||||||
Ok(ApplicationResponse::StatusOk)
|
Ok(ApplicationResponse::StatusOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,7 +467,13 @@ pub async fn invite_user(
|
|||||||
merchant_id: user_from_token.merchant_id,
|
merchant_id: user_from_token.merchant_id,
|
||||||
role_id: request.role_id,
|
role_id: request.role_id,
|
||||||
org_id: user_from_token.org_id,
|
org_id: user_from_token.org_id,
|
||||||
status: UserStatus::Active,
|
status: {
|
||||||
|
if cfg!(feature = "email") {
|
||||||
|
UserStatus::InvitationSent
|
||||||
|
} else {
|
||||||
|
UserStatus::Active
|
||||||
|
}
|
||||||
|
},
|
||||||
created_by: user_from_token.user_id.clone(),
|
created_by: user_from_token.user_id.clone(),
|
||||||
last_modified_by: user_from_token.user_id,
|
last_modified_by: user_from_token.user_id,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
@ -1050,12 +1069,14 @@ pub async fn verify_email_without_invite_checks(
|
|||||||
state: AppState,
|
state: AppState,
|
||||||
req: user_api::VerifyEmailRequest,
|
req: user_api::VerifyEmailRequest,
|
||||||
) -> UserResponse<user_api::DashboardEntryResponse> {
|
) -> UserResponse<user_api::DashboardEntryResponse> {
|
||||||
let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
|
let token = req.token.clone().expose();
|
||||||
|
let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::LinkInvalid)?;
|
.change_context(UserErrors::LinkInvalid)?;
|
||||||
|
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
|
||||||
let user = state
|
let user = state
|
||||||
.store
|
.store
|
||||||
.find_user_by_email(token.get_email())
|
.find_user_by_email(email_token.get_email())
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
let user = state
|
let user = state
|
||||||
@ -1065,6 +1086,9 @@ pub async fn verify_email_without_invite_checks(
|
|||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
let user_from_db: domain::UserFromStorage = user.into();
|
let user_from_db: domain::UserFromStorage = user.into();
|
||||||
let user_role = user_from_db.get_role_from_db(state.clone()).await?;
|
let user_role = user_from_db.get_role_from_db(state.clone()).await?;
|
||||||
|
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
|
let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
|
||||||
|
|
||||||
Ok(ApplicationResponse::Json(
|
Ok(ApplicationResponse::Json(
|
||||||
@ -1077,13 +1101,16 @@ pub async fn verify_email(
|
|||||||
state: AppState,
|
state: AppState,
|
||||||
req: user_api::VerifyEmailRequest,
|
req: user_api::VerifyEmailRequest,
|
||||||
) -> UserResponse<user_api::SignInResponse> {
|
) -> UserResponse<user_api::SignInResponse> {
|
||||||
let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
|
let token = req.token.clone().expose();
|
||||||
|
let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::LinkInvalid)?;
|
.change_context(UserErrors::LinkInvalid)?;
|
||||||
|
|
||||||
|
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
|
||||||
|
|
||||||
let user = state
|
let user = state
|
||||||
.store
|
.store
|
||||||
.find_user_by_email(token.get_email())
|
.find_user_by_email(email_token.get_email())
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
@ -1115,6 +1142,10 @@ pub async fn verify_email(
|
|||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
|
|
||||||
Ok(ApplicationResponse::Json(
|
Ok(ApplicationResponse::Json(
|
||||||
signin_strategy.get_signin_response(&state).await?,
|
signin_strategy.get_signin_response(&state).await?,
|
||||||
))
|
))
|
||||||
|
|||||||
@ -5,6 +5,8 @@ use common_utils::date_time;
|
|||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use redis_interface::RedisConnectionPool;
|
use redis_interface::RedisConnectionPool;
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
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, USER_BLACKLIST_PREFIX},
|
||||||
core::errors::{ApiErrorResponse, RouterResult},
|
core::errors::{ApiErrorResponse, RouterResult},
|
||||||
@ -47,6 +49,33 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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)?;
|
||||||
|
let blacklist_key = format!("{}{token}", EMAIL_TOKEN_BLACKLIST_PREFIX);
|
||||||
|
let expiry =
|
||||||
|
expiry_to_i64(EMAIL_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
|
||||||
|
redis_conn
|
||||||
|
.set_key_with_expiry(blacklist_key.as_str(), true, expiry)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
pub async fn check_email_token_in_blacklist(state: &AppState, token: &str) -> UserResult<()> {
|
||||||
|
let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?;
|
||||||
|
let blacklist_key = format!("{}{token}", EMAIL_TOKEN_BLACKLIST_PREFIX);
|
||||||
|
let key_exists = redis_conn
|
||||||
|
.exists::<()>(blacklist_key.as_str())
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
if key_exists {
|
||||||
|
return Err(UserErrors::LinkInvalid.into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_redis_connection<A: AppStateInfo>(state: &A) -> RouterResult<Arc<RedisConnectionPool>> {
|
fn get_redis_connection<A: AppStateInfo>(state: &A) -> RouterResult<Arc<RedisConnectionPool>> {
|
||||||
state
|
state
|
||||||
.store()
|
.store()
|
||||||
|
|||||||
Reference in New Issue
Block a user