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