mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-11-24 22:05:41 +03: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.
|
## Maximum attempts before an email token is reset and a new email will need to be sent.
|
||||||
# EMAIL_ATTEMPTS_LIMIT=3
|
# 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
|
## Other MFA/2FA settings
|
||||||
## Disable 2FA remember
|
## 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 {
|
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &mut conn).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
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(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 it is a member of an organization which forbids it");
|
||||||
|
|
|
@ -5,8 +5,9 @@ use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{
|
api::{
|
||||||
core::log_user_event, register_push_device, unregister_push_device, AnonymousNotify, EmptyResult, JsonResult,
|
core::{log_user_event, two_factor::email},
|
||||||
JsonUpcase, Notify, PasswordOrOtpData, UpdateType,
|
register_push_device, unregister_push_device, AnonymousNotify, EmptyResult, JsonResult, JsonUpcase, Notify,
|
||||||
|
PasswordOrOtpData, UpdateType,
|
||||||
},
|
},
|
||||||
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers},
|
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers},
|
||||||
crypto,
|
crypto,
|
||||||
|
@ -104,6 +105,19 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult
|
||||||
}
|
}
|
||||||
Ok(())
|
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>")]
|
#[post("/accounts/register", data = "<data>")]
|
||||||
async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult {
|
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 {
|
} else if let Err(e) = mail::send_welcome(&user.email).await {
|
||||||
error!("Error sending welcome email: {:#?}", e);
|
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?;
|
user.save(&mut conn).await?;
|
||||||
|
|
|
@ -1079,7 +1079,7 @@ async fn accept_invite(
|
||||||
let claims = decode_invite(&data.Token)?;
|
let claims = decode_invite(&data.Token)?;
|
||||||
|
|
||||||
match User::find_by_mail(&claims.email, &mut conn).await {
|
match User::find_by_mail(&claims.email, &mut conn).await {
|
||||||
Some(_) => {
|
Some(user) => {
|
||||||
Invitation::take(&claims.email, &mut conn).await;
|
Invitation::take(&claims.email, &mut conn).await;
|
||||||
|
|
||||||
if let (Some(user_org), Some(org)) = (&claims.user_org_id, &claims.org_id) {
|
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 {
|
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, &mut conn).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
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!("You cannot join this organization until you enable two-step login on your user account");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
Err(OrgPolicyErr::SingleOrgEnforced) => {
|
||||||
err!("You cannot join this organization because you are a member of an organization which forbids it");
|
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 {
|
match OrgPolicy::is_user_allowed(&user_to_confirm.user_uuid, org_id, true, conn).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
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(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 {
|
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, org_id, true, &mut conn).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
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(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 {
|
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, conn).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(OrgPolicyErr::TwoFactorMissing) => {
|
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(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,
|
auth::Headers,
|
||||||
crypto,
|
crypto,
|
||||||
db::{
|
db::{
|
||||||
models::{EventType, TwoFactor, TwoFactorType},
|
models::{EventType, TwoFactor, TwoFactorType, User},
|
||||||
DbConn,
|
DbConn,
|
||||||
},
|
},
|
||||||
error::{Error, MapResult},
|
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.
|
/// Takes an email address and obscures it by replacing it with asterisks except two characters.
|
||||||
pub fn obscure_email(email: &str) -> String {
|
pub fn obscure_email(email: &str) -> String {
|
||||||
let split: Vec<&str> = email.rsplitn(2, '@').collect();
|
let split: Vec<&str> = email.rsplitn(2, '@').collect();
|
||||||
|
@ -318,6 +327,14 @@ pub fn obscure_email(email: &str) -> String {
|
||||||
format!("{}@{}", new_name, &domain)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -686,6 +686,10 @@ make_config! {
|
||||||
email_expiration_time: u64, true, def, 600;
|
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
|
/// 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;
|
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")
|
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
|
// Check if the icon blacklist regex is valid
|
||||||
if let Some(ref r) = cfg.icon_blacklist_regex {
|
if let Some(ref r) = cfg.icon_blacklist_regex {
|
||||||
let validate_regex = regex::Regex::new(r);
|
let validate_regex = regex::Regex::new(r);
|
||||||
|
|
|
@ -340,4 +340,11 @@ impl OrgPolicy {
|
||||||
}
|
}
|
||||||
false
|
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