mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 13:30:39 +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:
@ -72,6 +72,8 @@ pub enum UserErrors {
|
||||
InvalidTotp,
|
||||
#[error("TotpRequired")]
|
||||
TotpRequired,
|
||||
#[error("InvalidRecoveryCode")]
|
||||
InvalidRecoveryCode,
|
||||
#[error("TwoFactorAuthRequired")]
|
||||
TwoFactorAuthRequired,
|
||||
#[error("TwoFactorAuthNotSetup")]
|
||||
@ -188,12 +190,15 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::TotpRequired => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None))
|
||||
}
|
||||
Self::TwoFactorAuthRequired => {
|
||||
Self::InvalidRecoveryCode => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 39, self.get_error_message(), None))
|
||||
}
|
||||
Self::TwoFactorAuthNotSetup => {
|
||||
Self::TwoFactorAuthRequired => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 40, self.get_error_message(), None))
|
||||
}
|
||||
Self::TwoFactorAuthNotSetup => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 41, self.get_error_message(), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,6 +238,7 @@ impl UserErrors {
|
||||
Self::TotpNotSetup => "TOTP not setup",
|
||||
Self::InvalidTotp => "Invalid TOTP",
|
||||
Self::TotpRequired => "TOTP required",
|
||||
Self::InvalidRecoveryCode => "Invalid Recovery Code",
|
||||
Self::TwoFactorAuthRequired => "Two factor auth required",
|
||||
Self::TwoFactorAuthNotSetup => "Two factor auth not setup",
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ pub async fn signin(
|
||||
})?
|
||||
.into();
|
||||
|
||||
user_from_db.compare_password(request.password)?;
|
||||
user_from_db.compare_password(&request.password)?;
|
||||
|
||||
let signin_strategy =
|
||||
if let Some(preferred_merchant_id) = user_from_db.get_preferred_merchant_id() {
|
||||
@ -217,7 +217,7 @@ pub async fn signin_token_only_flow(
|
||||
.to_not_found_response(UserErrors::InvalidCredentials)?
|
||||
.into();
|
||||
|
||||
user_from_db.compare_password(request.password)?;
|
||||
user_from_db.compare_password(&request.password)?;
|
||||
|
||||
let next_flow =
|
||||
domain::NextFlow::from_origin(domain::Origin::SignIn, user_from_db.clone(), &state).await?;
|
||||
@ -341,7 +341,7 @@ pub async fn change_password(
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.into();
|
||||
|
||||
user.compare_password(request.old_password.to_owned())
|
||||
user.compare_password(&request.old_password)
|
||||
.change_context(UserErrors::InvalidOldPassword)?;
|
||||
|
||||
if request.old_password == request.new_password {
|
||||
@ -439,7 +439,7 @@ pub async fn rotate_password(
|
||||
let password = domain::UserPassword::new(request.password.to_owned())?;
|
||||
let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;
|
||||
|
||||
if user.compare_password(request.password).is_ok() {
|
||||
if user.compare_password(&request.password).is_ok() {
|
||||
return Err(UserErrors::ChangePasswordError.into());
|
||||
}
|
||||
|
||||
@ -1766,6 +1766,51 @@ pub async fn generate_recovery_codes(
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn verify_recovery_code(
|
||||
state: AppState,
|
||||
user_token: auth::UserFromSinglePurposeToken,
|
||||
req: user_api::VerifyRecoveryCodeRequest,
|
||||
) -> UserResponse<user_api::TokenResponse> {
|
||||
let user_from_db: domain::UserFromStorage = state
|
||||
.store
|
||||
.find_user_by_id(&user_token.user_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.into();
|
||||
|
||||
if user_from_db.get_totp_status() != TotpStatus::Set {
|
||||
return Err(UserErrors::TwoFactorAuthNotSetup.into());
|
||||
}
|
||||
|
||||
let mut recovery_codes = user_from_db
|
||||
.get_recovery_codes()
|
||||
.ok_or(UserErrors::InternalServerError)?;
|
||||
|
||||
let matching_index = utils::user::password::get_index_for_correct_recovery_code(
|
||||
&req.recovery_code,
|
||||
&recovery_codes,
|
||||
)?
|
||||
.ok_or(UserErrors::InvalidRecoveryCode)?;
|
||||
|
||||
tfa_utils::insert_recovery_code_in_redis(&state, user_from_db.get_user_id()).await?;
|
||||
let _ = recovery_codes.remove(matching_index);
|
||||
|
||||
state
|
||||
.store
|
||||
.update_user_by_user_id(
|
||||
user_from_db.get_user_id(),
|
||||
storage_user::UserUpdate::TotpUpdate {
|
||||
totp_status: None,
|
||||
totp_secret: None,
|
||||
totp_recovery_codes: Some(recovery_codes),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn terminate_two_factor_auth(
|
||||
state: AppState,
|
||||
user_token: auth::UserFromSinglePurposeToken,
|
||||
|
||||
Reference in New Issue
Block a user