mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-11-24 21:03:05 +00:00
automatically use email address as 2fa provider (#4317)
This commit is contained in:
parent
7c3cad197c
commit
79ce5b49bc
7 changed files with 90 additions and 12 deletions
|
@ -444,6 +444,11 @@
|
|||
##
|
||||
## Maximum attempts before an email token is reset and a new email will need to be sent.
|
||||
# EMAIL_ATTEMPTS_LIMIT=3
|
||||
##
|
||||
## Setup email 2FA regardless of any organization policy
|
||||
# EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false
|
||||
## Automatically setup email 2FA as fallback provider when needed
|
||||
# EMAIL_2FA_AUTO_FALLBACK=false
|
||||
|
||||
## Other MFA/2FA settings
|
||||
## Disable 2FA remember
|
||||
|
|
|
@ -510,7 +510,11 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
|
|||
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &mut conn).await {
|
||||
Ok(_) => {}
|
||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||
err!("You cannot modify this user to this type because it has no two-step login method activated");
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::find_and_activate_email_2fa(&user_to_edit.user_uuid, &mut conn).await?;
|
||||
} else {
|
||||
err!("You cannot modify this user to this type because they have not setup 2FA");
|
||||
}
|
||||
}
|
||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
|
||||
|
|
|
@ -5,8 +5,9 @@ use serde_json::Value;
|
|||
|
||||
use crate::{
|
||||
api::{
|
||||
core::log_user_event, register_push_device, unregister_push_device, AnonymousNotify, EmptyResult, JsonResult,
|
||||
JsonUpcase, Notify, PasswordOrOtpData, UpdateType,
|
||||
core::{log_user_event, two_factor::email},
|
||||
register_push_device, unregister_push_device, AnonymousNotify, EmptyResult, JsonResult, JsonUpcase, Notify,
|
||||
PasswordOrOtpData, UpdateType,
|
||||
},
|
||||
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers},
|
||||
crypto,
|
||||
|
@ -104,6 +105,19 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn is_email_2fa_required(org_user_uuid: Option<String>, conn: &mut DbConn) -> bool {
|
||||
if !CONFIG._enable_email_2fa() {
|
||||
return false;
|
||||
}
|
||||
if CONFIG.email_2fa_enforce_on_verified_invite() {
|
||||
return true;
|
||||
}
|
||||
if org_user_uuid.is_some() {
|
||||
return OrgPolicy::is_enabled_by_org(&org_user_uuid.unwrap(), OrgPolicyType::TwoFactorAuthentication, conn)
|
||||
.await;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[post("/accounts/register", data = "<data>")]
|
||||
async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult {
|
||||
|
@ -208,6 +222,10 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
|
|||
} else if let Err(e) = mail::send_welcome(&user.email).await {
|
||||
error!("Error sending welcome email: {:#?}", e);
|
||||
}
|
||||
|
||||
if verified_by_invite && is_email_2fa_required(data.OrganizationUserId, &mut conn).await {
|
||||
let _ = email::activate_email_2fa(&user, &mut conn).await;
|
||||
}
|
||||
}
|
||||
|
||||
user.save(&mut conn).await?;
|
||||
|
|
|
@ -1079,7 +1079,7 @@ async fn accept_invite(
|
|||
let claims = decode_invite(&data.Token)?;
|
||||
|
||||
match User::find_by_mail(&claims.email, &mut conn).await {
|
||||
Some(_) => {
|
||||
Some(user) => {
|
||||
Invitation::take(&claims.email, &mut conn).await;
|
||||
|
||||
if let (Some(user_org), Some(org)) = (&claims.user_org_id, &claims.org_id) {
|
||||
|
@ -1103,8 +1103,12 @@ async fn accept_invite(
|
|||
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, &mut conn).await {
|
||||
Ok(_) => {}
|
||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::activate_email_2fa(&user, &mut conn).await?;
|
||||
} else {
|
||||
err!("You cannot join this organization until you enable two-step login on your user account");
|
||||
}
|
||||
}
|
||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||
err!("You cannot join this organization because you are a member of an organization which forbids it");
|
||||
}
|
||||
|
@ -1228,10 +1232,14 @@ async fn _confirm_invite(
|
|||
match OrgPolicy::is_user_allowed(&user_to_confirm.user_uuid, org_id, true, conn).await {
|
||||
Ok(_) => {}
|
||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||
err!("You cannot confirm this user because it has no two-step login method activated");
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::find_and_activate_email_2fa(&user_to_confirm.user_uuid, conn).await?;
|
||||
} else {
|
||||
err!("You cannot confirm this user because they have not setup 2FA");
|
||||
}
|
||||
}
|
||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||
err!("You cannot confirm this user because it is a member of an organization which forbids it");
|
||||
err!("You cannot confirm this user because they are a member of an organization which forbids it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1359,10 +1367,14 @@ async fn edit_user(
|
|||
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, org_id, true, &mut conn).await {
|
||||
Ok(_) => {}
|
||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||
err!("You cannot modify this user to this type because it has no two-step login method activated");
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::find_and_activate_email_2fa(&user_to_edit.user_uuid, &mut conn).await?;
|
||||
} else {
|
||||
err!("You cannot modify this user to this type because they have not setup 2FA");
|
||||
}
|
||||
}
|
||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
|
||||
err!("You cannot modify this user to this type because they are a member of an organization which forbids it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2159,10 +2171,14 @@ async fn _restore_organization_user(
|
|||
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, conn).await {
|
||||
Ok(_) => {}
|
||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
||||
err!("You cannot restore this user because it has no two-step login method activated");
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::find_and_activate_email_2fa(&user_org.user_uuid, conn).await?;
|
||||
} else {
|
||||
err!("You cannot restore this user because they have not setup 2FA");
|
||||
}
|
||||
}
|
||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||
err!("You cannot restore this user because it is a member of an organization which forbids it");
|
||||
err!("You cannot restore this user because they are a member of an organization which forbids it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
auth::Headers,
|
||||
crypto,
|
||||
db::{
|
||||
models::{EventType, TwoFactor, TwoFactorType},
|
||||
models::{EventType, TwoFactor, TwoFactorType, User},
|
||||
DbConn,
|
||||
},
|
||||
error::{Error, MapResult},
|
||||
|
@ -297,6 +297,15 @@ impl EmailTokenData {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn activate_email_2fa(user: &User, conn: &mut DbConn) -> EmptyResult {
|
||||
if user.verified_at.is_none() {
|
||||
err!("Auto-enabling of email 2FA failed because the users email address has not been verified!");
|
||||
}
|
||||
let twofactor_data = EmailTokenData::new(user.email.clone(), String::new());
|
||||
let twofactor = TwoFactor::new(user.uuid.clone(), TwoFactorType::Email, twofactor_data.to_json());
|
||||
twofactor.save(conn).await
|
||||
}
|
||||
|
||||
/// Takes an email address and obscures it by replacing it with asterisks except two characters.
|
||||
pub fn obscure_email(email: &str) -> String {
|
||||
let split: Vec<&str> = email.rsplitn(2, '@').collect();
|
||||
|
@ -318,6 +327,14 @@ pub fn obscure_email(email: &str) -> String {
|
|||
format!("{}@{}", new_name, &domain)
|
||||
}
|
||||
|
||||
pub async fn find_and_activate_email_2fa(user_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
if let Some(user) = User::find_by_uuid(user_uuid, conn).await {
|
||||
activate_email_2fa(&user, conn).await
|
||||
} else {
|
||||
err!("User not found!");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -686,6 +686,10 @@ make_config! {
|
|||
email_expiration_time: u64, true, def, 600;
|
||||
/// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent
|
||||
email_attempts_limit: u64, true, def, 3;
|
||||
/// Automatically enforce at login |> Setup email 2FA provider regardless of any organization policy
|
||||
email_2fa_enforce_on_verified_invite: bool, true, def, false;
|
||||
/// Auto-enable 2FA (Know the risks!) |> Automatically setup email 2FA as fallback provider when needed
|
||||
email_2fa_auto_fallback: bool, true, def, false;
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -888,6 +892,13 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
|||
err!("To enable email 2FA, a mail transport must be configured")
|
||||
}
|
||||
|
||||
if !cfg._enable_email_2fa && cfg.email_2fa_enforce_on_verified_invite {
|
||||
err!("To enforce email 2FA on verified invitations, email 2fa has to be enabled!");
|
||||
}
|
||||
if !cfg._enable_email_2fa && cfg.email_2fa_auto_fallback {
|
||||
err!("To use email 2FA as automatic fallback, email 2fa has to be enabled!");
|
||||
}
|
||||
|
||||
// Check if the icon blacklist regex is valid
|
||||
if let Some(ref r) = cfg.icon_blacklist_regex {
|
||||
let validate_regex = regex::Regex::new(r);
|
||||
|
|
|
@ -340,4 +340,11 @@ impl OrgPolicy {
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn is_enabled_by_org(org_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> bool {
|
||||
if let Some(policy) = OrgPolicy::find_by_org_and_type(org_uuid, policy_type, conn).await {
|
||||
return policy.enabled;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue