geforkt von mirrored/vaultwarden
Use local time in email notifications for new device logins
In this implementation, the `TZ` environment variable must be set in order for the formatted output to use a more user-friendly time zone abbreviation (e.g., `UTC`). Otherwise, the output uses the time zone's UTC offset (e.g., `+00:00`).
Dieser Commit ist enthalten in:
Ursprung
77e47ddd1f
Commit
a28ebcb401
4 geänderte Dateien mit 52 neuen und 12 gelöschten Zeilen
24
Cargo.lock
generiert
24
Cargo.lock
generiert
|
@ -132,6 +132,7 @@ dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"chashmap",
|
"chashmap",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"chrono-tz",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"data-url",
|
"data-url",
|
||||||
"diesel",
|
"diesel",
|
||||||
|
@ -275,15 +276,25 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.12"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0fee792e164f78f5fe0c296cc2eb3688a2ca2b70cdff33040922d298203f0c4"
|
checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"time 0.1.43",
|
"time 0.1.43",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono-tz"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65d96be7c3e993c9ee4356442db24ba364c924b6b8331866be6b6952bfe74b9d"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"parse-zoneinfo",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.33.1"
|
version = "2.33.1"
|
||||||
|
@ -1618,6 +1629,15 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parse-zoneinfo"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pear"
|
name = "pear"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
|
|
@ -62,8 +62,9 @@ ring = "0.16.15"
|
||||||
# UUID generation
|
# UUID generation
|
||||||
uuid = { version = "0.8.1", features = ["v4"] }
|
uuid = { version = "0.8.1", features = ["v4"] }
|
||||||
|
|
||||||
# Date and time librar for Rust
|
# Date and time libraries
|
||||||
chrono = "0.4.12"
|
chrono = "0.4.13"
|
||||||
|
chrono-tz = "0.5.2"
|
||||||
time = "0.2.16"
|
time = "0.2.16"
|
||||||
|
|
||||||
# TOTP library
|
# TOTP library
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use chrono::Utc;
|
use chrono::Local;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use rocket::request::{Form, FormItems, FromForm};
|
use rocket::request::{Form, FormItems, FromForm};
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
@ -97,8 +97,10 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let now = Local::now();
|
||||||
|
|
||||||
if user.verified_at.is_none() && CONFIG.mail_enabled() && CONFIG.signups_verify() {
|
if user.verified_at.is_none() && CONFIG.mail_enabled() && CONFIG.signups_verify() {
|
||||||
let now = Utc::now().naive_utc();
|
let now = now.naive_utc();
|
||||||
if user.last_verifying_at.is_none() || now.signed_duration_since(user.last_verifying_at.unwrap()).num_seconds() > CONFIG.signups_verify_resend_time() as i64 {
|
if user.last_verifying_at.is_none() || now.signed_duration_since(user.last_verifying_at.unwrap()).num_seconds() > CONFIG.signups_verify_resend_time() as i64 {
|
||||||
let resend_limit = CONFIG.signups_verify_resend_limit() as i32;
|
let resend_limit = CONFIG.signups_verify_resend_limit() as i32;
|
||||||
if resend_limit == 0 || user.login_verify_count < resend_limit {
|
if resend_limit == 0 || user.login_verify_count < resend_limit {
|
||||||
|
@ -130,7 +132,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
|
||||||
let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &ip, &conn)?;
|
let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &ip, &conn)?;
|
||||||
|
|
||||||
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(), &device.updated_at, &device.name) {
|
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name) {
|
||||||
error!("Error sending new device email: {:#?}", e);
|
error!("Error sending new device email: {:#?}", e);
|
||||||
|
|
||||||
if CONFIG.require_device_email() {
|
if CONFIG.require_device_email() {
|
||||||
|
|
27
src/mail.rs
27
src/mail.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use lettre::message::{header, Mailbox, Message, MultiPart, SinglePart};
|
use lettre::message::{header, Mailbox, Message, MultiPart, SinglePart};
|
||||||
|
@ -12,7 +13,9 @@ use crate::api::EmptyResult;
|
||||||
use crate::auth::{encode_jwt, generate_delete_claims, generate_invite_claims, generate_verify_email_claims};
|
use crate::auth::{encode_jwt, generate_delete_claims, generate_invite_claims, generate_verify_email_claims};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
|
||||||
fn mailer() -> SmtpTransport {
|
fn mailer() -> SmtpTransport {
|
||||||
let host = CONFIG.smtp_host().unwrap();
|
let host = CONFIG.smtp_host().unwrap();
|
||||||
|
@ -87,6 +90,22 @@ fn get_template(template_name: &str, data: &serde_json::Value) -> Result<(String
|
||||||
Ok((subject, body))
|
Ok((subject, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format_datetime(dt: &DateTime<Local>) -> String {
|
||||||
|
let fmt = "%A, %B %_d, %Y at %r %Z";
|
||||||
|
|
||||||
|
// With a DateTime<Local>, `%Z` formats as the time zone's UTC offset
|
||||||
|
// (e.g., `+00:00`). If the `TZ` environment variable is set, try to
|
||||||
|
// format as a time zone abbreviation instead (e.g., `UTC`).
|
||||||
|
if let Ok(tz) = env::var("TZ") {
|
||||||
|
if let Ok(tz) = tz.parse::<Tz>() {
|
||||||
|
return dt.with_timezone(&tz).format(fmt).to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, fall back to just displaying the UTC offset.
|
||||||
|
dt.format(fmt).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_password_hint(address: &str, hint: Option<String>) -> EmptyResult {
|
pub fn send_password_hint(address: &str, hint: Option<String>) -> EmptyResult {
|
||||||
let template_name = if hint.is_some() {
|
let template_name = if hint.is_some() {
|
||||||
"email/pw_hint_some"
|
"email/pw_hint_some"
|
||||||
|
@ -217,19 +236,17 @@ pub fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult {
|
||||||
send_email(address, &subject, &body_html, &body_text)
|
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: &DateTime<Local>, device: &str) -> EmptyResult {
|
||||||
use crate::util::upcase_first;
|
use crate::util::upcase_first;
|
||||||
let device = upcase_first(device);
|
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(
|
let (subject, body_html, body_text) = get_text(
|
||||||
"email/new_device_logged_in",
|
"email/new_device_logged_in",
|
||||||
json!({
|
json!({
|
||||||
"url": CONFIG.domain(),
|
"url": CONFIG.domain(),
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
"device": device,
|
"device": device,
|
||||||
"datetime": datetime,
|
"datetime": format_datetime(dt),
|
||||||
}),
|
}),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
Laden …
In neuem Issue referenzieren