diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 002452a8a3..54fbecc64f 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -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::(request.token.expose().as_str(), &state) - .await - .change_context(UserErrors::LinkInvalid)?; + let token = request.token.expose(); + let email_token = auth::decode_jwt::(&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 { - let token = auth::decode_jwt::(&req.token.clone().expose(), &state) + let token = req.token.clone().expose(); + let email_token = auth::decode_jwt::(&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 { - let token = auth::decode_jwt::(&req.token.clone().expose(), &state) + let token = req.token.clone().expose(); + let email_token = auth::decode_jwt::(&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?, )) diff --git a/crates/router/src/services/authentication/blacklist.rs b/crates/router/src/services/authentication/blacklist.rs index 6fab28433b..325ef29bad 100644 --- a/crates/router/src/services/authentication/blacklist.rs +++ b/crates/router/src/services/authentication/blacklist.rs @@ -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( .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(state: &A) -> RouterResult> { state .store()