From d75a80bd2dbe21e5a1eb2b0a6b18a9422441e071 Mon Sep 17 00:00:00 2001
From: Olivier Martin <olivier.martin@illogika.com>
Date: Sun, 11 Apr 2021 22:57:17 -0400
Subject: [PATCH 01/13] Resolves dani-garcia/bitwarden_rs#981 * a user without
 2fa trying to join a 2fa org will fail, but user gets an email to enable 2fa
 * a user disabling 2fa will be removed from 2fa orgs; user gets an email for
 each org * an org enabling 2fa policy will remove users without 2fa; users
 get an email

---
 src/api/core/organizations.rs                 |  33 +++++
 src/api/core/two_factor/mod.rs                |  20 ++-
 src/config.rs                                 |   1 +
 src/db/models/org_policy.rs                   |   1 +
 src/db/models/organization.rs                 |  21 ++-
 src/mail.rs                                   |  16 +++
 .../email/send_2fa_removed_from_org.hbs       |   9 ++
 .../email/send_2fa_removed_from_org.html.hbs  | 129 ++++++++++++++++++
 8 files changed, 228 insertions(+), 2 deletions(-)
 create mode 100644 src/static/templates/email/send_2fa_removed_from_org.hbs
 create mode 100644 src/static/templates/email/send_2fa_removed_from_org.html.hbs

diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
index 5698c187..e750841c 100644
--- a/src/api/core/organizations.rs
+++ b/src/api/core/organizations.rs
@@ -647,6 +647,21 @@ 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 {
+                    let org = Organization::find_by_uuid(&org, &conn).unwrap();
+                    // you haven't joined yet, but mail explains why you were unable to accept invitation
+                    mail::send_2fa_removed_from_org(&claims.email, &org.name)?;
+                    err!("Organization policy requires that you enable two Two-step Login begin joining.")
+                }
+
                 user_org.status = UserOrgStatus::Accepted as i32;
                 user_org.save(&conn)?;
             }
