vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 4f08167d6f9856eda576f7ec47b5c4b938973bea
parent fef76e2f6f94834a8a0eaf4ea47ec489af4e1575
Author: Daniel García <dani-garcia@users.noreply.github.com>
Date:   Thu, 15 Jul 2021 19:27:36 +0200

Merge branch '2fa_enforcement' of https://github.com/olivierIllogika/bitwarden_rs into olivierIllogika-2fa_enforcement

Diffstat:
Msrc/api/core/organizations.rs | 31+++++++++++++++++++++++++++++++
Msrc/api/core/two_factor/mod.rs | 23+++++++++++++++++++----
Msrc/config.rs | 1+
Msrc/db/models/org_policy.rs | 2+-
Msrc/db/models/organization.rs | 21++++++++++++++++++++-
Msrc/error.rs | 2+-
Msrc/mail.rs | 12++++++++++++
Asrc/static/templates/email/send_2fa_removed_from_org.hbs | 9+++++++++
Asrc/static/templates/email/send_2fa_removed_from_org.html.hbs | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 223 insertions(+), 7 deletions(-)

diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -646,6 +646,19 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD err!("User already accepted the invitation") } + let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).is_empty(); + + let policy = OrgPolicyType::TwoFactorAuthentication as i32; + let org_twofactor_policy_enabled = + match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, policy, &conn) { + Some(p) => p.enabled, + None => false, + }; + + if org_twofactor_policy_enabled && user_twofactor_disabled { + err!("You cannot join this organization until you enable two-step login on your user account.") + } + user_org.status = UserOrgStatus::Accepted as i32; user_org.save(&conn)?; } @@ -998,6 +1011,24 @@ fn put_policy( None => err!("Invalid policy type"), }; + if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { + let org_list = UserOrganization::find_by_org(&org_id, &conn); + + for user_org in org_list.into_iter() { + let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).is_empty(); + + if user_twofactor_disabled && user_org.atype < UserOrgType::Admin { + if CONFIG.mail_enabled() { + let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); + let user = User::find_by_uuid(&user_org.user_uuid, &conn).unwrap(); + + mail::send_2fa_removed_from_org(&user.email, &org.name)?; + } + user_org.delete(&conn)?; + } + } + } + let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { Some(p) => p, None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()), diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs @@ -7,10 +7,8 @@ use crate::{ api::{JsonResult, JsonUpcase, NumberOrString, PasswordData}, auth::Headers, crypto, - db::{ - models::{TwoFactor, User}, - DbConn, - }, + db::{models::*, DbConn}, + mail, CONFIG, }; pub mod authenticator; @@ -130,6 +128,23 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c twofactor.delete(&conn)?; } + let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).is_empty(); + + if twofactor_disabled { + let policy_type = OrgPolicyType::TwoFactorAuthentication; + let org_list = UserOrganization::find_by_user_and_policy(&user.uuid, policy_type, &conn); + + for user_org in org_list.into_iter() { + if user_org.atype < UserOrgType::Admin { + if CONFIG.mail_enabled() { + let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); + mail::send_2fa_removed_from_org(&user.email, &org.name)?; + } + user_org.delete(&conn)?; + } + } + } + Ok(Json(json!({ "Enabled": false, "Type": type_, diff --git a/src/config.rs b/src/config.rs @@ -858,6 +858,7 @@ where reg!("email/new_device_logged_in", ".html"); reg!("email/pw_hint_none", ".html"); reg!("email/pw_hint_some", ".html"); + reg!("email/send_2fa_removed_from_org", ".html"); reg!("email/send_org_invite", ".html"); reg!("email/twofactor_email", ".html"); reg!("email/verify_email", ".html"); diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs @@ -22,7 +22,7 @@ db_object! { } } -#[derive(Copy, Clone, num_derive::FromPrimitive)] +#[derive(Copy, Clone, PartialEq, num_derive::FromPrimitive)] pub enum OrgPolicyType { TwoFactorAuthentication = 0, MasterPassword = 1, diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs @@ -2,7 +2,7 @@ use num_traits::FromPrimitive; use serde_json::Value; use std::cmp::Ordering; -use super::{CollectionUser, OrgPolicy, User}; +use super::{CollectionUser, OrgPolicy, OrgPolicyType, User}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -535,6 +535,25 @@ impl UserOrganization { }} } + pub fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> { + db_run! { conn: { + users_organizations::table + .inner_join( + org_policies::table.on( + org_policies::org_uuid.eq(users_organizations::org_uuid) + .and(users_organizations::user_uuid.eq(user_uuid)) + .and(org_policies::atype.eq(policy_type as i32)) + .and(org_policies::enabled.eq(true))) + ) + .filter( + users_organizations::status.eq(UserOrgStatus::Confirmed as i32) + ) + .select(users_organizations::all_columns) + .load::<UserOrganizationDb>(conn) + .unwrap_or_default().from_db() + }} + } + pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> { db_run! { conn: { users_organizations::table diff --git a/src/error.rs b/src/error.rs @@ -166,7 +166,7 @@ fn _serialize(e: &impl serde::Serialize, _msg: &str) -> String { fn _api_error(_: &impl std::any::Any, msg: &str) -> String { let json = json!({ - "Message": "", + "Message": msg, "error": "", "error_description": "", "ValidationErrors": {"": [ msg ]}, diff --git a/src/mail.rs b/src/mail.rs @@ -180,6 +180,18 @@ pub fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text) } +pub fn send_2fa_removed_from_org(address: &str, org_name: &str) -> EmptyResult { + let (subject, body_html, body_text) = get_text( + "email/send_2fa_removed_from_org", + json!({ + "url": CONFIG.domain(), + "org_name": org_name, + }), + )?; + + send_email(address, &subject, body_html, body_text) +} + pub fn send_invite( address: &str, uuid: &str, diff --git a/src/static/templates/email/send_2fa_removed_from_org.hbs b/src/static/templates/email/send_2fa_removed_from_org.hbs @@ -0,0 +1,9 @@ +Removed from {{{org_name}}} +<!----------------> +You have been removed from organization *{{org_name}}* because your account does not have Two-step Login enabled. + + +You can enable Two-step Login in your account settings. + +=== +Github: https://github.com/dani-garcia/vaultwarden diff --git a/src/static/templates/email/send_2fa_removed_from_org.html.hbs b/src/static/templates/email/send_2fa_removed_from_org.html.hbs @@ -0,0 +1,129 @@ +Removed from {{{org_name}}} +<!----------------> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Vaultwarden</title> + </head> + <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> + <style type="text/css"> +  body { + margin: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 16px; + color: #333; + line-height: 25px; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + } + body * { + margin: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 16px; + color: #333; + line-height: 25px; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + } + img { + max-width: 100%; + border: none; + } + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 25px; + } + body { + background-color: #f6f6f6; + } + @media only screen and (max-width: 600px) { + body { + padding: 0 !important; + } + .container { + padding: 0 !important; + width: 100% !important; + } + .container-table { + padding: 0 !important; + width: 100% !important; + } + .content { + padding: 0 0 10px 0 !important; + } + .content-wrap { + padding: 10px !important; + } + .invoice { + width: 100% !important; + } + .main { + border-right: none !important; + border-left: none !important; + border-radius: 0 !important; + } + .logo { + padding-top: 10px !important; + } + .footer { + margin-top: 10px !important; + } + .indented { + padding-left: 10px; + } + } + </style> + <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center"> + <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /> + </td> + </tr> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top"> + <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: max-content;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top"> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white"> + <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> + <td class="content-wrap" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 20px; -webkit-text-size-adjust: none;" valign="top"> + <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> + <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> + <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> + You have been removed from organization <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{org_name}}</b> because your account does not have Two-step Login enabled. + </td> + </tr> + <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> + <td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> + You can enable Two-step Login in your account settings. + </td> + </tr> + </table> + </td> + </tr> + </table> + <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> + <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/vaultwarden" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html>