Browse Source

Actually use Device Type for mails (#4916)

- match Bitwarden behaviour
- add a different segment in mails for Device Name
pull/4968/head
Daniel 2 weeks ago
committed by GitHub
parent
commit
21efc0800d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      migrations/mysql/2024-09-04-091351_use_device_type_for_mails/down.sql
  2. 1
      migrations/mysql/2024-09-04-091351_use_device_type_for_mails/up.sql
  3. 1
      migrations/postgresql/2024-09-04-091351_use_device_type_for_mails/down.sql
  4. 1
      migrations/postgresql/2024-09-04-091351_use_device_type_for_mails/up.sql
  5. 1
      migrations/sqlite/2024-09-04-091351_use_device_type_for_mails/down.sql
  6. 1
      migrations/sqlite/2024-09-04-091351_use_device_type_for_mails/up.sql
  7. 8
      src/api/core/two_factor/mod.rs
  8. 6
      src/api/identity.rs
  9. 3
      src/db/models/two_factor_incomplete.rs
  10. 1
      src/db/schemas/mysql/schema.rs
  11. 1
      src/db/schemas/postgresql/schema.rs
  12. 1
      src/db/schemas/sqlite/schema.rs
  13. 20
      src/mail.rs
  14. 5
      src/static/templates/email/incomplete_2fa_login.hbs
  15. 9
      src/static/templates/email/incomplete_2fa_login.html.hbs
  16. 5
      src/static/templates/email/new_device_logged_in.hbs
  17. 9
      src/static/templates/email/new_device_logged_in.html.hbs

1
migrations/mysql/2024-09-04-091351_use_device_type_for_mails/down.sql

@ -0,0 +1 @@
ALTER TABLE `twofactor_incomplete` DROP COLUMN `device_type`;

1
migrations/mysql/2024-09-04-091351_use_device_type_for_mails/up.sql

@ -0,0 +1 @@
ALTER TABLE `twofactor_incomplete` ADD COLUMN `device_type` INTEGER NOT NULL;

1
migrations/postgresql/2024-09-04-091351_use_device_type_for_mails/down.sql

@ -0,0 +1 @@
ALTER TABLE `twofactor_incomplete` DROP COLUMN `device_type`;

1
migrations/postgresql/2024-09-04-091351_use_device_type_for_mails/up.sql

@ -0,0 +1 @@
ALTER TABLE `twofactor_incomplete` ADD COLUMN `device_type` INTEGER NOT NULL;

1
migrations/sqlite/2024-09-04-091351_use_device_type_for_mails/down.sql

@ -0,0 +1 @@
ALTER TABLE `twofactor_incomplete` DROP COLUMN `device_type`;

1
migrations/sqlite/2024-09-04-091351_use_device_type_for_mails/up.sql

@ -0,0 +1 @@
ALTER TABLE `twofactor_incomplete` ADD COLUMN `device_type` INTEGER NOT NULL;

8
src/api/core/two_factor/mod.rs

@ -269,7 +269,13 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
"User {} did not complete a 2FA login within the configured time limit. IP: {}", "User {} did not complete a 2FA login within the configured time limit. IP: {}",
user.email, login.ip_address user.email, login.ip_address
); );
match mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name) match mail::send_incomplete_2fa_login(
&user.email,
&login.ip_address,
&login.login_time,
&login.device_name,
&DeviceType::from_i32(login.device_type).to_string(),
)
.await .await
{ {
Ok(_) => { Ok(_) => {

6
src/api/identity.rs

@ -265,7 +265,7 @@ async fn _password_login(
let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?; let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?;
if CONFIG.mail_enabled() && new_device { if CONFIG.mail_enabled() && new_device {
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name).await { if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device).await {
error!("Error sending new device email: {:#?}", e); error!("Error sending new device email: {:#?}", e);
if CONFIG.require_device_email() { if CONFIG.require_device_email() {
@ -421,7 +421,7 @@ async fn _user_api_key_login(
if CONFIG.mail_enabled() && new_device { if CONFIG.mail_enabled() && new_device {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name).await { if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device).await {
error!("Error sending new device email: {:#?}", e); error!("Error sending new device email: {:#?}", e);
if CONFIG.require_device_email() { if CONFIG.require_device_email() {
@ -535,7 +535,7 @@ async fn twofactor_auth(
return Ok(None); return Ok(None);
} }
TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, ip, conn).await?; TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, device.atype, ip, conn).await?;
let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect(); let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect();
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one

3
src/db/models/two_factor_incomplete.rs

@ -13,6 +13,7 @@ db_object! {
// must complete 2FA login before being added into the devices table. // must complete 2FA login before being added into the devices table.
pub device_uuid: String, pub device_uuid: String,
pub device_name: String, pub device_name: String,
pub device_type: i32,
pub login_time: NaiveDateTime, pub login_time: NaiveDateTime,
pub ip_address: String, pub ip_address: String,
} }
@ -23,6 +24,7 @@ impl TwoFactorIncomplete {
user_uuid: &str, user_uuid: &str,
device_uuid: &str, device_uuid: &str,
device_name: &str, device_name: &str,
device_type: i32,
ip: &ClientIp, ip: &ClientIp,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
@ -44,6 +46,7 @@ impl TwoFactorIncomplete {
twofactor_incomplete::user_uuid.eq(user_uuid), twofactor_incomplete::user_uuid.eq(user_uuid),
twofactor_incomplete::device_uuid.eq(device_uuid), twofactor_incomplete::device_uuid.eq(device_uuid),
twofactor_incomplete::device_name.eq(device_name), twofactor_incomplete::device_name.eq(device_name),
twofactor_incomplete::device_type.eq(device_type),
twofactor_incomplete::login_time.eq(Utc::now().naive_utc()), twofactor_incomplete::login_time.eq(Utc::now().naive_utc()),
twofactor_incomplete::ip_address.eq(ip.ip.to_string()), twofactor_incomplete::ip_address.eq(ip.ip.to_string()),
)) ))

1
src/db/schemas/mysql/schema.rs

@ -169,6 +169,7 @@ table! {
user_uuid -> Text, user_uuid -> Text,
device_uuid -> Text, device_uuid -> Text,
device_name -> Text, device_name -> Text,
device_type -> Integer,
login_time -> Timestamp, login_time -> Timestamp,
ip_address -> Text, ip_address -> Text,
} }

1
src/db/schemas/postgresql/schema.rs

@ -169,6 +169,7 @@ table! {
user_uuid -> Text, user_uuid -> Text,
device_uuid -> Text, device_uuid -> Text,
device_name -> Text, device_name -> Text,
device_type -> Integer,
login_time -> Timestamp, login_time -> Timestamp,
ip_address -> Text, ip_address -> Text,
} }

1
src/db/schemas/sqlite/schema.rs

@ -169,6 +169,7 @@ table! {
user_uuid -> Text, user_uuid -> Text,
device_uuid -> Text, device_uuid -> Text,
device_name -> Text, device_name -> Text,
device_type -> Integer,
login_time -> Timestamp, login_time -> Timestamp,
ip_address -> Text, ip_address -> Text,
} }

20
src/mail.rs

@ -17,7 +17,7 @@ use crate::{
encode_jwt, generate_delete_claims, generate_emergency_access_invite_claims, generate_invite_claims, encode_jwt, generate_delete_claims, generate_emergency_access_invite_claims, generate_invite_claims,
generate_verify_email_claims, generate_verify_email_claims,
}, },
db::models::User, db::models::{Device, DeviceType, User},
error::Error, error::Error,
CONFIG, CONFIG,
}; };
@ -442,9 +442,8 @@ pub async fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult
send_email(address, &subject, body_html, body_text).await send_email(address, &subject, body_html, body_text).await
} }
pub async fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult { pub async fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTime, device: &Device) -> EmptyResult {
use crate::util::upcase_first; use crate::util::upcase_first;
let device = upcase_first(device);
let fmt = "%A, %B %_d, %Y at %r %Z"; let fmt = "%A, %B %_d, %Y at %r %Z";
let (subject, body_html, body_text) = get_text( let (subject, body_html, body_text) = get_text(
@ -453,7 +452,8 @@ pub async fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTi
"url": CONFIG.domain(), "url": CONFIG.domain(),
"img_src": CONFIG._smtp_img_src(), "img_src": CONFIG._smtp_img_src(),
"ip": ip, "ip": ip,
"device": device, "device_name": upcase_first(&device.name),
"device_type": DeviceType::from_i32(device.atype).to_string(),
"datetime": crate::util::format_naive_datetime_local(dt, fmt), "datetime": crate::util::format_naive_datetime_local(dt, fmt),
}), }),
)?; )?;
@ -461,9 +461,14 @@ pub async fn send_new_device_logged_in(address: &str, ip: &str, dt: &NaiveDateTi
send_email(address, &subject, body_html, body_text).await send_email(address, &subject, body_html, body_text).await
} }
pub async fn send_incomplete_2fa_login(address: &str, ip: &str, dt: &NaiveDateTime, device: &str) -> EmptyResult { pub async fn send_incomplete_2fa_login(
address: &str,
ip: &str,
dt: &NaiveDateTime,
device_name: &str,
device_type: &str,
) -> EmptyResult {
use crate::util::upcase_first; use crate::util::upcase_first;
let device = upcase_first(device);
let fmt = "%A, %B %_d, %Y at %r %Z"; let fmt = "%A, %B %_d, %Y at %r %Z";
let (subject, body_html, body_text) = get_text( let (subject, body_html, body_text) = get_text(
@ -472,7 +477,8 @@ pub async fn send_incomplete_2fa_login(address: &str, ip: &str, dt: &NaiveDateTi
"url": CONFIG.domain(), "url": CONFIG.domain(),
"img_src": CONFIG._smtp_img_src(), "img_src": CONFIG._smtp_img_src(),
"ip": ip, "ip": ip,
"device": device, "device_name": upcase_first(device_name),
"device_type": device_type,
"datetime": crate::util::format_naive_datetime_local(dt, fmt), "datetime": crate::util::format_naive_datetime_local(dt, fmt),
"time_limit": CONFIG.incomplete_2fa_time_limit(), "time_limit": CONFIG.incomplete_2fa_time_limit(),
}), }),

5
src/static/templates/email/incomplete_2fa_login.hbs

@ -1,10 +1,11 @@
Incomplete Two-Step Login From {{{device}}} Incomplete Two-Step Login From {{{device_name}}}
<!----------------> <!---------------->
Someone attempted to log into your account with the correct master password, but did not provide the correct token or action required to complete the two-step login process within {{time_limit}} minutes of the initial login attempt. Someone attempted to log into your account with the correct master password, but did not provide the correct token or action required to complete the two-step login process within {{time_limit}} minutes of the initial login attempt.
* Date: {{datetime}} * Date: {{datetime}}
* IP Address: {{ip}} * IP Address: {{ip}}
* Device Type: {{device}} * Device Name: {{device_name}}
* Device Type: {{device_type}}
If this was not you or someone you authorized, then you should change your master password as soon as possible, as it is likely to be compromised. If this was not you or someone you authorized, then you should change your master password as soon as possible, as it is likely to be compromised.
{{> email/email_footer_text }} {{> email/email_footer_text }}

9
src/static/templates/email/incomplete_2fa_login.html.hbs

@ -1,4 +1,4 @@
Incomplete Two-Step Login From {{{device}}} Incomplete Two-Step Login From {{{device_name}}}
<!----------------> <!---------------->
{{> email/email_header }} {{> 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;"> <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;">
@ -19,7 +19,12 @@ Incomplete Two-Step Login From {{{device}}}
</tr> </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;"> <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"> <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}} <b>Device Name:</b> {{device_name}}
</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_type}}
</td> </td>
</tr> </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;"> <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;">

