commit 8a21c6df10fc624c2db9ef4219048eeab8dc5c61
parent 05a1137828a69da8ac687127e0ab6ae88c37a348
Author: Daniel García <dani-garcia@users.noreply.github.com>
Date: Tue, 30 Jul 2019 13:18:42 -0400
Merge pull request #541 from vverst/mail-new-device
Add "New Device Logged In From" email
Diffstat:
5 files changed, 216 insertions(+), 20 deletions(-)
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -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> {
@@ -99,27 +101,14 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
)
}
- // On iOS, device_type sends "iOS", on others it sends a number
- let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(0);
- let device_id = data.device_identifier.clone().expect("No device id provided");
- let device_name = data.device_name.clone().expect("No device name provided");
-
- // 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");
- 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),
- };
+ let (mut device, new_device) = get_device(&data, &conn, &user);
let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?;
+ if CONFIG.mail_enabled() && new_device {
+ mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &device.updated_at, &device.name)?
+ }
+
// Common
let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap();
let orgs = UserOrganization::find_by_user(&user.uuid, &conn);
@@ -134,7 +123,6 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
"refresh_token": device.refresh_token,
"Key": user.akey,
"PrivateKey": user.private_key,
- //"TwoFactorToken": "11122233333444555666777888999"
});
if let Some(token) = twofactor_token {
@@ -145,6 +133,35 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
Ok(Json(result))
}
+/// Retrieves an existing device or creates a new device from ConnectData and the User
+fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) {
+ // On iOS, device_type sends "iOS", on others it sends a number
+ let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(0);
+ 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 new_device = false;
+ // Find device or create new
+ let 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");
+ new_device = true;
+ Device::new(device_id, user.uuid.clone(), device_name, device_type)
+ } else {
+ device
+ }
+ }
+ None => {
+ new_device = true;
+ Device::new(device_id, user.uuid.clone(), device_name, device_type)
+ }
+ };
+
+ (device, new_device)
+}
+
fn twofactor_auth(
user_uuid: &str,
data: &ConnectData,
diff --git a/src/config.rs b/src/config.rs
@@ -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");
diff --git a/src/mail.rs b/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,25 @@ 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))
diff --git a/src/static/templates/email/new_device_logged_in.hbs b/src/static/templates/email/new_device_logged_in.hbs
@@ -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>
diff --git a/src/static/templates/email/new_device_logged_in.html.hbs b/src/static/templates/email/new_device_logged_in.html.hbs
@@ -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>