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