Introduce mail::Service.
- This conveniently combines From and MailTemplates, and will allow use to introduce new transports.
This commit is contained in:
parent
e735758e50
commit
4459dc9bac
155
src/mail.rs
155
src/mail.rs
|
@ -14,78 +14,93 @@ pub struct Context {
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_mail<T>(
|
pub struct Service {
|
||||||
to: &Email, subject: &str, mail_templates: &Handlebars, template: &str,
|
from: String,
|
||||||
from: &str, ctx: T,
|
templates: Handlebars,
|
||||||
) -> Result<()>
|
transport: Transport,
|
||||||
where
|
}
|
||||||
T: Serialize + Clone,
|
|
||||||
{
|
enum Transport {
|
||||||
let tmpl_html = format!("{}-html", template);
|
Sendmail,
|
||||||
let tmpl_txt = format!("{}-txt", template);
|
}
|
||||||
let (html, txt) = {
|
|
||||||
if let (Ok(inner_html), Ok(inner_txt)) = (
|
impl Service {
|
||||||
mail_templates.render(&tmpl_html, &ctx),
|
/// Sends mail via sendmail.
|
||||||
mail_templates.render(&tmpl_txt, &ctx),
|
pub fn sendmail(from: String, templates: Handlebars) -> Self {
|
||||||
) {
|
Self {
|
||||||
(Some(inner_html), Some(inner_txt))
|
from: from,
|
||||||
} else {
|
templates: templates,
|
||||||
(None, None)
|
transport: Transport::Sendmail,
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let email = EmailBuilder::new()
|
pub fn send_verification(&self, userid: &Email, token: &str, domain: &str)
|
||||||
.to(to.to_string())
|
-> Result<()> {
|
||||||
.from(from)
|
let ctx = Context {
|
||||||
.subject(subject)
|
token: token.to_string(),
|
||||||
.alternative(
|
userid: userid.to_string(),
|
||||||
html.ok_or(failure::err_msg("Email template failed to render"))?,
|
domain: domain.to_string(),
|
||||||
txt.ok_or(failure::err_msg("Email template failed to render"))?,
|
};
|
||||||
|
|
||||||
|
self.send(
|
||||||
|
userid,
|
||||||
|
"Please verify your email address",
|
||||||
|
"verify",
|
||||||
|
ctx,
|
||||||
)
|
)
|
||||||
.build()
|
}
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut sender = SendmailTransport::new();
|
pub fn send_confirmation(&self, userid: &Email, token: &str, domain: &str)
|
||||||
sender.send(&email)?;
|
-> Result<()> {
|
||||||
Ok(())
|
let ctx = Context {
|
||||||
}
|
token: token.to_string(),
|
||||||
|
userid: userid.to_string(),
|
||||||
pub fn send_verification_mail(
|
domain: domain.to_string(),
|
||||||
userid: &Email, token: &str, mail_templates: &Handlebars, domain: &str,
|
};
|
||||||
from: &str,
|
|
||||||
) -> Result<()> {
|
self.send(
|
||||||
let ctx = Context {
|
userid,
|
||||||
token: token.to_string(),
|
"Please confirm deletion of your key",
|
||||||
userid: userid.to_string(),
|
"confirm",
|
||||||
domain: domain.to_string(),
|
ctx,
|
||||||
};
|
)
|
||||||
|
}
|
||||||
send_mail(
|
|
||||||
userid,
|
fn send<T>(&self, to: &Email, subject: &str, template: &str, ctx: T)
|
||||||
"Please verify your email address",
|
-> Result<()>
|
||||||
mail_templates,
|
where T: Serialize + Clone,
|
||||||
"verify",
|
{
|
||||||
from,
|
let tmpl_html = format!("{}-html", template);
|
||||||
ctx,
|
let tmpl_txt = format!("{}-txt", template);
|
||||||
)
|
let (html, txt) = {
|
||||||
}
|
if let (Ok(inner_html), Ok(inner_txt)) = (
|
||||||
|
self.templates.render(&tmpl_html, &ctx),
|
||||||
pub fn send_confirmation_mail(
|
self.templates.render(&tmpl_txt, &ctx),
|
||||||
userid: &Email, token: &str, mail_templates: &Handlebars, domain: &str,
|
) {
|
||||||
from: &str,
|
(Some(inner_html), Some(inner_txt))
|
||||||
) -> Result<()> {
|
} else {
|
||||||
let ctx = Context {
|
(None, None)
|
||||||
token: token.to_string(),
|
}
|
||||||
userid: userid.to_string(),
|
};
|
||||||
domain: domain.to_string(),
|
|
||||||
};
|
let email = EmailBuilder::new()
|
||||||
|
.to(to.to_string())
|
||||||
send_mail(
|
.from(self.from.clone())
|
||||||
userid,
|
.subject(subject)
|
||||||
"Please confirm deletion of your key",
|
.alternative(
|
||||||
mail_templates,
|
html.ok_or(failure::err_msg("Email template failed to render"))?,
|
||||||
"confirm",
|
txt.ok_or(failure::err_msg("Email template failed to render"))?,
|
||||||
from,
|
)
|
||||||
ctx,
|
.build()
|
||||||
)
|
.unwrap();
|
||||||
|
|
||||||
|
match self.transport {
|
||||||
|
Transport::Sendmail => {
|
||||||
|
let mut transport = SendmailTransport::new();
|
||||||
|
transport.send(&email)?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use handlebars::Handlebars;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
mod upload;
|
mod upload;
|
||||||
|
use mail;
|
||||||
|
|
||||||
use database::{Database, Polymorphic, Query};
|
use database::{Database, Polymorphic, Query};
|
||||||
use Result;
|
use Result;
|
||||||
|
@ -190,8 +191,6 @@ mod templates {
|
||||||
|
|
||||||
struct StaticDir(String);
|
struct StaticDir(String);
|
||||||
pub struct Domain(String);
|
pub struct Domain(String);
|
||||||
pub struct From(String);
|
|
||||||
pub struct MailTemplates(Handlebars);
|
|
||||||
pub struct XAccelRedirect(bool);
|
pub struct XAccelRedirect(bool);
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for queries::Hkp {
|
impl<'a, 'r> FromRequest<'a, 'r> for queries::Hkp {
|
||||||
|
@ -470,11 +469,10 @@ struct ManageRequest {
|
||||||
|
|
||||||
#[post("/vks/v1/manage", data="<request>")]
|
#[post("/vks/v1/manage", data="<request>")]
|
||||||
fn manage_post(
|
fn manage_post(
|
||||||
db: State<Polymorphic>, tmpl: State<MailTemplates>, domain: State<Domain>,
|
db: State<Polymorphic>, mail_service: State<mail::Service>,
|
||||||
from: State<From>, request: Form<ManageRequest>,
|
domain: State<Domain>, request: Form<ManageRequest>,
|
||||||
) -> MyResponse {
|
) -> MyResponse {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use mail::send_confirmation_mail;
|
|
||||||
|
|
||||||
let query = match request.search_term.parse() {
|
let query = match request.search_term.parse() {
|
||||||
Ok(query) => query,
|
Ok(query) => query,
|
||||||
|
@ -498,8 +496,8 @@ fn manage_post(
|
||||||
};
|
};
|
||||||
|
|
||||||
for uid in uids {
|
for uid in uids {
|
||||||
if let Err(e) = send_confirmation_mail(
|
if let Err(e) = mail_service.send_confirmation(
|
||||||
&uid, &token, &tmpl.0, &domain.0, &from.0) {
|
&uid, &token, &domain.0) {
|
||||||
return MyResponse::ise(e);
|
return MyResponse::ise(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -683,24 +681,21 @@ fn rocket_factory(rocket: rocket::Rocket, db: Polymorphic) -> rocket::Rocket {
|
||||||
|
|
||||||
Ok(rocket.manage(Domain(domain)))
|
Ok(rocket.manage(Domain(domain)))
|
||||||
}))
|
}))
|
||||||
.attach(AdHoc::on_attach("from", |rocket| {
|
|
||||||
let from = rocket.config().get_str("from").unwrap().to_string();
|
|
||||||
|
|
||||||
Ok(rocket.manage(From(from)))
|
|
||||||
}))
|
|
||||||
.attach(AdHoc::on_attach("x-accel-redirect", |rocket| {
|
.attach(AdHoc::on_attach("x-accel-redirect", |rocket| {
|
||||||
let x_accel_redirect =
|
let x_accel_redirect =
|
||||||
rocket.config().get_bool("x-accel-redirect").unwrap();
|
rocket.config().get_bool("x-accel-redirect").unwrap();
|
||||||
|
|
||||||
Ok(rocket.manage(XAccelRedirect(x_accel_redirect)))
|
Ok(rocket.manage(XAccelRedirect(x_accel_redirect)))
|
||||||
}))
|
}))
|
||||||
.attach(AdHoc::on_attach("mail_templates", |rocket| {
|
.attach(AdHoc::on_attach("mail-service", |rocket| {
|
||||||
let dir: PathBuf = rocket
|
let dir: PathBuf = rocket
|
||||||
.config()
|
.config()
|
||||||
.get_str("template_dir")
|
.get_str("template_dir")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string()
|
.to_string()
|
||||||
.into();
|
.into();
|
||||||
|
let from = rocket.config().get_str("from").unwrap().to_string();
|
||||||
|
|
||||||
let confirm_html = dir.join("confirm-email-html.hbs");
|
let confirm_html = dir.join("confirm-email-html.hbs");
|
||||||
let confirm_txt = dir.join("confirm-email-txt.hbs");
|
let confirm_txt = dir.join("confirm-email-txt.hbs");
|
||||||
let verify_html = dir.join("verify-email-html.hbs");
|
let verify_html = dir.join("verify-email-html.hbs");
|
||||||
|
@ -720,7 +715,7 @@ fn rocket_factory(rocket: rocket::Rocket, db: Polymorphic) -> rocket::Rocket {
|
||||||
.register_template_file("verify-txt", verify_txt)
|
.register_template_file("verify-txt", verify_txt)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Ok(rocket.manage(MailTemplates(handlebars)))
|
Ok(rocket.manage(mail::Service::sendmail(from, handlebars)))
|
||||||
}))
|
}))
|
||||||
.mount("/", routes)
|
.mount("/", routes)
|
||||||
.manage(db)
|
.manage(db)
|
||||||
|
|
|
@ -9,11 +9,9 @@ use rocket::{Data, State};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
use rocket::response::Flash;
|
use rocket::response::Flash;
|
||||||
|
|
||||||
use handlebars::Handlebars;
|
|
||||||
|
|
||||||
use database::{Database, Polymorphic};
|
use database::{Database, Polymorphic};
|
||||||
use mail::send_verification_mail;
|
use mail;
|
||||||
use web::{Domain, From, MailTemplates};
|
use web::Domain;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
@ -80,9 +78,9 @@ fn show_error(error: String) -> Template {
|
||||||
#[post("/vks/v1/publish/submit", data = "<data>")]
|
#[post("/vks/v1/publish/submit", data = "<data>")]
|
||||||
pub fn vks_publish_submit(
|
pub fn vks_publish_submit(
|
||||||
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
|
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
|
||||||
tmpl: State<MailTemplates>, domain: State<Domain>, from: State<From>
|
mail_service: State<mail::Service>, domain: State<Domain>,
|
||||||
) -> Flash<Redirect> {
|
) -> Flash<Redirect> {
|
||||||
match do_upload_hkp(db, cont_type, data, tmpl, domain, from) {
|
match do_upload_hkp(db, cont_type, data, mail_service, domain) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => Flash::error(Redirect::to("/vks/v1/publish?err"), err.to_string()),
|
Err(err) => Flash::error(Redirect::to("/vks/v1/publish?err"), err.to_string()),
|
||||||
}
|
}
|
||||||
|
@ -91,14 +89,14 @@ pub fn vks_publish_submit(
|
||||||
// signature requires the request to have a `Content-Type`
|
// signature requires the request to have a `Content-Type`
|
||||||
fn do_upload_hkp(
|
fn do_upload_hkp(
|
||||||
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
|
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
|
||||||
tmpl: State<MailTemplates>, domain: State<Domain>, from: State<From>,
|
mail_service: State<mail::Service>, domain: State<Domain>,
|
||||||
) -> Result<Flash<Redirect>, String> {
|
) -> Result<Flash<Redirect>, String> {
|
||||||
if cont_type.is_form_data() {
|
if cont_type.is_form_data() {
|
||||||
// multipart/form-data
|
// multipart/form-data
|
||||||
let (_, boundary) = cont_type.params().find(|&(k, _)| k == "boundary").ok_or_else(
|
let (_, boundary) = cont_type.params().find(|&(k, _)| k == "boundary").ok_or_else(
|
||||||
|| "`Content-Type: multipart/form-data` boundary param not provided".to_owned())?;
|
|| "`Content-Type: multipart/form-data` boundary param not provided".to_owned())?;
|
||||||
|
|
||||||
process_upload(boundary, data, db.inner(), &tmpl.0, &domain.0, &from.0)
|
process_upload(boundary, data, db.inner(), mail_service, &domain.0)
|
||||||
} else if cont_type.is_form() {
|
} else if cont_type.is_form() {
|
||||||
use rocket::request::FormItems;
|
use rocket::request::FormItems;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
@ -121,9 +119,8 @@ fn do_upload_hkp(
|
||||||
return process_key(
|
return process_key(
|
||||||
Cursor::new(decoded_value.as_bytes()),
|
Cursor::new(decoded_value.as_bytes()),
|
||||||
&db,
|
&db,
|
||||||
&tmpl.0,
|
mail_service,
|
||||||
&domain.0,
|
&domain.0,
|
||||||
&from.0,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => { /* skip */ }
|
_ => { /* skip */ }
|
||||||
|
@ -137,26 +134,27 @@ fn do_upload_hkp(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_upload(
|
fn process_upload(
|
||||||
boundary: &str, data: Data, db: &Polymorphic, mail_templates: &Handlebars,
|
boundary: &str, data: Data, db: &Polymorphic,
|
||||||
domain: &str, from: &str,
|
mail_service: State<mail::Service>,
|
||||||
|
domain: &str,
|
||||||
) -> Result<Flash<Redirect>, String> {
|
) -> Result<Flash<Redirect>, String> {
|
||||||
// saves all fields, any field longer than 10kB goes to a temporary directory
|
// saves all fields, any field longer than 10kB goes to a temporary directory
|
||||||
// Entries could implement FromData though that would give zero control over
|
// Entries could implement FromData though that would give zero control over
|
||||||
// how the files are saved; Multipart would be a good impl candidate though
|
// how the files are saved; Multipart would be a good impl candidate though
|
||||||
match Multipart::with_body(data.open(), boundary).save().temp() {
|
match Multipart::with_body(data.open(), boundary).save().temp() {
|
||||||
Full(entries) => {
|
Full(entries) => {
|
||||||
process_multipart(entries, db, mail_templates, domain, from)
|
process_multipart(entries, db, mail_service, domain)
|
||||||
}
|
}
|
||||||
Partial(partial, _) => {
|
Partial(partial, _) => {
|
||||||
process_multipart(partial.entries, db, mail_templates, domain, from)
|
process_multipart(partial.entries, db, mail_service, domain)
|
||||||
}
|
}
|
||||||
Error(err) => Err(err.to_string())
|
Error(err) => Err(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_multipart(
|
fn process_multipart(
|
||||||
entries: Entries, db: &Polymorphic, mail_templates: &Handlebars,
|
entries: Entries, db: &Polymorphic, mail_service: State<mail::Service>,
|
||||||
domain: &str, from: &str,
|
domain: &str,
|
||||||
) -> Result<Flash<Redirect>, String> {
|
) -> Result<Flash<Redirect>, String> {
|
||||||
match entries.fields.get("keytext") {
|
match entries.fields.get("keytext") {
|
||||||
Some(ent) if ent.len() == 1 => {
|
Some(ent) if ent.len() == 1 => {
|
||||||
|
@ -164,7 +162,7 @@ fn process_multipart(
|
||||||
err.to_string()
|
err.to_string()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
process_key(reader, db, mail_templates, domain, from)
|
process_key(reader, db, mail_service, domain)
|
||||||
}
|
}
|
||||||
Some(_) | None => {
|
Some(_) | None => {
|
||||||
Err("Not a PGP public key".into())
|
Err("Not a PGP public key".into())
|
||||||
|
@ -173,8 +171,8 @@ fn process_multipart(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_key<R>(
|
fn process_key<R>(
|
||||||
reader: R, db: &Polymorphic, mail_templates: &Handlebars, domain: &str,
|
reader: R, db: &Polymorphic, mail_service: State<mail::Service>,
|
||||||
from: &str
|
domain: &str,
|
||||||
) -> Result<Flash<Redirect>, String>
|
) -> Result<Flash<Redirect>, String>
|
||||||
where
|
where
|
||||||
R: Read,
|
R: Read,
|
||||||
|
@ -188,12 +186,10 @@ where
|
||||||
let mut results: Vec<String> = vec!();
|
let mut results: Vec<String> = vec!();
|
||||||
|
|
||||||
for (email,token) in tokens {
|
for (email,token) in tokens {
|
||||||
send_verification_mail(
|
mail_service.send_verification(
|
||||||
&email,
|
&email,
|
||||||
&token,
|
&token,
|
||||||
mail_templates,
|
|
||||||
domain,
|
domain,
|
||||||
from,
|
|
||||||
).map_err(|e| format!("{}", e))?;
|
).map_err(|e| format!("{}", e))?;
|
||||||
results.push(email.to_string());
|
results.push(email.to_string());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue