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:
Apoorv Dixit
2024-05-23 19:10:27 +05:30
committed by GitHub
parent 42e5ef1551
commit f04c6ac030
20 changed files with 140 additions and 27 deletions

View File

@ -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();

View File

@ -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)
}