SMTP integration, send password hint by email.

This commit is contained in:
Jean-Christophe BEGUE 2018-08-15 08:32:19 +02:00
parent f7ffb81d9e
commit 812387e586
5 changed files with 85 additions and 13 deletions

7
.env
View File

@ -41,3 +41,10 @@
# ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app # ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app
# ROCKET_PORT=8000 # ROCKET_PORT=8000
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}
## Mail specific settings, if SMTP_HOST is specified, SMTP_USERNAME and SMTP_PASSWORD are mandatory
# SMTP_HOST=smtp.domain.tld
# SMTP_PORT=587
# SMTP_SSL=true
# SMTP_USERNAME=username
# SMTP_PASSWORD=password

View File

@ -58,6 +58,10 @@ lazy_static = "1.0.1"
num-traits = "0.2.5" num-traits = "0.2.5"
num-derive = "0.2.2" num-derive = "0.2.2"
lettre = "0.8.2"
lettre_email = "0.8.2"
native-tls = "0.1.5"
[patch.crates-io] [patch.crates-io]
# Make jwt use ring 0.11, to match rocket # Make jwt use ring 0.11, to match rocket
jsonwebtoken = { path = "libs/jsonwebtoken" } jsonwebtoken = { path = "libs/jsonwebtoken" }

View File

@ -5,6 +5,7 @@ use db::models::*;
use api::{PasswordData, JsonResult, EmptyResult, JsonUpcase, NumberOrString}; use api::{PasswordData, JsonResult, EmptyResult, JsonUpcase, NumberOrString};
use auth::Headers; use auth::Headers;
use mail;
use CONFIG; use CONFIG;
@ -258,15 +259,23 @@ struct PasswordHintData {
fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult {
let data: PasswordHintData = data.into_inner().data; let data: PasswordHintData = data.into_inner().data;
if !CONFIG.show_password_hint { let user = User::find_by_mail(&data.Email, &conn);
return Ok(()) if user.is_none() {
return Ok(());
} }
match User::find_by_mail(&data.Email, &conn) { let user = user.unwrap();
Some(user) => { let hint = user.password_hint.to_owned().unwrap_or("You don't have any...".to_string());
let hint = user.password_hint.to_owned().unwrap_or_default();
err!(format!("Your password hint is: {}", hint)) if let Some(ref mail_config) = CONFIG.mail {
}, if let Err(e) = mail::send_password_hint(&user.email, &hint, mail_config) {
None => Ok(()), err!(format!("There have been a problem sending the email: {}", e));
} }
}
if !CONFIG.show_password_hint {
err!(format!("Your password hint is: {}", &hint));
}
Ok(())
} }

47
src/mail.rs Normal file
View File

@ -0,0 +1,47 @@
use std::error::Error;
use native_tls::TlsConnector;
use native_tls::{Protocol};
use lettre::{EmailTransport, SmtpTransport, ClientTlsParameters, ClientSecurity};
use lettre::smtp::{ConnectionReuseParameters, SmtpTransportBuilder};
use lettre::smtp::authentication::{Credentials, Mechanism};
use lettre_email::EmailBuilder;
use MailConfig;
fn mailer(config: &MailConfig) -> SmtpTransport {
let client_security = if config.smtp_ssl {
let mut tls_builder = TlsConnector::builder().unwrap();
tls_builder.supported_protocols(&[
Protocol::Tlsv10, Protocol::Tlsv11, Protocol::Tlsv12
]).unwrap();
ClientSecurity::Required(
ClientTlsParameters::new(config.smtp_host.to_owned(), tls_builder.build().unwrap())
)
} else {
ClientSecurity::None
};
SmtpTransportBuilder::new((config.smtp_host.to_owned().as_str(), config.smtp_port), client_security)
.unwrap()
.credentials(Credentials::new(config.smtp_username.to_owned(), config.smtp_password.to_owned()))
.authentication_mechanism(Mechanism::Login)
.smtp_utf8(true)
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
.build()
}
pub fn send_password_hint(address: &str, hint: &str, config: &MailConfig) -> Result<(), String> {
let email = EmailBuilder::new()
.to(address)
.from((config.smtp_from.to_owned(), "Bitwarden-rs"))
.subject("Your Master Password Hint")
.body(hint)
.build().unwrap();
match mailer(config).send(&email) {
Ok(_) => Ok(()),
Err(e) => Err(e.description().to_string()),
}
}

View File

@ -26,6 +26,9 @@ extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate num_derive; extern crate num_derive;
extern crate num_traits; extern crate num_traits;
extern crate lettre;
extern crate lettre_email;
extern crate native_tls;
use std::{env, path::Path, process::{exit, Command}}; use std::{env, path::Path, process::{exit, Command}};
use rocket::Rocket; use rocket::Rocket;
@ -37,6 +40,7 @@ mod api;
mod db; mod db;
mod crypto; mod crypto;
mod auth; mod auth;
mod mail;
fn init_rocket() -> Rocket { fn init_rocket() -> Rocket {
rocket::ignite() rocket::ignite()
@ -155,10 +159,10 @@ lazy_static! {
#[derive(Debug)] #[derive(Debug)]
pub struct MailConfig { pub struct MailConfig {
reply_to_email: Option<String>,
smtp_host: String, smtp_host: String,
smtp_port: u16, smtp_port: u16,
smtp_ssl: bool, smtp_ssl: bool,
smtp_from: String,
smtp_username: String, smtp_username: String,
smtp_password: String, smtp_password: String,
} }
@ -172,22 +176,23 @@ impl MailConfig {
return None return None
} }
let smtp_ssl = util::parse_option_string(env::var("SMTP_SSL").ok()).unwrap_or(false); let smtp_ssl = util::parse_option_string(env::var("SMTP_SSL").ok()).unwrap_or(true);
let smtp_port = util::parse_option_string(env::var("SMTP_PORT").ok()) let smtp_port = util::parse_option_string(env::var("SMTP_PORT").ok())
.unwrap_or_else(|| { .unwrap_or_else(|| {
if smtp_ssl { if smtp_ssl {
465u16 587u16
} else { } else {
25u16 25u16
} }
}); });
Some(MailConfig { Some(MailConfig {
reply_to_email: util::parse_option_string(env::var("REPLY_TO_EMAIL").ok()),
smtp_host: smtp_host.unwrap(), smtp_host: smtp_host.unwrap(),
smtp_port: smtp_port, smtp_port: smtp_port,
smtp_ssl: smtp_ssl, smtp_ssl: smtp_ssl,
// If username or password is not specified, and SMTP support seems to be wanted, smtp_from: util::parse_option_string(env::var("SMTP_FROM").ok())
.unwrap_or("bitwarden@localhost".to_string()),
// If username or password is not specified and SMTP support seems to be wanted,
// don't let the app start: the configuration is clearly incomplete. // don't let the app start: the configuration is clearly incomplete.
smtp_username: util::parse_option_string(env::var("SMTP_USERNAME").ok()) smtp_username: util::parse_option_string(env::var("SMTP_USERNAME").ok())
.unwrap_or_else(|| { .unwrap_or_else(|| {