From 2c36993792e187ba7517c7e691d67ac12645ea87 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk <509385+stefan0xC@users.noreply.github.com> Date: Mon, 1 Jan 2024 19:41:40 +0100 Subject: [PATCH] enforce 2FA policy on removal of second factor and login (#3803) * enforce 2fa policy on removal of second factor users should be revoked when their second factors are removed. we want to revoke users so they don't have to be invited again and organization admins and owners are aware that they no longer have access. we make an exception for non-confirmed users to speed up the invitation process as they would have to be restored before they can accept their invitation or be confirmed. if email is enabled, invited users have to add a second factor before they can accept the invitation to an organization with 2fa policy. and if it is not enabled that check is done when confirming the user. * use &str instead of String in log_event() * enforce the 2fa policy on login if a user doesn't have a second factor check if they are in an organization that has the 2fa policy enabled to revoke their access --- src/api/admin.rs | 12 +++-- src/api/core/ciphers.rs | 24 +++------ src/api/core/events.rs | 4 +- src/api/core/organizations.rs | 93 +++++++++++++------------------- src/api/core/two_factor/mod.rs | 96 ++++++++++++++++++++++++++++------ src/api/identity.rs | 46 ++++++++-------- src/db/models/organization.rs | 10 ++++ 7 files changed, 163 insertions(+), 122 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index ae304253..a10df891 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -13,7 +13,10 @@ use rocket::{ }; use crate::{ - api::{core::log_event, unregister_push_device, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString}, + api::{ + core::{log_event, two_factor}, + unregister_push_device, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString, + }, auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp}, config::ConfigBuilder, db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, @@ -390,7 +393,7 @@ async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRe EventType::OrganizationUserRemoved as i32, &user_org.uuid, &user_org.org_uuid, - String::from(ACTING_ADMIN_USER), + ACTING_ADMIN_USER, 14, // Use UnknownBrowser type &token.ip.ip, &mut conn, @@ -445,9 +448,10 @@ async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyR } #[post("/users//remove-2fa")] -async fn remove_2fa(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { +async fn remove_2fa(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { let mut user = get_user_or_404(uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; + two_factor::enforce_2fa_policy(&user, ACTING_ADMIN_USER, 14, &token.ip.ip, &mut conn).await?; user.totp_recover = None; user.save(&mut conn).await } @@ -517,7 +521,7 @@ async fn update_user_org_type(data: Json, token: AdminToken, mu EventType::OrganizationUserUpdated as i32, &user_to_edit.uuid, &data.org_uuid, - String::from(ACTING_ADMIN_USER), + ACTING_ADMIN_USER, 14, // Use UnknownBrowser type &token.ip.ip, &mut conn, diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 0410d68e..a4a2d836 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -510,7 +510,7 @@ pub async fn update_cipher_from_data( event_type as i32, &cipher.uuid, org_uuid, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -791,7 +791,7 @@ async fn post_collections_admin( EventType::CipherUpdatedCollections as i32, &cipher.uuid, &cipher.organization_uuid.unwrap(), - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1145,7 +1145,7 @@ async fn save_attachment( EventType::CipherAttachmentCreated as i32, &cipher.uuid, org_uuid, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1479,7 +1479,7 @@ async fn delete_all( EventType::OrganizationPurgedVault as i32, &org_data.org_id, &org_data.org_id, - user.uuid, + &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1560,16 +1560,8 @@ async fn _delete_cipher_by_uuid( false => EventType::CipherDeleted as i32, }; - log_event( - event_type, - &cipher.uuid, - &org_uuid, - headers.user.uuid.clone(), - headers.device.atype, - &headers.ip.ip, - conn, - ) - .await; + log_event(event_type, &cipher.uuid, &org_uuid, &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn) + .await; } Ok(()) @@ -1629,7 +1621,7 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon EventType::CipherRestored as i32, &cipher.uuid.clone(), org_uuid, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -1713,7 +1705,7 @@ async fn _delete_cipher_attachment_by_id( EventType::CipherAttachmentDeleted as i32, &cipher.uuid, &org_uuid, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 70704d79..9a2027e0 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -263,7 +263,7 @@ pub async fn log_event( event_type: i32, source_uuid: &str, org_uuid: &str, - act_user_uuid: String, + act_user_uuid: &str, device_type: i32, ip: &IpAddr, conn: &mut DbConn, @@ -271,7 +271,7 @@ pub async fn log_event( if !CONFIG.org_events_enabled() { return; } - _log_event(event_type, source_uuid, org_uuid, &act_user_uuid, device_type, None, ip, conn).await; + _log_event(event_type, source_uuid, org_uuid, act_user_uuid, device_type, None, ip, conn).await; } #[allow(clippy::too_many_arguments)] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 59079e01..f57d8506 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -5,7 +5,7 @@ use serde_json::Value; use crate::{ api::{ - core::{log_event, CipherSyncData, CipherSyncType}, + core::{log_event, two_factor, CipherSyncData, CipherSyncType}, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, Notify, NumberOrString, PasswordOrOtpData, UpdateType, }, @@ -226,7 +226,7 @@ async fn leave_organization(org_id: &str, headers: Headers, mut conn: DbConn) -> EventType::OrganizationUserRemoved as i32, &user_org.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -279,7 +279,7 @@ async fn post_organization( EventType::OrganizationUpdated as i32, org_id, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -396,7 +396,7 @@ async fn post_organization_collections( EventType::CollectionCreated as i32, &collection.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -477,7 +477,7 @@ async fn post_organization_collection_update( EventType::CollectionUpdated as i32, &collection.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -565,7 +565,7 @@ async fn _delete_organization_collection( EventType::CollectionDeleted as i32, &collection.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -946,7 +946,7 @@ async fn send_invite( EventType::OrganizationUserInvited as i32, &new_user.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1240,7 +1240,7 @@ async fn _confirm_invite( EventType::OrganizationUserConfirmed as i32, &user_to_confirm.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -1402,7 +1402,7 @@ async fn edit_user( EventType::OrganizationUserUpdated as i32, &user_to_edit.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1494,7 +1494,7 @@ async fn _delete_user( EventType::OrganizationUserRemoved as i32, &user_to_delete.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -1697,38 +1697,16 @@ async fn put_policy( None => err!("Invalid or unsupported policy type"), }; - // When enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA + // When enabling the TwoFactorAuthentication policy, revoke all members that do not have 2FA if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { - for member in UserOrganization::find_by_org(org_id, &mut conn).await.into_iter() { - let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &mut conn).await.is_empty(); - - // Policy only applies to non-Owner/non-Admin members who have accepted joining the org - // Invited users still need to accept the invite and will get an error when they try to accept the invite. - if user_twofactor_disabled - && member.atype < UserOrgType::Admin - && member.status != UserOrgStatus::Invited as i32 - { - if CONFIG.mail_enabled() { - let org = Organization::find_by_uuid(&member.org_uuid, &mut conn).await.unwrap(); - let user = User::find_by_uuid(&member.user_uuid, &mut conn).await.unwrap(); - - mail::send_2fa_removed_from_org(&user.email, &org.name).await?; - } - - log_event( - EventType::OrganizationUserRemoved as i32, - &member.uuid, - org_id, - headers.user.uuid.clone(), - headers.device.atype, - &headers.ip.ip, - &mut conn, - ) - .await; - - member.delete(&mut conn).await?; - } - } + two_factor::enforce_2fa_policy_for_org( + org_id, + &headers.user.uuid, + headers.device.atype, + &headers.ip.ip, + &mut conn, + ) + .await?; } // When enabling the SingleOrg policy, remove this org's members that are members of other orgs @@ -1753,7 +1731,7 @@ async fn put_policy( EventType::OrganizationUserRemoved as i32, &member.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1778,7 +1756,7 @@ async fn put_policy( EventType::PolicyUpdated as i32, &policy.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1895,7 +1873,7 @@ async fn import(org_id: &str, data: JsonUpcase, headers: Headers, EventType::OrganizationUserRemoved as i32, &user_org.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1925,7 +1903,7 @@ async fn import(org_id: &str, data: JsonUpcase, headers: Headers, EventType::OrganizationUserInvited as i32, &new_org_user.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -1961,7 +1939,7 @@ async fn import(org_id: &str, data: JsonUpcase, headers: Headers, EventType::OrganizationUserRemoved as i32, &user_org.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2074,7 +2052,7 @@ async fn _revoke_organization_user( EventType::OrganizationUserRevoked as i32, &user_org.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -2193,7 +2171,7 @@ async fn _restore_organization_user( EventType::OrganizationUserRestored as i32, &user_org.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -2322,7 +2300,7 @@ async fn post_groups( EventType::GroupCreated as i32, &group.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2359,7 +2337,7 @@ async fn put_group( EventType::GroupUpdated as i32, &updated_group.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2392,7 +2370,7 @@ async fn add_update_group( EventType::OrganizationUserUpdatedGroups as i32, &assigned_user_id, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -2447,7 +2425,7 @@ async fn _delete_group(org_id: &str, group_id: &str, headers: &AdminHeaders, con EventType::GroupDeleted as i32, &group.uuid, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, conn, @@ -2538,7 +2516,7 @@ async fn put_group_users( EventType::OrganizationUserUpdatedGroups as i32, &assigned_user_id, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2616,7 +2594,7 @@ async fn put_user_groups( EventType::OrganizationUserUpdatedGroups as i32, org_user_id, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2671,7 +2649,7 @@ async fn delete_group_user( EventType::OrganizationUserUpdatedGroups as i32, org_user_id, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2760,7 +2738,7 @@ async fn put_reset_password( EventType::OrganizationUserAdminResetPassword as i32, org_user_id, org_id, - headers.user.uuid.clone(), + &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn, @@ -2887,8 +2865,7 @@ async fn put_reset_password_enrollment( EventType::OrganizationUserResetPasswordWithdraw as i32 }; - log_event(log_id, org_user_id, org_id, headers.user.uuid.clone(), headers.device.atype, &headers.ip.ip, &mut conn) - .await; + log_event(log_id, org_user_id, org_id, &headers.user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; Ok(()) } diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index 41368666..9a922d26 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -5,7 +5,10 @@ use rocket::Route; use serde_json::Value; use crate::{ - api::{core::log_user_event, JsonResult, JsonUpcase, NumberOrString, PasswordOrOtpData}, + api::{ + core::{log_event, log_user_event}, + EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordOrOtpData, + }, auth::{ClientHeaders, Headers}, crypto, db::{models::*, DbConn, DbPool}, @@ -96,6 +99,7 @@ async fn recover(data: JsonUpcase, client_headers: ClientHeade // Remove all twofactors from the user TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; + enforce_2fa_policy(&user, &user.uuid, client_headers.device_type, &client_headers.ip.ip, &mut conn).await?; log_user_event( EventType::UserRecovered2fa as i32, @@ -149,22 +153,8 @@ async fn disable_twofactor(data: JsonUpcase, headers: Head .await; } - let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty(); - - if twofactor_disabled { - for user_org in - UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &mut conn) - .await - .into_iter() - { - if user_org.atype < UserOrgType::Admin { - if CONFIG.mail_enabled() { - let org = Organization::find_by_uuid(&user_org.org_uuid, &mut conn).await.unwrap(); - mail::send_2fa_removed_from_org(&user.email, &org.name).await?; - } - user_org.delete(&mut conn).await?; - } - } + if TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty() { + enforce_2fa_policy(&user, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await?; } Ok(Json(json!({ @@ -179,6 +169,78 @@ async fn disable_twofactor_put(data: JsonUpcase, headers: disable_twofactor(data, headers, conn).await } +pub async fn enforce_2fa_policy( + user: &User, + act_uuid: &str, + device_type: i32, + ip: &std::net::IpAddr, + conn: &mut DbConn, +) -> EmptyResult { + for member in UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, conn) + .await + .into_iter() + { + // Policy only applies to non-Owner/non-Admin members who have accepted joining the org + if member.atype < UserOrgType::Admin { + if CONFIG.mail_enabled() { + let org = Organization::find_by_uuid(&member.org_uuid, conn).await.unwrap(); + mail::send_2fa_removed_from_org(&user.email, &org.name).await?; + } + let mut member = member; + member.revoke(); + member.save(conn).await?; + + log_event( + EventType::OrganizationUserRevoked as i32, + &member.uuid, + &member.org_uuid, + act_uuid, + device_type, + ip, + conn, + ) + .await; + } + } + + Ok(()) +} + +pub async fn enforce_2fa_policy_for_org( + org_uuid: &str, + act_uuid: &str, + device_type: i32, + ip: &std::net::IpAddr, + conn: &mut DbConn, +) -> EmptyResult { + let org = Organization::find_by_uuid(org_uuid, conn).await.unwrap(); + for member in UserOrganization::find_confirmed_by_org(org_uuid, conn).await.into_iter() { + // Don't enforce the policy for Admins and Owners. + if member.atype < UserOrgType::Admin && TwoFactor::find_by_user(&member.user_uuid, conn).await.is_empty() { + if CONFIG.mail_enabled() { + let user = User::find_by_uuid(&member.user_uuid, conn).await.unwrap(); + mail::send_2fa_removed_from_org(&user.email, &org.name).await?; + } + let mut member = member; + member.revoke(); + member.save(conn).await?; + + log_event( + EventType::OrganizationUserRevoked as i32, + &member.uuid, + org_uuid, + act_uuid, + device_type, + ip, + conn, + ) + .await; + } + } + + Ok(()) +} + pub async fn send_incomplete_2fa_notifications(pool: DbPool) { debug!("Sending notifications for incomplete 2FA logins"); diff --git a/src/api/identity.rs b/src/api/identity.rs index 13e7ab43..12becf6b 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -9,9 +9,11 @@ use serde_json::Value; use crate::{ api::{ - core::accounts::{PreloginData, RegisterData, _prelogin, _register}, - core::log_user_event, - core::two_factor::{duo, email, email::EmailTokenData, yubikey}, + core::{ + accounts::{PreloginData, RegisterData, _prelogin, _register}, + log_user_event, + two_factor::{authenticator, duo, email, enforce_2fa_policy, webauthn, yubikey}, + }, ApiResult, EmptyResult, JsonResult, JsonUpcase, }, auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp}, @@ -247,7 +249,7 @@ async fn _password_login( let (mut device, new_device) = get_device(&data, conn, &user).await; - let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, conn).await?; + let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?; if CONFIG.mail_enabled() && new_device { if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name).await { @@ -468,32 +470,32 @@ async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> (Devi } async fn twofactor_auth( - user_uuid: &str, + user: &User, data: &ConnectData, device: &mut Device, ip: &ClientIp, conn: &mut DbConn, ) -> ApiResult> { - let twofactors = TwoFactor::find_by_user(user_uuid, conn).await; + let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await; // No twofactor token if twofactor is disabled if twofactors.is_empty() { + enforce_2fa_policy(user, &user.uuid, device.atype, &ip.ip, conn).await?; return Ok(None); } - TwoFactorIncomplete::mark_incomplete(user_uuid, &device.uuid, &device.name, ip, conn).await?; + TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, ip, conn).await?; let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect(); let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one let twofactor_code = match data.two_factor_token { Some(ref code) => code, - None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, "2FA token not provided"), + None => err_json!(_json_err_twofactor(&twofactor_ids, &user.uuid, conn).await?, "2FA token not provided"), }; let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled); - use crate::api::core::two_factor as _tf; use crate::crypto::ct_eq; let selected_data = _selected_data(selected_twofactor); @@ -501,17 +503,15 @@ async fn twofactor_auth( match TwoFactorType::from_i32(selected_id) { Some(TwoFactorType::Authenticator) => { - _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn).await? + authenticator::validate_totp_code_str(&user.uuid, twofactor_code, &selected_data?, ip, conn).await? } - Some(TwoFactorType::Webauthn) => { - _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn).await? - } - Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?).await?, + Some(TwoFactorType::Webauthn) => webauthn::validate_webauthn_login(&user.uuid, twofactor_code, conn).await?, + Some(TwoFactorType::YubiKey) => yubikey::validate_yubikey_login(twofactor_code, &selected_data?).await?, Some(TwoFactorType::Duo) => { - _tf::duo::validate_duo_login(data.username.as_ref().unwrap().trim(), twofactor_code, conn).await? + duo::validate_duo_login(data.username.as_ref().unwrap().trim(), twofactor_code, conn).await? } Some(TwoFactorType::Email) => { - _tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn).await? + email::validate_email_code_str(&user.uuid, twofactor_code, &selected_data?, conn).await? } Some(TwoFactorType::Remember) => { @@ -521,7 +521,7 @@ async fn twofactor_auth( } _ => { err_json!( - _json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, + _json_err_twofactor(&twofactor_ids, &user.uuid, conn).await?, "2FA Remember token not provided" ) } @@ -535,7 +535,7 @@ async fn twofactor_auth( ), } - TwoFactorIncomplete::mark_complete(user_uuid, &device.uuid, conn).await?; + TwoFactorIncomplete::mark_complete(&user.uuid, &device.uuid, conn).await?; if !CONFIG.disable_2fa_remember() && remember == 1 { Ok(Some(device.refresh_twofactor_remember())) @@ -550,8 +550,6 @@ fn _selected_data(tf: Option) -> ApiResult { } async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbConn) -> ApiResult { - use crate::api::core::two_factor; - let mut result = json!({ "error" : "invalid_grant", "error_description" : "Two factor required.", @@ -566,7 +564,7 @@ async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbCo Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => { - let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn).await?; + let request = webauthn::generate_webauthn_login(user_uuid, conn).await?; result["TwoFactorProviders2"][provider.to_string()] = request.0; } @@ -598,8 +596,6 @@ async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbCo } Some(tf_type @ TwoFactorType::Email) => { - use crate::api::core::two_factor as _tf; - let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await { Some(tf) => tf, None => err!("No twofactor email registered"), @@ -607,10 +603,10 @@ async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbCo // Send email immediately if email is the only 2FA option if providers.len() == 1 { - _tf::email::send_token(user_uuid, conn).await? + email::send_token(user_uuid, conn).await? } - let email_data = EmailTokenData::from_json(&twofactor.data)?; + let email_data = email::EmailTokenData::from_json(&twofactor.data)?; result["TwoFactorProviders2"][provider.to_string()] = json!({ "Email": email::obscure_email(&email_data.email), }) diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index d78ac07c..180b1c1d 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -664,6 +664,16 @@ impl UserOrganization { }} } + pub async fn find_confirmed_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec { + db_run! { conn: { + users_organizations::table + .filter(users_organizations::org_uuid.eq(org_uuid)) + .filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) + .load::(conn) + .unwrap_or_default().from_db() + }} + } + pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { db_run! { conn: { users_organizations::table