@@ -996,6 +1011,24 @@ fn put_policy(org_id: String, pol_type: i32, data: Json<PolicyData>, _headers: A
         Some(pt) => pt,
         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 {
+
+                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,
diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs
index a3dfd319..c0764761 100644
--- a/src/api/core/two_factor/mod.rs
+++ b/src/api/core/two_factor/mod.rs
@@ -8,9 +8,10 @@ use crate::{
     auth::Headers,
     crypto,
     db::{
-        models::{TwoFactor, User},
+        models::*,
         DbConn,
     },
+    mail,
 };
 
 pub mod authenticator;
@@ -134,6 +135,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 {
+                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
index 86031c72..38f916e9 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -842,6 +842,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
index 0707eccc..7d9cb6fc 100644
--- a/src/db/models/org_policy.rs
+++ b/src/db/models/org_policy.rs
@@ -22,6 +22,7 @@ db_object! {
 
 #[derive(Copy, Clone)]
 #[derive(num_derive::FromPrimitive)]
+#[derive(PartialEq)]
 pub enum OrgPolicyType {
     TwoFactorAuthentication = 0,
     MasterPassword = 1,
diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
index 1eeb04d2..671ba120 100644
--- a/src/db/models/organization.rs
+++ b/src/db/models/organization.rs
@@ -2,7 +2,7 @@ use serde_json::Value;
 use std::cmp::Ordering;
 use num_traits::FromPrimitive;
 
-use super::{CollectionUser, User, OrgPolicy};
+use super::{CollectionUser, User, OrgPolicy, OrgPolicyType};
 
 db_object! {
     #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@@ -538,6 +538,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/mail.rs b/src/mail.rs
index cd9edd9e..b1e81a52 100644
--- a/src/mail.rs
+++ b/src/mail.rs
@@ -179,6 +179,22 @@ 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
new file mode 100644
index 00000000..b836ff31
--- /dev/null
+++ 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/bitwarden_rs
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
new file mode 100644
index 00000000..56dfc191
--- /dev/null
+++ 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>Bitwarden_rs</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/bitwarden_rs" 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>

From 1db37bf3d06543c890612ff88193813035763034 Mon Sep 17 00:00:00 2001
From: Olivier Martin <olivier.martin@illogika.com>
Date: Mon, 12 Apr 2021 21:54:57 -0400
Subject: [PATCH 02/13] make error toast display detailed message replace
 invite accept error message with the one from upstream check if config mail
 is enabled

---
 src/api/core/organizations.rs  | 14 +++++++-------
 src/api/core/two_factor/mod.rs |  8 +++++---
 src/error.rs                   |  2 +-
 3 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
index e750841c..fbb27840 100644
--- a/src/api/core/organizations.rs
+++ b/src/api/core/organizations.rs
@@ -656,10 +656,8 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD
                 };
 
                 if org_twofactor_policy_enabled && user_twofactor_disabled {
-                    let org = Organization::find_by_uuid(&org, &conn).unwrap();
-                    // you haven't joined yet, but mail explains why you were unable to accept invitation
-                    mail::send_2fa_removed_from_org(&claims.email, &org.name)?;
-                    err!("Organization policy requires that you enable two Two-step Login begin joining.")
+
+                    err!("You cannot join this organization until you enable two-step login on your user account.")
                 }
 
                 user_org.status = UserOrgStatus::Accepted as i32;
@@ -1021,10 +1019,12 @@ fn put_policy(org_id: String, pol_type: i32, data: Json<PolicyData>, _headers: A
 
             if user_twofactor_disabled && user_org.atype < UserOrgType::Admin {
 
-                let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap();
-                let user = User::find_by_uuid(&user_org.user_uuid, &conn).unwrap();
+                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)?;
+                    mail::send_2fa_removed_from_org(&user.email, &org.name)?;
+                }
                 user_org.delete(&conn)?;
             }
         }        
diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs
index c0764761..d1d9e2b4 100644
--- a/src/api/core/two_factor/mod.rs
+++ b/src/api/core/two_factor/mod.rs
@@ -11,7 +11,7 @@ use crate::{
         models::*,
         DbConn,
     },
-    mail,
+    mail, CONFIG,
 };
 
 pub mod authenticator;
@@ -144,9 +144,11 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c
         for user_org in org_list.into_iter() {
 
             if user_org.atype < UserOrgType::Admin {
-                let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap();
 
-                mail::send_2fa_removed_from_org(&user.email, &org.name)?;
+                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)?;
             }
         }
diff --git a/src/error.rs b/src/error.rs
index a0b28a4b..25c18e10 100644
--- 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 ]},

From 89a68741d6c049e827e84dc224566d1a61dda1f7 Mon Sep 17 00:00:00 2001
From: Olivier Martin <olivier.martin@illogika.com>
Date: Fri, 16 Apr 2021 14:49:59 -0400
Subject: [PATCH 03/13] ran cargo fmt --all

---
 src/api/core/organizations.rs  | 16 +++++++---------
 src/api/core/two_factor/mod.rs |  7 +------
 src/mail.rs                    |  6 +-----
 3 files changed, 9 insertions(+), 20 deletions(-)

diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
index cb53e204..54ec92eb 100644
--- a/src/api/core/organizations.rs
+++ b/src/api/core/organizations.rs
@@ -649,13 +649,13 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD
                 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,
-                };
+                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.")
                 }
 
@@ -1010,16 +1010,14 @@ fn put_policy(
         Some(pt) => pt,
         None => err!("Invalid policy type"),
     };
-       
-    if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled {
 
+    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();
@@ -1028,7 +1026,7 @@ fn put_policy(
                 }
                 user_org.delete(&conn)?;
             }
-        }        
+        }
     }
 
     let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) {
diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs
index a51eb2c3..fb0ee593 100644
--- a/src/api/core/two_factor/mod.rs
+++ b/src/api/core/two_factor/mod.rs
@@ -7,10 +7,7 @@ use crate::{
     api::{JsonResult, JsonUpcase, NumberOrString, PasswordData},
     auth::Headers,
     crypto,
-    db::{
-        models::*,
-        DbConn,
-    },
+    db::{models::*, DbConn},
     mail, CONFIG,
 };
 
@@ -136,9 +133,7 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c
         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)?;
