mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(users): Add redis in Begin and Verify TOTP and create a new API that updates TOTP (#4765)
This commit is contained in:
		| @ -255,12 +255,11 @@ pub struct BeginTotpResponse { | |||||||
| pub struct TotpSecret { | pub struct TotpSecret { | ||||||
|     pub secret: Secret<String>, |     pub secret: Secret<String>, | ||||||
|     pub totp_url: Secret<String>, |     pub totp_url: Secret<String>, | ||||||
|     pub recovery_codes: Vec<Secret<String>>, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, serde::Deserialize, serde::Serialize)] | #[derive(Debug, serde::Deserialize, serde::Serialize)] | ||||||
| pub struct VerifyTotpRequest { | pub struct VerifyTotpRequest { | ||||||
|     pub totp: Option<Secret<String>>, |     pub totp: Secret<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, serde::Deserialize, serde::Serialize)] | #[derive(Debug, serde::Deserialize, serde::Serialize)] | ||||||
|  | |||||||
| @ -13,5 +13,7 @@ pub const TOTP_TOLERANCE: u8 = 1; | |||||||
| pub const MAX_PASSWORD_LENGTH: usize = 70; | pub const MAX_PASSWORD_LENGTH: usize = 70; | ||||||
| pub const MIN_PASSWORD_LENGTH: usize = 8; | pub const MIN_PASSWORD_LENGTH: usize = 8; | ||||||
|  |  | ||||||
| pub const TOTP_PREFIX: &str = "TOTP_"; | pub const REDIS_TOTP_PREFIX: &str = "TOTP_"; | ||||||
| pub const REDIS_RECOVERY_CODE_PREFIX: &str = "RC_"; | pub const REDIS_RECOVERY_CODE_PREFIX: &str = "RC_"; | ||||||
|  | pub const REDIS_TOTP_SECRET_PREFIX: &str = "TOTP_SEC_"; | ||||||
|  | pub const REDIS_TOTP_SECRET_TTL_IN_SECS: i64 = 5 * 60; // 5 minutes | ||||||
|  | |||||||
| @ -78,6 +78,8 @@ pub enum UserErrors { | |||||||
|     TwoFactorAuthRequired, |     TwoFactorAuthRequired, | ||||||
|     #[error("TwoFactorAuthNotSetup")] |     #[error("TwoFactorAuthNotSetup")] | ||||||
|     TwoFactorAuthNotSetup, |     TwoFactorAuthNotSetup, | ||||||
|  |     #[error("TOTP secret not found")] | ||||||
|  |     TotpSecretNotFound, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors { | impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors { | ||||||
| @ -199,6 +201,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon | |||||||
|             Self::TwoFactorAuthNotSetup => { |             Self::TwoFactorAuthNotSetup => { | ||||||
|                 AER::BadRequest(ApiError::new(sub_code, 41, self.get_error_message(), None)) |                 AER::BadRequest(ApiError::new(sub_code, 41, self.get_error_message(), None)) | ||||||
|             } |             } | ||||||
|  |             Self::TotpSecretNotFound => { | ||||||
|  |                 AER::BadRequest(ApiError::new(sub_code, 42, self.get_error_message(), None)) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -241,6 +246,7 @@ impl UserErrors { | |||||||
|             Self::InvalidRecoveryCode => "Invalid Recovery Code", |             Self::InvalidRecoveryCode => "Invalid Recovery Code", | ||||||
|             Self::TwoFactorAuthRequired => "Two factor auth required", |             Self::TwoFactorAuthRequired => "Two factor auth required", | ||||||
|             Self::TwoFactorAuthNotSetup => "Two factor auth not setup", |             Self::TwoFactorAuthNotSetup => "Two factor auth not setup", | ||||||
|  |             Self::TotpSecretNotFound => "TOTP secret not found", | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1633,41 +1633,14 @@ pub async fn begin_totp( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     let totp = tfa_utils::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 secret = totp.get_secret_base32().into(); | ||||||
|  |  | ||||||
|     let key_store = user_from_db.get_or_create_key_store(&state).await?; |     tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?; | ||||||
|  |  | ||||||
|     state |  | ||||||
|         .store |  | ||||||
|         .update_user_by_user_id( |  | ||||||
|             user_from_db.get_user_id(), |  | ||||||
|             storage_user::UserUpdate::TotpUpdate { |  | ||||||
|                 totp_status: Some(TotpStatus::InProgress), |  | ||||||
|                 totp_secret: Some( |  | ||||||
|                     // TODO: Impl conversion trait for User and move this there |  | ||||||
|                     domain::types::encrypt::<String, masking::WithType>( |  | ||||||
|                         totp.get_secret_base32().into(), |  | ||||||
|                         key_store.key.peek(), |  | ||||||
|                     ) |  | ||||||
|                     .await |  | ||||||
|                     .change_context(UserErrors::InternalServerError)? |  | ||||||
|                     .into(), |  | ||||||
|                 ), |  | ||||||
|                 totp_recovery_codes: Some( |  | ||||||
|                     recovery_codes |  | ||||||
|                         .get_hashed() |  | ||||||
|                         .change_context(UserErrors::InternalServerError)?, |  | ||||||
|                 ), |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
|         .await |  | ||||||
|         .change_context(UserErrors::InternalServerError)?; |  | ||||||
|  |  | ||||||
|     Ok(ApplicationResponse::Json(user_api::BeginTotpResponse { |     Ok(ApplicationResponse::Json(user_api::BeginTotpResponse { | ||||||
|         secret: Some(user_api::TotpSecret { |         secret: Some(user_api::TotpSecret { | ||||||
|             secret: totp.get_secret_base32().into(), |             secret, | ||||||
|             totp_url: totp.get_url().into(), |             totp_url: totp.get_url().into(), | ||||||
|             recovery_codes: recovery_codes.into_inner(), |  | ||||||
|         }), |         }), | ||||||
|     })) |     })) | ||||||
| } | } | ||||||
| @ -1684,8 +1657,7 @@ pub async fn verify_totp( | |||||||
|         .change_context(UserErrors::InternalServerError)? |         .change_context(UserErrors::InternalServerError)? | ||||||
|         .into(); |         .into(); | ||||||
|  |  | ||||||
|     if let Some(user_totp) = req.totp { |     if user_from_db.get_totp_status() != TotpStatus::Set { | ||||||
|         if user_from_db.get_totp_status() == TotpStatus::NotSet { |  | ||||||
|         return Err(UserErrors::TotpNotSetup.into()); |         return Err(UserErrors::TotpNotSetup.into()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -1694,44 +1666,84 @@ pub async fn verify_totp( | |||||||
|         .await? |         .await? | ||||||
|         .ok_or(UserErrors::InternalServerError)?; |         .ok_or(UserErrors::InternalServerError)?; | ||||||
|  |  | ||||||
|         let totp = |     let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), Some(user_totp_secret))?; | ||||||
|             tfa_utils::generate_default_totp(user_from_db.get_email(), Some(user_totp_secret))?; |  | ||||||
|  |  | ||||||
|     if totp |     if totp | ||||||
|         .generate_current() |         .generate_current() | ||||||
|         .change_context(UserErrors::InternalServerError)? |         .change_context(UserErrors::InternalServerError)? | ||||||
|             != user_totp.expose() |         != req.totp.expose() | ||||||
|     { |     { | ||||||
|         return Err(UserErrors::InvalidTotp.into()); |         return Err(UserErrors::InvalidTotp.into()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         if user_from_db.get_totp_status() == TotpStatus::InProgress { |     tfa_utils::insert_totp_in_redis(&state, &user_token.user_id).await?; | ||||||
|  |  | ||||||
|  |     Ok(ApplicationResponse::StatusOk) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn update_totp( | ||||||
|  |     state: AppState, | ||||||
|  |     user_token: auth::UserFromSinglePurposeToken, | ||||||
|  |     req: user_api::VerifyTotpRequest, | ||||||
|  | ) -> UserResponse<()> { | ||||||
|  |     let user_from_db: domain::UserFromStorage = state | ||||||
|  |         .store | ||||||
|  |         .find_user_by_id(&user_token.user_id) | ||||||
|  |         .await | ||||||
|  |         .change_context(UserErrors::InternalServerError)? | ||||||
|  |         .into(); | ||||||
|  |  | ||||||
|  |     let new_totp_secret = tfa_utils::get_totp_secret_from_redis(&state, &user_token.user_id) | ||||||
|  |         .await? | ||||||
|  |         .ok_or(UserErrors::TotpSecretNotFound)?; | ||||||
|  |  | ||||||
|  |     let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), Some(new_totp_secret))?; | ||||||
|  |  | ||||||
|  |     if totp | ||||||
|  |         .generate_current() | ||||||
|  |         .change_context(UserErrors::InternalServerError)? | ||||||
|  |         != req.totp.expose() | ||||||
|  |     { | ||||||
|  |         return Err(UserErrors::InvalidTotp.into()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let key_store = user_from_db.get_or_create_key_store(&state).await?; | ||||||
|  |  | ||||||
|     state |     state | ||||||
|         .store |         .store | ||||||
|         .update_user_by_user_id( |         .update_user_by_user_id( | ||||||
|                     user_from_db.get_user_id(), |             &user_token.user_id, | ||||||
|             storage_user::UserUpdate::TotpUpdate { |             storage_user::UserUpdate::TotpUpdate { | ||||||
|                         totp_status: Some(TotpStatus::Set), |                 totp_status: None, | ||||||
|                         totp_secret: None, |                 totp_secret: Some( | ||||||
|  |                     // TODO: Impl conversion trait for User and move this there | ||||||
|  |                     domain::types::encrypt::<String, masking::WithType>( | ||||||
|  |                         totp.get_secret_base32().into(), | ||||||
|  |                         key_store.key.peek(), | ||||||
|  |                     ) | ||||||
|  |                     .await | ||||||
|  |                     .change_context(UserErrors::InternalServerError)? | ||||||
|  |                     .into(), | ||||||
|  |                 ), | ||||||
|  |  | ||||||
|                 totp_recovery_codes: None, |                 totp_recovery_codes: None, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         .await |         .await | ||||||
|         .change_context(UserErrors::InternalServerError)?; |         .change_context(UserErrors::InternalServerError)?; | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     let current_flow = domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::TOTP.into())?; |     let _ = tfa_utils::delete_totp_secret_from_redis(&state, &user_token.user_id) | ||||||
|     let next_flow = current_flow.next(user_from_db, &state).await?; |         .await | ||||||
|     let token = next_flow.get_token(&state).await?; |         .map_err(|e| logger::error!(?e)); | ||||||
|  |  | ||||||
|     auth::cookies::set_cookie_response( |     // This is not the main task of this API, so we don't throw error if this fails. | ||||||
|         user_api::TokenResponse { |     // Any following API which requires TOTP will throw error if TOTP is not set in redis | ||||||
|             token: token.clone(), |     // and FE will ask user to enter TOTP again | ||||||
|             token_type: next_flow.get_flow().into(), |     let _ = tfa_utils::insert_totp_in_redis(&state, &user_token.user_id) | ||||||
|         }, |         .await | ||||||
|         token, |         .map_err(|e| logger::error!(?e)); | ||||||
|     ) |  | ||||||
|  |     Ok(ApplicationResponse::StatusOk) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn generate_recovery_codes( | pub async fn generate_recovery_codes( | ||||||
|  | |||||||
| @ -1209,17 +1209,33 @@ impl User { | |||||||
|                 web::resource("/data") |                 web::resource("/data") | ||||||
|                     .route(web::get().to(get_multiple_dashboard_metadata)) |                     .route(web::get().to(get_multiple_dashboard_metadata)) | ||||||
|                     .route(web::post().to(set_dashboard_metadata)), |                     .route(web::post().to(set_dashboard_metadata)), | ||||||
|             ) |  | ||||||
|             .service(web::resource("/totp/begin").route(web::get().to(totp_begin))) |  | ||||||
|             .service(web::resource("/totp/verify").route(web::post().to(totp_verify))) |  | ||||||
|             .service( |  | ||||||
|                 web::resource("/2fa/terminate").route(web::get().to(terminate_two_factor_auth)), |  | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|  |         // Two factor auth routes | ||||||
|         route = route.service( |         route = route.service( | ||||||
|  |             web::scope("/2fa") | ||||||
|  |                 .service( | ||||||
|  |                     web::scope("/totp") | ||||||
|  |                         .service(web::resource("/begin").route(web::get().to(totp_begin))) | ||||||
|  |                         .service( | ||||||
|  |                             web::resource("/verify") | ||||||
|  |                                 .route(web::post().to(totp_verify)) | ||||||
|  |                                 .route(web::put().to(totp_update)), | ||||||
|  |                         ), | ||||||
|  |                 ) | ||||||
|  |                 .service( | ||||||
|                     web::scope("/recovery_code") |                     web::scope("/recovery_code") | ||||||
|                 .service(web::resource("/verify").route(web::post().to(verify_recovery_code))) |                         .service( | ||||||
|                 .service(web::resource("/generate").route(web::post().to(generate_recovery_codes))), |                             web::resource("/verify").route(web::post().to(verify_recovery_code)), | ||||||
|  |                         ) | ||||||
|  |                         .service( | ||||||
|  |                             web::resource("/generate") | ||||||
|  |                                 .route(web::get().to(generate_recovery_codes)), | ||||||
|  |                         ), | ||||||
|  |                 ) | ||||||
|  |                 .service( | ||||||
|  |                     web::resource("/terminate").route(web::get().to(terminate_two_factor_auth)), | ||||||
|  |                 ), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         #[cfg(feature = "email")] |         #[cfg(feature = "email")] | ||||||
|  | |||||||
| @ -215,9 +215,11 @@ impl From<Flow> for ApiIdentifier { | |||||||
|             | Flow::UpdateUserAccountDetails |             | Flow::UpdateUserAccountDetails | ||||||
|             | Flow::TotpBegin |             | Flow::TotpBegin | ||||||
|             | Flow::TotpVerify |             | Flow::TotpVerify | ||||||
|  |             | Flow::TotpUpdate | ||||||
|             | Flow::RecoveryCodeVerify |             | Flow::RecoveryCodeVerify | ||||||
|             | Flow::RecoveryCodesGenerate |             | Flow::RecoveryCodesGenerate | ||||||
|             | Flow::TerminateTwoFactorAuth => Self::User, |             | Flow::TerminateTwoFactorAuth => Self::User, | ||||||
|  |  | ||||||
|             Flow::ListRoles |             Flow::ListRoles | ||||||
|             | Flow::GetRole |             | Flow::GetRole | ||||||
|             | Flow::GetRoleFromToken |             | Flow::GetRoleFromToken | ||||||
|  | |||||||
| @ -684,6 +684,24 @@ pub async fn verify_recovery_code( | |||||||
|     .await |     .await | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub async fn totp_update( | ||||||
|  |     state: web::Data<AppState>, | ||||||
|  |     req: HttpRequest, | ||||||
|  |     json_payload: web::Json<user_api::VerifyTotpRequest>, | ||||||
|  | ) -> HttpResponse { | ||||||
|  |     let flow = Flow::TotpUpdate; | ||||||
|  |     Box::pin(api::server_wrap( | ||||||
|  |         flow, | ||||||
|  |         state.clone(), | ||||||
|  |         &req, | ||||||
|  |         json_payload.into_inner(), | ||||||
|  |         |state, user, req_body, _| user_core::update_totp(state, user, req_body), | ||||||
|  |         &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), | ||||||
|  |         api_locking::LockAction::NotApplicable, | ||||||
|  |     )) | ||||||
|  |     .await | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn generate_recovery_codes(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse { | pub async fn generate_recovery_codes(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse { | ||||||
|     let flow = Flow::RecoveryCodesGenerate; |     let flow = Flow::RecoveryCodesGenerate; | ||||||
|     Box::pin(api::server_wrap( |     Box::pin(api::server_wrap( | ||||||
|  | |||||||
| @ -1,9 +1,10 @@ | |||||||
| use std::collections::HashMap; | use std::{collections::HashMap, sync::Arc}; | ||||||
|  |  | ||||||
| use api_models::user as user_api; | use api_models::user as user_api; | ||||||
| use common_utils::errors::CustomResult; | use common_utils::errors::CustomResult; | ||||||
| use diesel_models::{enums::UserStatus, user_role::UserRole}; | use diesel_models::{enums::UserStatus, user_role::UserRole}; | ||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
|  | use redis_interface::RedisConnectionPool; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     core::errors::{StorageError, UserErrors, UserResult}, |     core::errors::{StorageError, UserErrors, UserResult}, | ||||||
| @ -191,3 +192,11 @@ pub fn get_token_from_signin_response(resp: &user_api::SignInResponse) -> maskin | |||||||
|         user_api::SignInResponse::MerchantSelect(data) => data.token.clone(), |         user_api::SignInResponse::MerchantSelect(data) => data.token.clone(), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn get_redis_connection(state: &AppState) -> UserResult<Arc<RedisConnectionPool>> { | ||||||
|  |     state | ||||||
|  |         .store | ||||||
|  |         .get_redis_conn() | ||||||
|  |         .change_context(UserErrors::InternalServerError) | ||||||
|  |         .attach_printable("Failed to get redis connection") | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,9 +1,6 @@ | |||||||
| use std::sync::Arc; |  | ||||||
|  |  | ||||||
| use common_utils::pii; | use common_utils::pii; | ||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
| use masking::ExposeInterface; | use masking::{ExposeInterface, PeekInterface}; | ||||||
| use redis_interface::RedisConnectionPool; |  | ||||||
| use totp_rs::{Algorithm, TOTP}; | use totp_rs::{Algorithm, TOTP}; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -35,8 +32,8 @@ pub fn generate_default_totp( | |||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn check_totp_in_redis(state: &AppState, user_id: &str) -> UserResult<bool> { | pub async fn check_totp_in_redis(state: &AppState, user_id: &str) -> UserResult<bool> { | ||||||
|     let redis_conn = get_redis_connection(state)?; |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|     let key = format!("{}{}", consts::user::TOTP_PREFIX, user_id); |     let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id); | ||||||
|     redis_conn |     redis_conn | ||||||
|         .exists::<()>(&key) |         .exists::<()>(&key) | ||||||
|         .await |         .await | ||||||
| @ -44,7 +41,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> { | pub async fn check_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult<bool> { | ||||||
|     let redis_conn = get_redis_connection(state)?; |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|     let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); |     let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); | ||||||
|     redis_conn |     redis_conn | ||||||
|         .exists::<()>(&key) |         .exists::<()>(&key) | ||||||
| @ -52,16 +49,62 @@ pub async fn check_recovery_code_in_redis(state: &AppState, user_id: &str) -> Us | |||||||
|         .change_context(UserErrors::InternalServerError) |         .change_context(UserErrors::InternalServerError) | ||||||
| } | } | ||||||
|  |  | ||||||
| fn get_redis_connection(state: &AppState) -> UserResult<Arc<RedisConnectionPool>> { | pub async fn insert_totp_in_redis(state: &AppState, user_id: &str) -> UserResult<()> { | ||||||
|     state |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|         .store |     let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id); | ||||||
|         .get_redis_conn() |     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) |         .change_context(UserErrors::InternalServerError) | ||||||
|         .attach_printable("Failed to get redis connection") | } | ||||||
|  |  | ||||||
|  | pub async fn insert_totp_secret_in_redis( | ||||||
|  |     state: &AppState, | ||||||
|  |     user_id: &str, | ||||||
|  |     secret: &masking::Secret<String>, | ||||||
|  | ) -> UserResult<()> { | ||||||
|  |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|  |     redis_conn | ||||||
|  |         .set_key_with_expiry( | ||||||
|  |             &get_totp_secret_key(user_id), | ||||||
|  |             secret.peek(), | ||||||
|  |             consts::user::REDIS_TOTP_SECRET_TTL_IN_SECS, | ||||||
|  |         ) | ||||||
|  |         .await | ||||||
|  |         .change_context(UserErrors::InternalServerError) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn get_totp_secret_from_redis( | ||||||
|  |     state: &AppState, | ||||||
|  |     user_id: &str, | ||||||
|  | ) -> UserResult<Option<masking::Secret<String>>> { | ||||||
|  |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|  |     redis_conn | ||||||
|  |         .get_key::<Option<String>>(&get_totp_secret_key(user_id)) | ||||||
|  |         .await | ||||||
|  |         .change_context(UserErrors::InternalServerError) | ||||||
|  |         .map(|secret| secret.map(Into::into)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn delete_totp_secret_from_redis(state: &AppState, user_id: &str) -> UserResult<()> { | ||||||
|  |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|  |     redis_conn | ||||||
|  |         .delete_key(&get_totp_secret_key(user_id)) | ||||||
|  |         .await | ||||||
|  |         .change_context(UserErrors::InternalServerError) | ||||||
|  |         .map(|_| ()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn get_totp_secret_key(user_id: &str) -> String { | ||||||
|  |     format!("{}{}", consts::user::REDIS_TOTP_SECRET_PREFIX, user_id) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn insert_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult<()> { | pub async fn insert_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult<()> { | ||||||
|     let redis_conn = get_redis_connection(state)?; |     let redis_conn = super::get_redis_connection(state)?; | ||||||
|     let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); |     let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); | ||||||
|     redis_conn |     redis_conn | ||||||
|         .set_key_with_expiry( |         .set_key_with_expiry( | ||||||
|  | |||||||
| @ -406,6 +406,8 @@ pub enum Flow { | |||||||
|     TotpBegin, |     TotpBegin, | ||||||
|     /// Verify TOTP |     /// Verify TOTP | ||||||
|     TotpVerify, |     TotpVerify, | ||||||
|  |     /// Update TOTP secret | ||||||
|  |     TotpUpdate, | ||||||
|     /// Verify Access Code |     /// Verify Access Code | ||||||
|     RecoveryCodeVerify, |     RecoveryCodeVerify, | ||||||
|     /// Generate or Regenerate recovery codes |     /// Generate or Regenerate recovery codes | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Mani Chandra
					Mani Chandra