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:
Rachit Naithani
2024-02-15 21:35:47 +05:30
committed by GitHub
parent 2f473dd4e7
commit a9e3d74cc1
2 changed files with 72 additions and 12 deletions

View File

@ -311,6 +311,10 @@ pub async fn change_password(
.await
.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"))]
{
state
@ -372,10 +376,12 @@ pub async fn reset_password(
state: AppState,
request: user_api::ResetPasswordRequest,
) -> UserResponse<()> {
let token =
auth::decode_jwt::<email_types::EmailToken>(request.token.expose().as_str(), &state)
.await
.change_context(UserErrors::LinkInvalid)?;
let token = request.token.expose();
let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
.await
.change_context(UserErrors::LinkInvalid)?;
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
let password = domain::UserPassword::new(request.password)?;
@ -384,7 +390,7 @@ pub async fn reset_password(
let user = state
.store
.update_user_by_email(
token.get_email(),
email_token.get_email(),
storage_user::UserUpdate::AccountUpdate {
name: None,
password: Some(hash_password),
@ -395,7 +401,7 @@ pub async fn reset_password(
.await
.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
.store
.update_user_role_by_user_id_merchant_id(
@ -403,13 +409,20 @@ pub async fn reset_password(
inviter_merchant_id,
UserRoleUpdate::UpdateStatus {
status: UserStatus::Active,
modified_by: user.user_id,
modified_by: user.user_id.clone(),
},
)
.await;
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)
}
@ -454,7 +467,13 @@ pub async fn invite_user(
merchant_id: user_from_token.merchant_id,
role_id: request.role_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(),
last_modified_by: user_from_token.user_id,
created_at: now,
@ -1050,12 +1069,14 @@ pub async fn verify_email_without_invite_checks(
state: AppState,
req: user_api::VerifyEmailRequest,
) -> 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
.change_context(UserErrors::LinkInvalid)?;
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
let user = state
.store
.find_user_by_email(token.get_email())
.find_user_by_email(email_token.get_email())
.await
.change_context(UserErrors::InternalServerError)?;
let user = state
@ -1065,6 +1086,9 @@ pub async fn verify_email_without_invite_checks(
.change_context(UserErrors::InternalServerError)?;
let user_from_db: domain::UserFromStorage = user.into();
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?;
Ok(ApplicationResponse::Json(
@ -1077,13 +1101,16 @@ pub async fn verify_email(
state: AppState,
req: user_api::VerifyEmailRequest,
) -> 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
.change_context(UserErrors::LinkInvalid)?;
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
let user = state
.store
.find_user_by_email(token.get_email())
.find_user_by_email(email_token.get_email())
.await
.change_context(UserErrors::InternalServerError)?;
@ -1115,6 +1142,10 @@ pub async fn verify_email(
.await?
};
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
.await
.map_err(|e| logger::error!(?e));
Ok(ApplicationResponse::Json(
signin_strategy.get_signin_response(&state).await?,
))

View File

@ -5,6 +5,8 @@ use common_utils::date_time;
use error_stack::{IntoReport, ResultExt};
use redis_interface::RedisConnectionPool;
#[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},
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))
}
#[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>> {
state
.store()