mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
feat(users): add support to verify 2FA using recovery code (#4737)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -7,7 +7,7 @@ use argon2::{
|
||||
};
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::ResultExt;
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
|
||||
use crate::core::errors::UserErrors;
|
||||
@ -25,13 +25,13 @@ pub fn generate_password_hash(
|
||||
}
|
||||
|
||||
pub fn is_correct_password(
|
||||
candidate: Secret<String>,
|
||||
password: Secret<String>,
|
||||
candidate: &Secret<String>,
|
||||
password: &Secret<String>,
|
||||
) -> CustomResult<bool, UserErrors> {
|
||||
let password = password.expose();
|
||||
let password = password.peek();
|
||||
let parsed_hash =
|
||||
PasswordHash::new(&password).change_context(UserErrors::InternalServerError)?;
|
||||
let result = Argon2::default().verify_password(candidate.expose().as_bytes(), &parsed_hash);
|
||||
PasswordHash::new(password).change_context(UserErrors::InternalServerError)?;
|
||||
let result = Argon2::default().verify_password(candidate.peek().as_bytes(), &parsed_hash);
|
||||
match result {
|
||||
Ok(_) => Ok(true),
|
||||
Err(argon2Err::Password) => Ok(false),
|
||||
@ -40,6 +40,19 @@ pub fn is_correct_password(
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
}
|
||||
|
||||
pub fn get_index_for_correct_recovery_code(
|
||||
candidate: &Secret<String>,
|
||||
recovery_codes: &[Secret<String>],
|
||||
) -> CustomResult<Option<usize>, UserErrors> {
|
||||
for (index, recovery_code) in recovery_codes.iter().enumerate() {
|
||||
let is_match = is_correct_password(candidate, recovery_code)?;
|
||||
if is_match {
|
||||
return Ok(Some(index));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_temp_password() -> Secret<String> {
|
||||
let uuid_pass = uuid::Uuid::new_v4().to_string();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
@ -45,7 +45,7 @@ pub async fn check_totp_in_redis(state: &AppState, user_id: &str) -> UserResult<
|
||||
|
||||
pub async fn check_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult<bool> {
|
||||
let redis_conn = get_redis_connection(state)?;
|
||||
let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODES_PREFIX, user_id);
|
||||
let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id);
|
||||
redis_conn
|
||||
.exists::<()>(&key)
|
||||
.await
|
||||
@ -59,3 +59,16 @@ fn get_redis_connection(state: &AppState) -> UserResult<Arc<RedisConnectionPool>
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to get redis connection")
|
||||
}
|
||||
|
||||
pub async fn insert_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult<()> {
|
||||
let redis_conn = get_redis_connection(state)?;
|
||||
let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id);
|
||||
redis_conn
|
||||
.set_key_with_expiry(
|
||||
key.as_str(),
|
||||
common_utils::date_time::now_unix_timestamp(),
|
||||
state.conf.user.two_factor_auth_expiry_in_secs,
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user