5
src/static/templates/email/new_device_logged_in.hbs

@ -1,10 +1,11 @@
New Device Logged In From {{{device}}} New Device Logged In From {{{device_name}}}
<!----------------> <!---------------->
Your account was just logged into from a new device. Your account was just logged into from a new device.
* Date: {{datetime}} * Date: {{datetime}}
* IP Address: {{ip}} * IP Address: {{ip}}
* Device Type: {{device}} * Device Name: {{device_name}}
* Device Type: {{device_type}}
You can deauthorize all devices that have access to your account from the web vault ( {{url}} ) under Settings > My Account > Deauthorize Sessions. You can deauthorize all devices that have access to your account from the web vault ( {{url}} ) under Settings > My Account > Deauthorize Sessions.
{{> email/email_footer_text }} {{> email/email_footer_text }}

9
src/static/templates/email/new_device_logged_in.html.hbs

@ -1,4 +1,4 @@
New Device Logged In From {{{device}}} New Device Logged In From {{{device_name}}}
<!----------------> <!---------------->
{{> email/email_header }} {{> 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;"> <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;">
@ -19,7 +19,12 @@ New Device Logged In From {{{device}}}
</tr> </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;"> <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"> <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}} <b>Device Name:</b> {{device_name}}
</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_type}}
</td> </td>
</tr> </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;"> <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;">

Loading…
Cancel
Save