diff --git a/src/mail.rs b/src/mail.rs
index e8583468..66c8812e 100644
--- a/src/mail.rs
+++ b/src/mail.rs
@@ -181,11 +181,7 @@ 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 {
-
+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!({

From cc021a47843a10b2923af82113a6d6a7045d2bdf Mon Sep 17 00:00:00 2001
From: Olivier Martin <olivier.martin@illogika.com>
Date: Tue, 27 Apr 2021 21:48:07 -0400
Subject: [PATCH 04/13] project name and links in new email templates

---
 src/static/templates/email/send_2fa_removed_from_org.hbs      | 2 +-
 src/static/templates/email/send_2fa_removed_from_org.html.hbs | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/static/templates/email/send_2fa_removed_from_org.hbs b/src/static/templates/email/send_2fa_removed_from_org.hbs
index b836ff31..95d41b97 100644
--- a/src/static/templates/email/send_2fa_removed_from_org.hbs
+++ b/src/static/templates/email/send_2fa_removed_from_org.hbs
@@ -6,4 +6,4 @@ You have been removed from organization *{{org_name}}* because your account does
 You can enable Two-step Login in your account settings.
 
 ===
-Github: https://github.com/dani-garcia/bitwarden_rs
+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
index 56dfc191..e881fd99 100644
--- a/src/static/templates/email/send_2fa_removed_from_org.html.hbs
+++ b/src/static/templates/email/send_2fa_removed_from_org.html.hbs
@@ -4,7 +4,7 @@ Removed from {{{org_name}}}
    <head>
       <meta name="viewport" content="width=device-width" />
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-      <title>Bitwarden_rs</title>
+      <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">
@@ -113,7 +113,7 @@ Removed from {{{org_name}}}
                               <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/bitwarden_rs" 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>
+                                        <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>

From a622b4d2fb3134f94f156c09365d3cd243d6b4f0 Mon Sep 17 00:00:00 2001
From: Kaito Udagawa <umireon@gmail.com>
Date: Thu, 8 Jul 2021 00:58:58 +0900
Subject: [PATCH 05/13] Add Edge's frame-ancestors

Edge's frame-ancestors are required for Edge extension to do WebAuthn.
---
 src/util.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/util.rs b/src/util.rs
index 8512bc7b..14e8c52e 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -29,7 +29,7 @@ impl Fairing for AppHeaders {
         res.set_raw_header("X-Content-Type-Options", "nosniff");
         res.set_raw_header("X-XSS-Protection", "1; mode=block");
         let csp = format!(
-            "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb moz-extension://* {};",
+            "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};",
             CONFIG.allowed_iframe_ancestors()
         );
         res.set_raw_header("Content-Security-Policy", csp);

From 13598c098f5c941c8dd4d0452ea67750d6a5020a Mon Sep 17 00:00:00 2001
From: Kaito Udagawa <umireon@gmail.com>
Date: Thu, 8 Jul 2021 02:51:11 +0900
Subject: [PATCH 06/13] Add links to browser extensions

---
 src/util.rs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/util.rs b/src/util.rs
index 14e8c52e..ffe77e99 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -29,6 +29,9 @@ impl Fairing for AppHeaders {
         res.set_raw_header("X-Content-Type-Options", "nosniff");
         res.set_raw_header("X-XSS-Protection", "1; mode=block");
         let csp = format!(
+            // Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb
+            // Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US
+            // Firefox Browser Add-ons: https://addons.mozilla.org/ja/firefox/addon/bitwarden-password-manager/
             "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};",
             CONFIG.allowed_iframe_ancestors()
         );

From c640abbcd71bfb9758cfe1be597a63323f6afef0 Mon Sep 17 00:00:00 2001
From: Kaito Udagawa <umireon@gmail.com>
Date: Thu, 8 Jul 2021 02:55:58 +0900
Subject: [PATCH 07/13] Update src/util.rs

Co-authored-by: William Desportes <williamdes@wdes.fr>
---
 src/util.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/util.rs b/src/util.rs
index ffe77e99..a397ed6d 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -31,7 +31,7 @@ impl Fairing for AppHeaders {
         let csp = format!(
             // Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb
             // Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US
-            // Firefox Browser Add-ons: https://addons.mozilla.org/ja/firefox/addon/bitwarden-password-manager/
+            // Firefox Browser Add-ons: https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/
             "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};",
             CONFIG.allowed_iframe_ancestors()
         );

From 8ee5d51bd47279d5b23c409744fab6614af0e918 Mon Sep 17 00:00:00 2001
From: Jeremy Lin <jeremy.lin@gmail.com>
Date: Sat, 10 Jul 2021 01:20:37 -0700
Subject: [PATCH 08/13] Disable `show_password_hint` by default

A setting that provides unauthenticated access to potentially sensitive data
shouldn't be enabled by default.
---
 .env.template | 6 ++++--
 src/config.rs | 7 ++++---
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/.env.template b/.env.template
index 530a6a01..1662080e 100644
--- a/.env.template
+++ b/.env.template
@@ -210,8 +210,10 @@
 ## The change only applies when the password is changed
 # PASSWORD_ITERATIONS=100000
 
-## Whether password hint should be sent into the error response when the client request it
-# SHOW_PASSWORD_HINT=true
+## Controls whether a password hint should be shown directly in the web page if
+## SMTP service is not configured. Not recommended for publicly-accessible instances
+## as this provides unauthenticated access to potentially sensitive data.
+# SHOW_PASSWORD_HINT=false
 
 ## Domain settings
 ## The domain must match the address from where you access the server
diff --git a/src/config.rs b/src/config.rs
index 6b4fce59..347b0c5e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -388,9 +388,10 @@ make_config! {
         /// Password iterations |> Number of server-side passwords hashing iterations.
         /// The changes only apply when a user changes their password. Not recommended to lower the value
         password_iterations:    i32,    true,   def,    100_000;
-        /// Show password hints |> Controls if the password hint should be shown directly in the web page.
-        /// Otherwise, if email is disabled, there is no way to see the password hint
-        show_password_hint:     bool,   true,   def,    true;
+        /// Show password hint |> Controls whether a password hint should be shown directly in the web page
+        /// if SMTP service is not configured. Not recommended for publicly-accessible instances as this
+        /// provides unauthenticated access to potentially sensitive data.
+        show_password_hint:     bool,   true,   def,    false;
 
         /// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
         admin_token:            Pass,   true,   option;

From 88bea44dd81c6fc9755d42d9bee2533db8765c2a Mon Sep 17 00:00:00 2001
From: Jeremy Lin <jeremy.lin@gmail.com>
Date: Sat, 10 Jul 2021 01:21:27 -0700
Subject: [PATCH 09/13] Prevent user enumeration via password hints

When `show_password_hint` is enabled but mail is not configured, the previous
implementation returned a differentiable response for non-existent email
addresses.

Even if mail is enabled, there is a timing side channel since mail is sent
synchronously. Add a randomized sleep to mitigate this somewhat.
---
 src/api/core/accounts.rs | 53 ++++++++++++++++++++++++++++------------
 1 file changed, 37 insertions(+), 16 deletions(-)

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
index 3888075b..b0c17cfe 100644
--- a/src/api/core/accounts.rs
+++ b/src/api/core/accounts.rs
@@ -576,24 +576,45 @@ struct PasswordHintData {
 
 #[post("/accounts/password-hint", data = "<data>")]
 fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult {
-    let data: PasswordHintData = data.into_inner().data;
-
-    let hint = match User::find_by_mail(&data.Email, &conn) {
-        Some(user) => user.password_hint,
-        None => return Ok(()),
-    };
-
-    if CONFIG.mail_enabled() {
-        mail::send_password_hint(&data.Email, hint)?;
-    } else if CONFIG.show_password_hint() {
-        if let Some(hint) = hint {
-            err!(format!("Your password hint is: {}", &hint));
-        } else {
-            err!("Sorry, you have no password hint...");
-        }
+    if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() {
+        err!("This server is not configured to provide password hints.");
     }
 
-    Ok(())
+    const NO_HINT: &str = "Sorry, you have no password hint...";
+
+    let data: PasswordHintData = data.into_inner().data;
+    let email = &data.Email;
+
+    match User::find_by_mail(email, &conn) {
+        None => {
+            // To prevent user enumeration, act as if the user exists.
+            if CONFIG.mail_enabled() {
+                // There is still a timing side channel here in that the code
+                // paths that send mail take noticeably longer than ones that
+                // don't. Add a randomized sleep to mitigate this somewhat.
+                use rand::{thread_rng, Rng};
+                let mut rng = thread_rng();
+                let base = 1000;
+                let delta: i32 = 100;
+                let sleep_ms = (base + rng.gen_range(-delta..=delta)) as u64;
+                std::thread::sleep(std::time::Duration::from_millis(sleep_ms));
+                Ok(())
+            } else {
+                err!(NO_HINT);
+            }
+        }
+        Some(user) => {
+            let hint: Option<String> = user.password_hint;
+            if CONFIG.mail_enabled() {
+                mail::send_password_hint(email, hint)?;
+                Ok(())
+            } else if let Some(hint) = hint {
+                err!(format!("Your password hint is: {}", hint));
+            } else {
+                err!(NO_HINT);
+            }
+        }
+    }
 }
 
 #[derive(Deserialize)]

From 6ea95d1ede727942e4677cae8c80545123b98e81 Mon Sep 17 00:00:00 2001
From: BlackDex <black.dex@gmail.com>
Date: Tue, 13 Jul 2021 15:17:03 +0200
Subject: [PATCH 10/13] Updated attachment limit descriptions

The user and org attachment limit use `size` as wording while it should
have been `storage` since it isn't per attachment, but the sum of all attachments.

- Changed the wording in the config/env
- Changed the wording of the error messages.

Resolves #1818
---
 .env.template           | 10 ++++++----
 src/api/core/ciphers.rs |  6 +++---
 src/api/core/sends.rs   |  4 ++--
 src/config.rs           |  4 ++--
 4 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/.env.template b/.env.template
index 530a6a01..d9410b26 100644
--- a/.env.template
+++ b/.env.template
@@ -194,11 +194,13 @@
 ## Name shown in the invitation emails that don't come from a specific organization
 # INVITATION_ORG_NAME=Vaultwarden
 
-## Per-organization attachment limit (KB)
-## Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more
+## Per-organization attachment storage limit (KB)
+## Max kilobytes of attachment storage allowed per organization.
+## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization.
 # ORG_ATTACHMENT_LIMIT=
-## Per-user attachment limit (KB).
-## Limit in kilobytes for a users attachments, once the limit is exceeded it won't be possible to upload more
+## Per-user attachment storage limit (KB)
+## Max kilobytes of attachment storage allowed per user.
+## When this limit is reached, the user will not be allowed to upload further attachments.
 # USER_ATTACHMENT_LIMIT=
 
 ## Number of days to wait before auto-deleting a trashed item.
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
index db455231..f9133bec 100644
--- a/src/api/core/ciphers.rs
+++ b/src/api/core/ciphers.rs
@@ -871,7 +871,7 @@ fn save_attachment(
             Some(limit_kb) => {
                 let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, conn) + size_adjust;
                 if left <= 0 {
-                    err_discard!("Attachment size limit reached! Delete some files to open space", data)
+                    err_discard!("Attachment storage limit reached! Delete some attachments to free up space", data)
                 }
                 Some(left as u64)
             }
@@ -883,7 +883,7 @@ fn save_attachment(
             Some(limit_kb) => {
                 let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, conn) + size_adjust;
                 if left <= 0 {
-                    err_discard!("Attachment size limit reached! Delete some files to open space", data)
+                    err_discard!("Attachment storage limit reached! Delete some attachments to free up space", data)
                 }
                 Some(left as u64)
             }
@@ -937,7 +937,7 @@ fn save_attachment(
                                 return;
                             }
                             SaveResult::Partial(_, reason) => {
-                                error = Some(format!("Attachment size limit exceeded with this file: {:?}", reason));
+                                error = Some(format!("Attachment storage limit exceeded with this file: {:?}", reason));
                                 return;
                             }
                             SaveResult::Error(e) => {
diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs
index 13cd300e..935fbd67 100644
--- a/src/api/core/sends.rs
+++ b/src/api/core/sends.rs
@@ -173,7 +173,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
         Some(limit_kb) => {
             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn);
             if left <= 0 {
-                err!("Attachment size limit reached! Delete some files to open space")
+                err!("Attachment storage limit reached! Delete some attachments to free up space")
             }
             std::cmp::Ord::max(left as u64, SIZE_110_MB)
         }
@@ -205,7 +205,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
         }
         SaveResult::Partial(_, reason) => {
             std::fs::remove_file(&file_path).ok();
-            err!(format!("Attachment size limit exceeded with this file: {:?}", reason));
+            err!(format!("Attachment storage limit exceeded with this file: {:?}", reason));
         }
         SaveResult::Error(e) => {
             std::fs::remove_file(&file_path).ok();
diff --git a/src/config.rs b/src/config.rs
index 6b4fce59..525d5991 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -356,9 +356,9 @@ make_config! {
         /// HIBP Api Key |> HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
         hibp_api_key:           Pass,   true,   option;
 
-        /// Per-user attachment limit (KB) |> Limit in kilobytes for a users attachments, once the limit is exceeded it won't be possible to upload more
+        /// Per-user attachment storage limit (KB) |> Max kilobytes of attachment storage allowed per user. When this limit is reached, the user will not be allowed to upload further attachments.
         user_attachment_limit:  i64,    true,   option;
-        /// Per-organization attachment limit (KB) |> Limit in kilobytes for an organization attachments, once the limit is exceeded it won't be possible to upload more
+        /// Per-organization attachment storage limit (KB) |> Max kilobytes of attachment storage allowed per org. When this limit is reached, org members will not be allowed to upload further attachments for ciphers owned by that org.
         org_attachment_limit:   i64,    true,   option;
 
         /// Trash auto-delete days |> Number of days to wait before auto-deleting a trashed item.

From e5ec245626e4a4d232b6a709338fe1e9811845e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garc=C3=ADa?=
 <dani-garcia@users.noreply.github.com>
Date: Thu, 15 Jul 2021 19:15:55 +0200
Subject: [PATCH 11/13] Protect namedfile against path traversal, rocket only
 does it for pathbuf

---
 src/api/core/sends.rs |  3 ++-
 src/api/web.rs        |  4 ++--
 src/util.rs           | 33 ++++++++++++++++++++++++++++++++-
 3 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs
index 13cd300e..ab0c7a36 100644
--- a/src/api/core/sends.rs
+++ b/src/api/core/sends.rs
@@ -10,6 +10,7 @@ use crate::{
     api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType},
     auth::{Headers, Host},
     db::{models::*, DbConn, DbPool},
+    util::SafeString,
     CONFIG,
 };
 
@@ -335,7 +336,7 @@ fn post_access_file(
 }
 
 #[get("/sends/<send_id>/<file_id>?<t>")]
-fn download_send(send_id: String, file_id: String, t: String) -> Option<NamedFile> {
+fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option<NamedFile> {
     if let Ok(claims) = crate::auth::decode_send(&t) {
         if claims.sub == format!("{}/{}", send_id, file_id) {
             return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).ok();
diff --git a/src/api/web.rs b/src/api/web.rs
index 37950f0c..e543bc00 100644
--- a/src/api/web.rs
+++ b/src/api/web.rs
@@ -4,7 +4,7 @@ use rocket::{http::ContentType, response::content::Content, response::NamedFile,
 use rocket_contrib::json::Json;
 use serde_json::Value;
 
-use crate::{error::Error, util::Cached, CONFIG};
+use crate::{CONFIG, error::Error, util::{Cached, SafeString}};
 
 pub fn routes() -> Vec<Route> {
     // If addding more routes here, consider also adding them to
@@ -56,7 +56,7 @@ fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
 }
 
 #[get("/attachments/<uuid>/<file_id>")]
-fn attachments(uuid: String, file_id: String) -> Option<NamedFile> {
+fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
     NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).ok()
 }
 
diff --git a/src/util.rs b/src/util.rs
index 8512bc7b..9e216d72 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -5,7 +5,8 @@ use std::io::Cursor;
 
 use rocket::{
     fairing::{Fairing, Info, Kind},
-    http::{ContentType, Header, HeaderMap, Method, Status},
+    http::{ContentType, Header, HeaderMap, Method, RawStr, Status},
+    request::FromParam,
     response::{self, Responder},
     Data, Request, Response, Rocket,
 };
@@ -125,6 +126,36 @@ impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
     }
 }
 
+pub struct SafeString(String);
+
+impl std::fmt::Display for SafeString {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl AsRef<Path> for SafeString {
+    #[inline]
+    fn as_ref(&self) -> &Path {
+        Path::new(&self.0)
+    }
+}
+
+impl<'r> FromParam<'r> for SafeString {
+    type Error = ();
+
+    #[inline(always)]
+    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
+        let s = param.percent_decode().map(|cow| cow.into_owned()).map_err(|_| ())?;
+
+        if s.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
+            Ok(SafeString(s))
+        } else {
+            Err(())
+        }
+    }
+}
+
 // Log all the routes from the main paths list, and the attachments endpoint
 // Effectively ignores, any static file route, and the alive endpoint
 const LOGGED_ROUTES: [&str; 6] =

From c546a59c38849b2ece730861cb438c4ce41fb520 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garc=C3=ADa?=
 <dani-garcia@users.noreply.github.com>
Date: Thu, 15 Jul 2021 19:18:16 +0200
Subject: [PATCH 12/13] Dependency updates

---
 Cargo.lock | 70 +++++++++++++++++++++++++-----------------------------
 Cargo.toml |  8 +++----
 2 files changed, 36 insertions(+), 42 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index ab9bd9fe..fc4327df 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -248,9 +248,9 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
 
 [[package]]
 name = "cc"
-version = "1.0.68"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
+checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
 
 [[package]]
 name = "cfg-if"
@@ -323,9 +323,9 @@ dependencies = [
 
 [[package]]
 name = "cookie"
-version = "0.15.0"
+version = "0.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffdf8865bac3d9a3bde5bde9088ca431b11f5d37c7a578b8086af77248b76627"
+checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
 dependencies = [
  "percent-encoding 2.1.0",
  "time 0.2.27",
@@ -354,7 +354,7 @@ version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "55b4ac5559dd39f7bdc516f769cb412b151585d8886d216871a8435ed7f862cd"
 dependencies = [
- "cookie 0.15.0",
+ "cookie 0.15.1",
  "idna 0.2.3",
  "log 0.4.14",
  "publicsuffix 2.1.0",
@@ -873,9 +873,9 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
 
 [[package]]
 name = "handlebars"
-version = "4.0.1"
+version = "4.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2060119114dd8a8bc87facce6384751af8280a7adc8e203c023c95cbb11f5663"
+checksum = "72a0ffab8c36d0436114310c7e10b59b3307e650ddfabf6d006028e29a70c6e6"
 dependencies = [
  "log 0.4.14",
  "pest",
@@ -886,12 +886,6 @@ dependencies = [
  "walkdir",
 ]
 
-[[package]]
-name = "hashbrown"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
-
 [[package]]
 name = "hashbrown"
 version = "0.11.2"
@@ -1008,9 +1002,9 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "0.14.9"
+version = "0.14.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83"
+checksum = "7728a72c4c7d72665fde02204bcbd93b247721025b222ef78606f14513e0fd03"
 dependencies = [
  "bytes 1.0.1",
  "futures-channel",
@@ -1049,7 +1043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
  "bytes 1.0.1",
- "hyper 0.14.9",
+ "hyper 0.14.10",
  "native-tls",
  "tokio",
  "tokio-native-tls",
@@ -1079,19 +1073,19 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "1.6.2"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
 dependencies = [
  "autocfg",
- "hashbrown 0.9.1",
+ "hashbrown",
 ]
 
 [[package]]
 name = "instant"
-version = "0.1.9"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
 dependencies = [
  "cfg-if 1.0.0",
 ]
@@ -1201,9 +1195,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.97"
+version = "0.2.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
+checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
 
 [[package]]
 name = "libsqlite3-sys"
@@ -1670,9 +1664,9 @@ dependencies = [
 
 [[package]]
 name = "parity-ws"
-version = "0.10.0"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e02a625dd75084c2a7024f07c575b61b782f729d18702dabb3cdbf31911dc61"
+checksum = "322d72dfe461b8b9e367d057ceace105379d64d5b03907d23c481ccf3fbf8aa4"
 dependencies = [
  "byteorder",
  "bytes 0.4.12",
@@ -1882,9 +1876,9 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
 
 [[package]]
 name = "pin-utils"
@@ -1972,7 +1966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3ac055aef7cc7a1caefbc65144be879e862467dcd9b8a8d57b64a13e7dce15d"
 dependencies = [
  "byteorder",
- "hashbrown 0.11.2",
+ "hashbrown",
  "idna 0.2.3",
  "psl-types",
 ]
@@ -2209,7 +2203,7 @@ dependencies = [
  "futures-util",
  "http",
  "http-body",
- "hyper 0.14.9",
+ "hyper 0.14.10",
  "hyper-tls",
  "ipnet",
  "js-sys",
@@ -2730,9 +2724,9 @@ dependencies = [
 
 [[package]]
 name = "subtle"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
 [[package]]
 name = "syn"
@@ -2801,18 +2795,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.25"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
+checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.25"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
+checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
 dependencies = [
  "proc-macro2 1.0.27",
  "quote 1.0.9",
@@ -2894,9 +2888,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.7.1"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2"
+checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985"
 dependencies = [
  "autocfg",
  "bytes 1.0.1",
@@ -3149,7 +3143,7 @@ dependencies = [
  "chashmap",
  "chrono",
  "chrono-tz",
- "cookie 0.15.0",
+ "cookie 0.15.1",
  "cookie_store 0.15.0",
  "data-encoding",
  "data-url",
diff --git a/Cargo.toml b/Cargo.toml
index ba302901..e3f5263a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,7 +35,7 @@ rocket_contrib = "=0.5.0-dev"
 reqwest = { version = "0.11.4", features = ["blocking", "json", "gzip", "brotli", "socks", "cookies"] }
 
 # Used for custom short lived cookie jar
-cookie = "0.15.0"
+cookie = "0.15.1"
 cookie_store = "0.15.0"
 bytes = "1.0.1"
 url = "2.2.2"
@@ -93,7 +93,7 @@ jsonwebtoken = "7.2.0"
 
 # U2F library
 u2f = "0.2.0"
-webauthn-rs = "0.3.0-alpha.7"
+webauthn-rs = "=0.3.0-alpha.7"
 
 # Yubico Library
 yubico = { version = "0.10.0", features = ["online-tokio"], default-features = false }
@@ -113,7 +113,7 @@ tracing = { version = "0.1.26", features = ["log"] } # Needed to have lettre tra
 lettre = { version = "0.10.0-rc.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
 
 # Template library
-handlebars = { version = "4.0.1", features = ["dir_source"] }
+handlebars = { version = "4.1.0", features = ["dir_source"] }
 
 # For favicon extraction from main website
 html5ever = "0.25.1"
@@ -122,7 +122,7 @@ regex = { version = "1.5.4", features = ["std", "perf"], default-features = fals
 data-url = "0.1.0"
 
 # Used by U2F, JWT and Postgres
-openssl = "0.10.34"
+openssl = "0.10.35"
 
 # URL encoding library
 percent-encoding = "2.1.0"

From e19420160f24ddb17f2ad708bbba3865ddfd15d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garc=C3=ADa?=
 <dani-garcia@users.noreply.github.com>
Date: Thu, 15 Jul 2021 21:25:46 +0200
Subject: [PATCH 13/13] Simplify 2fa removed email and remove extra table close
 in the footer

---
 src/static/templates/email/email_footer.hbs   |  25 ++--
 .../email/send_2fa_removed_from_org.html.hbs  | 141 ++----------------
 2 files changed, 26 insertions(+), 140 deletions(-)

diff --git a/src/static/templates/email/email_footer.hbs b/src/static/templates/email/email_footer.hbs
index b5aafc39..6d5f7be6 100644
--- a/src/static/templates/email/email_footer.hbs
+++ b/src/static/templates/email/email_footer.hbs
@@ -1,23 +1,22 @@
-                                    </td>
-                                </tr>
-                                </table>
-                              </td>
-                           </tr>
+                           </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%;">
+                     </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 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>
+                                 <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>
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
index e881fd99..6588a320 100644
--- a/src/static/templates/email/send_2fa_removed_from_org.html.hbs
+++ b/src/static/templates/email/send_2fa_removed_from_org.html.hbs
@@ -1,129 +1,16 @@
 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>
+{{> email/email_header }}
+<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>
+{{> email/email_footer }}