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