From bc6a53b847a398191f221d8d98829d50b2505e53 Mon Sep 17 00:00:00 2001
From: vpl <vpl@vpl.me>
Date: Mon, 22 Jul 2019 08:26:24 +0200
Subject: [PATCH 1/3] Add new device email when user logs in

---
 src/api/identity.rs                           |  18 ++-
 src/config.rs                                 |   1 +
 src/mail.rs                                   |  27 +++-
 .../templates/email/new_device_logged_in.hbs  |  14 ++
 .../email/new_device_logged_in.html.hbs       | 144 ++++++++++++++++++
 5 files changed, 202 insertions(+), 2 deletions(-)
 create mode 100644 src/static/templates/email/new_device_logged_in.hbs
 create mode 100644 src/static/templates/email/new_device_logged_in.html.hbs

diff --git a/src/api/identity.rs b/src/api/identity.rs
index 3602dc97..74e9fa37 100644
--- 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> {
@@ -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
diff --git a/src/config.rs b/src/config.rs
index 91feb1c6..85d97511 100644
--- 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
index 2b814b54..576cb6b0 100644
--- 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,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))
diff --git a/src/static/templates/email/new_device_logged_in.hbs b/src/static/templates/email/new_device_logged_in.hbs
new file mode 100644
index 00000000..0ef4bb26
--- /dev/null
+++ 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
new file mode 100644
index 00000000..0bf1e7b8
--- /dev/null
+++ 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>

From 60e39a9dd167959fdc6e474c4497853462d847f2 Mon Sep 17 00:00:00 2001
From: vpl <vpl@vpl.me>
Date: Mon, 22 Jul 2019 08:24:19 +0200
Subject: [PATCH 2/3] Move retrieve/new device from connData to separate
 function

---
 src/api/identity.rs | 65 +++++++++++++++++++++++----------------------
 src/mail.rs         |  7 +----
 2 files changed, 34 insertions(+), 38 deletions(-)

diff --git a/src/api/identity.rs b/src/api/identity.rs
index 74e9fa37..b293b7e6 100644
--- a/src/api/identity.rs
+++ b/src/api/identity.rs
@@ -101,38 +101,7 @@ 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");
-
-    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 => {
-            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 mut device = get_device(&data, &conn, &user, &ip)?;
 
     let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?;
 
@@ -161,6 +130,38 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
     Ok(Json(result))
 }
 
+fn get_device(data: &ConnectData, conn: &DbConn, user: &User, ip: &ClientIp) -> Result<Device, crate::error::Error> {
+    // 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)
+        }
+    };
+
+    if CONFIG.mail_enabled() && new_device {
+        mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &device.updated_at, &device.name)?
+    }
+
+    Ok(device)
+}
+
 fn twofactor_auth(
     user_uuid: &str,
     data: &ConnectData,
diff --git a/src/mail.rs b/src/mail.rs
index 576cb6b0..506c85eb 100644
--- a/src/mail.rs
+++ b/src/mail.rs
@@ -137,12 +137,7 @@ 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 {
+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);
 

From df71f57d869d981d56697d255ad3e87630d3629e Mon Sep 17 00:00:00 2001
From: vpl <vpl@vpl.me>
Date: Thu, 25 Jul 2019 20:47:58 +0200
Subject: [PATCH 3/3] Move send device email to end of password login

Send new device email after two factor authentication.
---
 src/api/identity.rs | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/api/identity.rs b/src/api/identity.rs
index b293b7e6..0fbe77ed 100644
--- a/src/api/identity.rs
+++ b/src/api/identity.rs
@@ -101,10 +101,14 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
         )
     }
 
-    let mut device = get_device(&data, &conn, &user, &ip)?;
+    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);
@@ -119,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 {
@@ -130,7 +133,8 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
     Ok(Json(result))
 }
 
-fn get_device(data: &ConnectData, conn: &DbConn, user: &User, ip: &ClientIp) -> Result<Device, crate::error::Error> {
+/// 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");
@@ -155,11 +159,7 @@ fn get_device(data: &ConnectData, conn: &DbConn, user: &User, ip: &ClientIp) ->
         }
     };
 
-    if CONFIG.mail_enabled() && new_device {
-        mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &device.updated_at, &device.name)?
-    }
-
-    Ok(device)
+    (device, new_device)
 }
 
 fn twofactor_auth(