From 7cc4dfabbf139c56dc5c8b4f25d049fc3cf11db4 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 27 Aug 2025 20:53:56 +0200 Subject: [PATCH] Fix 2fa recovery endpoint (#6240) The newer web-vaults handle the 2fa recovery code differently. This commit fixes this by adding this new flow. Fixes #6200 Fixes #6203 Signed-off-by: BlackDex --- src/api/identity.rs | 23 +++++++++++++++++++---- src/db/models/two_factor.rs | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index d3d22805..04863b58 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -267,7 +267,7 @@ async fn _sso_login( } Some((mut user, sso_user)) => { let mut device = get_device(&data, conn, &user).await?; - let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; + let twofactor_token = twofactor_auth(&mut user, &data, &mut device, ip, client_version, conn).await?; if user.private_key.is_none() { // User was invited a stub was created @@ -431,7 +431,7 @@ async fn _password_login( let mut device = get_device(&data, conn, &user).await?; - let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; + let twofactor_token = twofactor_auth(&mut user, &data, &mut device, ip, client_version, conn).await?; let auth_tokens = auth::AuthTokens::new(&device, &user, AuthMethod::Password, data.client_id); @@ -658,7 +658,7 @@ async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> ApiRe } async fn twofactor_auth( - user: &User, + user: &mut User, data: &ConnectData, device: &mut Device, ip: &ClientIp, @@ -723,7 +723,6 @@ async fn twofactor_auth( Some(TwoFactorType::Email) => { email::validate_email_code_str(&user.uuid, twofactor_code, &selected_data?, &ip.ip, conn).await? } - Some(TwoFactorType::Remember) => { match device.twofactor_remember { Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => { @@ -737,6 +736,22 @@ async fn twofactor_auth( } } } + Some(TwoFactorType::RecoveryCode) => { + // Check if recovery code is correct + if !user.check_valid_recovery_code(twofactor_code) { + err!("Recovery code is incorrect. Try again.") + } + + // Remove all twofactors from the user + TwoFactor::delete_all_by_user(&user.uuid, conn).await?; + enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?; + + log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, device.atype, &ip.ip, conn).await; + + // Remove the recovery code, not needed without twofactors + user.totp_recover = None; + user.save(conn).await?; + } _ => err!( "Invalid two factor provider", ErrorEvent { diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index 589967df..46b097bb 100644 --- a/src/db/models/two_factor.rs +++ b/src/db/models/two_factor.rs @@ -31,6 +31,7 @@ pub enum TwoFactorType { Remember = 5, OrganizationDuo = 6, Webauthn = 7, + RecoveryCode = 8, // These are implementation details U2fRegisterChallenge = 1000,