mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 13:51:14 +00:00 
			
		
		
		
	Add new device email when user logs in
This commit is contained in:
		
					parent
					
						
							
								05a1137828
							
						
					
				
			
			
				commit
				
					
						bc6a53b847
					
				
			
		
					 5 changed files with 202 additions and 2 deletions
				
			
		|  | @ -15,6 +15,8 @@ use crate::api::{ApiResult, EmptyResult, JsonResult}; | |||
| 
 | ||||
| use crate::auth::ClientIp; | ||||
| 
 | ||||
| use crate::mail; | ||||
| 
 | ||||
| use crate::CONFIG; | ||||
| 
 | ||||
| pub fn routes() -> Vec<Route> { | ||||
|  | @ -104,20 +106,34 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult | |||
|     let device_id = data.device_identifier.clone().expect("No device id provided"); | ||||
|     let device_name = data.device_name.clone().expect("No device name provided"); | ||||
| 
 | ||||
|     let mut send_email = false; | ||||
|     // Find device or create new
 | ||||
|     let mut device = match Device::find_by_uuid(&device_id, &conn) { | ||||
|         Some(device) => { | ||||
|             // Check if owned device, and recreate if not
 | ||||
|             if device.user_uuid != user.uuid { | ||||
|                 info!("Device exists but is owned by another user. The old device will be discarded"); | ||||
|                 send_email = true; | ||||
|                 Device::new(device_id, user.uuid.clone(), device_name, device_type) | ||||
|             } else { | ||||
|                 device | ||||
|             } | ||||
|         } | ||||
|         None => Device::new(device_id, user.uuid.clone(), device_name, device_type), | ||||
|         None => { | ||||
|             send_email = true; | ||||
|             Device::new(device_id, user.uuid.clone(), device_name, device_type) | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     if CONFIG.mail_enabled() && send_email { | ||||
|         mail::send_new_device_logged_in( | ||||
|             &username, | ||||
|             &ip.ip.to_string(), | ||||
|             &device.updated_at, | ||||
|             &device.name, | ||||
|         )?; | ||||
|     } | ||||
| 
 | ||||
|     let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?; | ||||
| 
 | ||||
|     // Common
 | ||||
|  |  | |||
|  | @ -526,6 +526,7 @@ fn load_templates(path: &str) -> Handlebars { | |||
|     // First register default templates here
 | ||||
|     reg!("email/invite_accepted", ".html"); | ||||
|     reg!("email/invite_confirmed", ".html"); | ||||
|     reg!("email/new_device_logged_in", ".html"); | ||||
|     reg!("email/pw_hint_none", ".html"); | ||||
|     reg!("email/pw_hint_some", ".html"); | ||||
|     reg!("email/send_org_invite", ".html"); | ||||
|  |  | |||
							
								
								
									
										27
									
								
								src/mail.rs
									
										
									
									
									
								
							
							
						
						
									
										27
									
								
								src/mail.rs
									
										
									
									
									
								
							|  | @ -3,13 +3,14 @@ use lettre::smtp::ConnectionReuseParameters; | |||
| use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, SmtpTransport, Transport}; | ||||
| use lettre_email::{EmailBuilder, MimeMultipartType, PartBuilder}; | ||||
| use native_tls::{Protocol, TlsConnector}; | ||||
| use quoted_printable::encode_to_str; | ||||
| use percent_encoding::{percent_encode, DEFAULT_ENCODE_SET}; | ||||
| use quoted_printable::encode_to_str; | ||||
| 
 | ||||
| use crate::api::EmptyResult; | ||||
| use crate::auth::{encode_jwt, generate_invite_claims}; | ||||
| use crate::error::Error; | ||||
| use crate::CONFIG; | ||||
| use chrono::NaiveDateTime; | ||||
| 
 | ||||
| fn mailer() -> SmtpTransport { | ||||
|     let host = CONFIG.smtp_host().unwrap(); | ||||
|  | @ -136,6 +137,30 @@ pub fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult { | |||
|     send_email(&address, &subject, &body_html, &body_text) | ||||
| } | ||||
| 
 | ||||
| pub fn send_new_device_logged_in( | ||||
|     address: &str, | ||||
|     ip: &str, | ||||
|     dt: &NaiveDateTime, | ||||
|     device: &str, | ||||
| ) -> EmptyResult { | ||||
|     use crate::util::upcase_first; | ||||
|     let device = upcase_first(device); | ||||
| 
 | ||||
|     let datetime = dt.format("%A, %B %_d, %Y at %H:%M").to_string(); | ||||
| 
 | ||||
|     let (subject, body_html, body_text) = get_text( | ||||
|         "email/new_device_logged_in", | ||||
|         json!({ | ||||
|             "url": CONFIG.domain(), | ||||
|             "ip": ip, | ||||
|             "device": device, | ||||
|             "datetime": datetime, | ||||
|         }), | ||||
|     )?; | ||||
| 
 | ||||
|     send_email(&address, &subject, &body_html, &body_text) | ||||
| } | ||||
| 
 | ||||
| fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) -> EmptyResult { | ||||
|     let html = PartBuilder::new() | ||||
|         .body(encode_to_str(body_html)) | ||||
|  |  | |||
							
								
								
									
										14
									
								
								src/static/templates/email/new_device_logged_in.hbs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/static/templates/email/new_device_logged_in.hbs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| New Device Logged In From {{device}} | ||||
| <!----------------> | ||||
| <html> | ||||
| <p> | ||||
|    Your account was just logged into from a new device. | ||||
| 
 | ||||
|    Date: {{datetime}} | ||||
|    IP Address: {{ip}} | ||||
|    Device Type: {{device}} | ||||
| 
 | ||||
|    You can deauthorize all devices that have access to your account from the | ||||
|     <a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions. | ||||
| </p> | ||||
| </html> | ||||
							
								
								
									
										144
									
								
								src/static/templates/email/new_device_logged_in.html.hbs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/static/templates/email/new_device_logged_in.html.hbs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | |||
| New Device Logged In From {{device}} | ||||
| <!----------------> | ||||
| <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_images/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: 600px;"> | ||||
|                   <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;" valign="top"> | ||||
|                                           Your account was just logged into from a new device. | ||||
|                                        </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" 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;" valign="top"> | ||||
|                                           <b>Date</b>: {{datetime}} | ||||
|                                        </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" 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;" valign="top"> | ||||
|                                            <b>IP Address:</b> {{ip}} | ||||
|                                        </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" 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;" valign="top"> | ||||
|                                            <b>Device Type:</b> {{device}} | ||||
|                                        </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;" valign="top"> | ||||
|                                            You can deauthorize all devices that have access to your account from the <a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions. | ||||
|                                        </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_images/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> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue