From 50a3feb4460ea421f40659b7b392193525bc9426 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Wed, 22 May 2024 17:36:42 +0530 Subject: [PATCH 1/6] feat(users):Create terminate 2fa API --- crates/api_models/src/user.rs | 5 ++ crates/router/src/consts/user.rs | 1 + crates/router/src/core/user.rs | 50 ++++++++++++++++++- crates/router/src/routes/app.rs | 3 +- crates/router/src/routes/lock_utils.rs | 1 + crates/router/src/routes/user.rs | 20 ++++++++ .../router/src/utils/user/two_factor_auth.rs | 9 ++++ crates/router_env/src/logger/types.rs | 2 + 8 files changed, 89 insertions(+), 2 deletions(-) diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 7864117856e6..fcb1083fa88a 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -224,6 +224,11 @@ pub struct TokenOnlyQueryParam { pub token_only: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct Skip2faQueryParam { + pub skip_2fa: Option, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct TokenResponse { pub token: Secret, diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 33642205d57f..8ca05efd9145 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -14,3 +14,4 @@ pub const MAX_PASSWORD_LENGTH: usize = 70; pub const MIN_PASSWORD_LENGTH: usize = 8; pub const TOTP_PREFIX: &str = "TOTP_"; +pub const RECOVERY_CODES_PREFIX: &str = "RC_"; diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 7a0ef683e7a6..e6d76bd0e924 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -15,6 +15,7 @@ use router_env::env; use router_env::logger; #[cfg(not(feature = "email"))] use user_api::dashboard_metadata::SetMetaDataRequest; +use utils::user::two_factor_auth::{check_access_code_in_redis, check_totp_in_redis}; use super::errors::{StorageErrorExt, UserErrors, UserResponse, UserResult}; #[cfg(feature = "email")] @@ -26,6 +27,7 @@ use crate::{ types::{domain, transformers::ForeignInto}, utils, }; + pub mod dashboard_metadata; #[cfg(feature = "dummy_connector")] pub mod sample_data; @@ -1739,7 +1741,7 @@ pub async fn generate_recovery_codes( state: AppState, user_token: auth::UserFromSinglePurposeToken, ) -> UserResponse { - if !utils::user::two_factor_auth::check_totp_in_redis(&state, &user_token.user_id).await? { + if !check_totp_in_redis(&state, &user_token.user_id).await? { return Err(UserErrors::TotpRequired.into()); } @@ -1766,3 +1768,49 @@ pub async fn generate_recovery_codes( recovery_codes: recovery_codes.into_inner(), })) } + +pub async fn terminate_2fa( + state: AppState, + user_token: auth::UserFromSinglePurposeToken, + skip_2fa: Option, +) -> UserResponse { + let user_from_db: domain::UserFromStorage = state + .store + .find_user_by_id(&user_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + if skip_2fa.is_none() { + if !(check_totp_in_redis(&state, &user_token.user_id).await? + || check_access_code_in_redis(&state, &user_token.user_id).await?) + { + return Err(UserErrors::TotpRequired.into()); + } + + state + .store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::TotpUpdate { + totp_status: Some(TotpStatus::Set), + totp_secret: None, + totp_recovery_codes: None, + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + } + + let current_flow = domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::TOTP.into())?; + let next_flow = current_flow.next(user_from_db, &state).await?; + let token = next_flow.get_token(&state).await?; + + auth::cookies::set_cookie_response( + user_api::TokenResponse { + token: token.clone(), + token_type: next_flow.get_flow().into(), + }, + token, + ) +} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0e152ec32c1d..0db1f285efbd 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1215,7 +1215,8 @@ impl User { .service( web::resource("/recovery_codes/generate") .route(web::get().to(generate_recovery_codes)), - ); + ) + .service(web::resource("/2fa/terminate").route(web::get().to(terminate_2fa))); #[cfg(feature = "email")] { diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 706726979dc1..18b8b409aafa 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -215,6 +215,7 @@ impl From for ApiIdentifier { | Flow::UpdateUserAccountDetails | Flow::TotpBegin | Flow::TotpVerify + | Flow::Terminate2fa | Flow::GenerateRecoveryCodes => Self::User, Flow::ListRoles diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index f542c446e498..0e3c26ed17ee 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -679,3 +679,23 @@ pub async fn generate_recovery_codes(state: web::Data, req: HttpReques )) .await } + +pub async fn terminate_2fa( + state: web::Data, + req: HttpRequest, + query: web::Query, +) -> HttpResponse { + let flow = Flow::Terminate2fa; + let skip_2fa = query.into_inner().skip_2fa; + + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + (), + |state, user, _, _| user_core::terminate_2fa(state, user, skip_2fa), + &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/utils/user/two_factor_auth.rs b/crates/router/src/utils/user/two_factor_auth.rs index 62bcf2f7eb15..9e76f080b223 100644 --- a/crates/router/src/utils/user/two_factor_auth.rs +++ b/crates/router/src/utils/user/two_factor_auth.rs @@ -43,6 +43,15 @@ pub async fn check_totp_in_redis(state: &AppState, user_id: &str) -> UserResult< .change_context(UserErrors::InternalServerError) } +pub async fn check_access_code_in_redis(state: &AppState, user_id: &str) -> UserResult { + let redis_conn = get_redis_connection(state)?; + let key = format!("{}{}", consts::user::RECOVERY_CODES_PREFIX, user_id); + redis_conn + .exists::<()>(&key) + .await + .change_context(UserErrors::InternalServerError) +} + fn get_redis_connection(state: &AppState) -> UserResult> { state .store diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 0e35aba31741..8aee2a2c9ddb 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -408,6 +408,8 @@ pub enum Flow { TotpVerify, /// Generate or Regenerate recovery codes GenerateRecoveryCodes, + // Terminate 2factor authentication + Terminate2fa, /// List initial webhook delivery attempts WebhookEventInitialDeliveryAttemptList, /// List delivery attempts for a webhook event From d6a522998405dae5b6fe552817e53653754367a6 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Wed, 22 May 2024 19:20:36 +0530 Subject: [PATCH 2/6] fix: comments addressed --- crates/router/src/consts/user.rs | 2 +- crates/router/src/core/errors/user.rs | 6 ++++++ crates/router/src/core/user.rs | 13 ++++++------- crates/router/src/utils/user/two_factor_auth.rs | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 8ca05efd9145..7b61a834f603 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -14,4 +14,4 @@ pub const MAX_PASSWORD_LENGTH: usize = 70; pub const MIN_PASSWORD_LENGTH: usize = 8; pub const TOTP_PREFIX: &str = "TOTP_"; -pub const RECOVERY_CODES_PREFIX: &str = "RC_"; +pub const REDIS_RECOVERY_CODES_PREFIX: &str = "RC_"; diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index 2d1c196f5df7..3d43f4d65dd9 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -72,6 +72,8 @@ pub enum UserErrors { InvalidTotp, #[error("TotpRequired")] TotpRequired, + #[error("TwoFARequired")] + TwoFARequired, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -184,6 +186,9 @@ impl common_utils::errors::ErrorSwitch { AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None)) } + Self::TwoFARequired => { + AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None)) + } } } } @@ -223,6 +228,7 @@ impl UserErrors { Self::TotpNotSetup => "TOTP not setup", Self::InvalidTotp => "Invalid TOTP", Self::TotpRequired => "TOTP required", + Self::TwoFARequired => "2fa required", } } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index e6d76bd0e924..c11f493506d9 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -15,7 +15,6 @@ use router_env::env; use router_env::logger; #[cfg(not(feature = "email"))] use user_api::dashboard_metadata::SetMetaDataRequest; -use utils::user::two_factor_auth::{check_access_code_in_redis, check_totp_in_redis}; use super::errors::{StorageErrorExt, UserErrors, UserResponse, UserResult}; #[cfg(feature = "email")] @@ -25,7 +24,7 @@ use crate::{ routes::{app::ReqState, AppState}, services::{authentication as auth, authorization::roles, ApplicationResponse}, types::{domain, transformers::ForeignInto}, - utils, + utils::{self, user::two_factor_auth as tfa_utils}, }; pub mod dashboard_metadata; @@ -1741,7 +1740,7 @@ pub async fn generate_recovery_codes( state: AppState, user_token: auth::UserFromSinglePurposeToken, ) -> UserResponse { - if !check_totp_in_redis(&state, &user_token.user_id).await? { + if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? { return Err(UserErrors::TotpRequired.into()); } @@ -1781,11 +1780,11 @@ pub async fn terminate_2fa( .change_context(UserErrors::InternalServerError)? .into(); - if skip_2fa.is_none() { - if !(check_totp_in_redis(&state, &user_token.user_id).await? - || check_access_code_in_redis(&state, &user_token.user_id).await?) + if !(skip_2fa.unwrap_or(false)) { + if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? + && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await? { - return Err(UserErrors::TotpRequired.into()); + return Err(UserErrors::TwoFARequired.into()); } state diff --git a/crates/router/src/utils/user/two_factor_auth.rs b/crates/router/src/utils/user/two_factor_auth.rs index 9e76f080b223..479c346b6e53 100644 --- a/crates/router/src/utils/user/two_factor_auth.rs +++ b/crates/router/src/utils/user/two_factor_auth.rs @@ -43,9 +43,9 @@ pub async fn check_totp_in_redis(state: &AppState, user_id: &str) -> UserResult< .change_context(UserErrors::InternalServerError) } -pub async fn check_access_code_in_redis(state: &AppState, user_id: &str) -> UserResult { +pub async fn check_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult { let redis_conn = get_redis_connection(state)?; - let key = format!("{}{}", consts::user::RECOVERY_CODES_PREFIX, user_id); + let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODES_PREFIX, user_id); redis_conn .exists::<()>(&key) .await From ab3008e47c0224fd2807b2b2bce43b7a97d2e2b5 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Wed, 22 May 2024 22:24:32 +0530 Subject: [PATCH 3/6] fix: code refactoring --- crates/router/src/core/errors/user.rs | 2 +- crates/router/src/core/user.rs | 28 ++++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index 3d43f4d65dd9..0430fd2857c0 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -187,7 +187,7 @@ impl common_utils::errors::ErrorSwitch { - AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None)) + AER::BadRequest(ApiError::new(sub_code, 39, self.get_error_message(), None)) } } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index c11f493506d9..23fda639aae1 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1780,25 +1780,27 @@ pub async fn terminate_2fa( .change_context(UserErrors::InternalServerError)? .into(); - if !(skip_2fa.unwrap_or(false)) { + if !skip_2fa.unwrap_or(false) { if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await? { return Err(UserErrors::TwoFARequired.into()); } - state - .store - .update_user_by_user_id( - user_from_db.get_user_id(), - storage_user::UserUpdate::TotpUpdate { - totp_status: Some(TotpStatus::Set), - totp_secret: None, - totp_recovery_codes: None, - }, - ) - .await - .change_context(UserErrors::InternalServerError)?; + if user_from_db.get_totp_status() != TotpStatus::Set { + state + .store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::TotpUpdate { + totp_status: Some(TotpStatus::Set), + totp_secret: None, + totp_recovery_codes: None, + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + } } let current_flow = domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::TOTP.into())?; From ccf26632510b9e3f10a9c523ce6f0e4f97845c25 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Thu, 23 May 2024 12:36:36 +0530 Subject: [PATCH 4/6] fix: import changes --- crates/router/src/core/user.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 23fda639aae1..d57933e9c6d9 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1632,7 +1632,7 @@ pub async fn begin_totp( })); } - let totp = utils::user::two_factor_auth::generate_default_totp(user_from_db.get_email(), None)?; + let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), None)?; let recovery_codes = domain::RecoveryCodes::generate_new(); let key_store = user_from_db.get_or_create_key_store(&state).await?; @@ -1694,10 +1694,8 @@ pub async fn verify_totp( .await? .ok_or(UserErrors::InternalServerError)?; - let totp = utils::user::two_factor_auth::generate_default_totp( - user_from_db.get_email(), - Some(user_totp_secret), - )?; + let totp = + tfa_utils::generate_default_totp(user_from_db.get_email(), Some(user_totp_secret))?; if totp .generate_current() From 7a8ea2bf9c6cdddc13d16ee7e1be194e76ef1bf1 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Thu, 23 May 2024 14:44:38 +0530 Subject: [PATCH 5/6] refactor: changed function names --- crates/api_models/src/user.rs | 4 ++-- crates/router/src/core/errors/user.rs | 8 ++++---- crates/router/src/core/user.rs | 8 ++++---- crates/router/src/routes/app.rs | 4 +++- crates/router/src/routes/lock_utils.rs | 2 +- crates/router/src/routes/user.rs | 10 +++++----- crates/router_env/src/logger/types.rs | 4 ++-- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index fcb1083fa88a..6b2748ca3442 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -225,8 +225,8 @@ pub struct TokenOnlyQueryParam { } #[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct Skip2faQueryParam { - pub skip_2fa: Option, +pub struct SkipTwoFactorAuthQueryParam { + pub skip_two_factor_auth: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index 0430fd2857c0..e5d8524dd6a7 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -72,8 +72,8 @@ pub enum UserErrors { InvalidTotp, #[error("TotpRequired")] TotpRequired, - #[error("TwoFARequired")] - TwoFARequired, + #[error("TwoFactorAuthRequired")] + TwoFactorAuthRequired, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -186,7 +186,7 @@ impl common_utils::errors::ErrorSwitch { AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None)) } - Self::TwoFARequired => { + Self::TwoFactorAuthRequired => { AER::BadRequest(ApiError::new(sub_code, 39, self.get_error_message(), None)) } } @@ -228,7 +228,7 @@ impl UserErrors { Self::TotpNotSetup => "TOTP not setup", Self::InvalidTotp => "Invalid TOTP", Self::TotpRequired => "TOTP required", - Self::TwoFARequired => "2fa required", + Self::TwoFactorAuthRequired => "Two factor auth required", } } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index d57933e9c6d9..e824213a9ccf 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1766,10 +1766,10 @@ pub async fn generate_recovery_codes( })) } -pub async fn terminate_2fa( +pub async fn terminate_two_factor_auth( state: AppState, user_token: auth::UserFromSinglePurposeToken, - skip_2fa: Option, + skip_two_factor_auth: Option, ) -> UserResponse { let user_from_db: domain::UserFromStorage = state .store @@ -1778,11 +1778,11 @@ pub async fn terminate_2fa( .change_context(UserErrors::InternalServerError)? .into(); - if !skip_2fa.unwrap_or(false) { + if !skip_two_factor_auth.unwrap_or(false) { if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await? { - return Err(UserErrors::TwoFARequired.into()); + return Err(UserErrors::TwoFactorAuthRequired.into()); } if user_from_db.get_totp_status() != TotpStatus::Set { diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0db1f285efbd..cf2e986c325f 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1216,7 +1216,9 @@ impl User { web::resource("/recovery_codes/generate") .route(web::get().to(generate_recovery_codes)), ) - .service(web::resource("/2fa/terminate").route(web::get().to(terminate_2fa))); + .service( + web::resource("/2fa/terminate").route(web::get().to(terminate_two_factor_auth)), + ); #[cfg(feature = "email")] { diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 18b8b409aafa..97d92d49911d 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -215,7 +215,7 @@ impl From for ApiIdentifier { | Flow::UpdateUserAccountDetails | Flow::TotpBegin | Flow::TotpVerify - | Flow::Terminate2fa + | Flow::TerminateTwoFactorAuth | Flow::GenerateRecoveryCodes => Self::User, Flow::ListRoles diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 0e3c26ed17ee..5e68a93f67c4 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -680,20 +680,20 @@ pub async fn generate_recovery_codes(state: web::Data, req: HttpReques .await } -pub async fn terminate_2fa( +pub async fn terminate_two_factor_auth( state: web::Data, req: HttpRequest, - query: web::Query, + query: web::Query, ) -> HttpResponse { - let flow = Flow::Terminate2fa; - let skip_2fa = query.into_inner().skip_2fa; + let flow = Flow::TerminateTwoFactorAuth; + let skip_two_factor_auth = query.into_inner().skip_two_factor_auth; Box::pin(api::server_wrap( flow, state.clone(), &req, (), - |state, user, _, _| user_core::terminate_2fa(state, user, skip_2fa), + |state, user, _, _| user_core::terminate_two_factor_auth(state, user, skip_two_factor_auth), &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), api_locking::LockAction::NotApplicable, )) diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 8aee2a2c9ddb..be071ffefc37 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -408,8 +408,8 @@ pub enum Flow { TotpVerify, /// Generate or Regenerate recovery codes GenerateRecoveryCodes, - // Terminate 2factor authentication - Terminate2fa, + // Terminate two factor authentication + TerminateTwoFactorAuth, /// List initial webhook delivery attempts WebhookEventInitialDeliveryAttemptList, /// List delivery attempts for a webhook event From 36a14b4cd6952c8fa94cd13ece1d65a86ae06b10 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Thu, 23 May 2024 16:16:05 +0530 Subject: [PATCH 6/6] fix: added condition when recovery code is empty --- crates/router/src/core/errors/user.rs | 6 ++++++ crates/router/src/core/user.rs | 8 ++++++-- crates/router/src/routes/user.rs | 2 +- crates/router/src/types/domain/user.rs | 4 ++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index e5d8524dd6a7..cda850787184 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -74,6 +74,8 @@ pub enum UserErrors { TotpRequired, #[error("TwoFactorAuthRequired")] TwoFactorAuthRequired, + #[error("TwoFactorAuthNotSetup")] + TwoFactorAuthNotSetup, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -189,6 +191,9 @@ impl common_utils::errors::ErrorSwitch { AER::BadRequest(ApiError::new(sub_code, 39, self.get_error_message(), None)) } + Self::TwoFactorAuthNotSetup => { + AER::BadRequest(ApiError::new(sub_code, 40, self.get_error_message(), None)) + } } } } @@ -229,6 +234,7 @@ impl UserErrors { Self::InvalidTotp => "Invalid TOTP", Self::TotpRequired => "TOTP required", Self::TwoFactorAuthRequired => "Two factor auth required", + Self::TwoFactorAuthNotSetup => "Two factor auth not setup", } } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index e824213a9ccf..705c12907ff5 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1769,7 +1769,7 @@ pub async fn generate_recovery_codes( pub async fn terminate_two_factor_auth( state: AppState, user_token: auth::UserFromSinglePurposeToken, - skip_two_factor_auth: Option, + skip_two_factor_auth: bool, ) -> UserResponse { let user_from_db: domain::UserFromStorage = state .store @@ -1778,13 +1778,17 @@ pub async fn terminate_two_factor_auth( .change_context(UserErrors::InternalServerError)? .into(); - if !skip_two_factor_auth.unwrap_or(false) { + if !skip_two_factor_auth { if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await? { return Err(UserErrors::TwoFactorAuthRequired.into()); } + if user_from_db.get_recovery_codes().is_none() { + return Err(UserErrors::TwoFactorAuthNotSetup.into()); + } + if user_from_db.get_totp_status() != TotpStatus::Set { state .store diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 5e68a93f67c4..2019855114a3 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -686,7 +686,7 @@ pub async fn terminate_two_factor_auth( query: web::Query, ) -> HttpResponse { let flow = Flow::TerminateTwoFactorAuth; - let skip_two_factor_auth = query.into_inner().skip_two_factor_auth; + let skip_two_factor_auth = query.into_inner().skip_two_factor_auth.unwrap_or(false); Box::pin(api::server_wrap( flow, diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 051e6ccf38ed..0704e51e7813 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -930,6 +930,10 @@ impl UserFromStorage { self.0.totp_status } + pub fn get_recovery_codes(&self) -> Option>> { + self.0.totp_recovery_codes.clone() + } + pub async fn decrypt_and_get_totp_secret( &self, state: &AppState,