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 }}