302 lines
8.2 KiB
Rust
302 lines
8.2 KiB
Rust
use std::env;
|
|
|
|
use uuid::Uuid;
|
|
use rand::{thread_rng, Rng};
|
|
use rand::distributions::Alphanumeric;
|
|
use std::iter;
|
|
use postgres::transaction::Transaction;
|
|
|
|
use failure::Error;
|
|
use failure::{err_msg, format_err};
|
|
|
|
use crossbeam_channel::Receiver;
|
|
use lettre::smtp::authentication::{Credentials, Mechanism};
|
|
use lettre::smtp::ConnectionReuseParameters;
|
|
use lettre::smtp::error::Error as MailError;
|
|
use lettre::smtp::extension::ClientId;
|
|
use lettre::smtp::response::Response;
|
|
use lettre::{SendableEmail, SmtpClient, SmtpTransport, Transport};
|
|
use lettre_email::Email as LettreEmail;
|
|
|
|
use account::Account;
|
|
use pg::Db;
|
|
|
|
#[derive(Debug,Clone,Serialize)]
|
|
pub struct Email {
|
|
pub id: Uuid,
|
|
pub email: String,
|
|
pub account: Uuid,
|
|
pub confirmed: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Mail {
|
|
Recover { email: String, name: String, token: String },
|
|
Confirm { email: String, name: String, token: String },
|
|
}
|
|
|
|
// create link that will set a token
|
|
// put msg saying pls reset your password
|
|
// redirect to main page cause cbf
|
|
|
|
fn recover(email: &String, name: &String, token: &String) -> SendableEmail {
|
|
let body = format!("{:},
|
|
the link below will recover your account.
|
|
please change your password immediately in the account page.
|
|
this link will expire in 48 hours or once used.
|
|
|
|
http://mnml.gg/api/account/recover?recover_token={:}
|
|
|
|
glhf
|
|
--mnml", name, token);
|
|
|
|
LettreEmail::builder()
|
|
.from("machines@mnml.gg")
|
|
.to(email.clone())
|
|
.subject("account recovery")
|
|
.text(body)
|
|
.build()
|
|
.unwrap()
|
|
.into()
|
|
}
|
|
|
|
fn confirm(email: &String, name: &String, token: &String) -> SendableEmail {
|
|
let confirm_body = format!("{:},
|
|
please click the link below to confirm your email
|
|
http://mnml.gg/api/account/email/confirm?confirm_token={:}
|
|
|
|
glhf
|
|
--mnml", name, token);
|
|
|
|
LettreEmail::builder()
|
|
.from("machines@mnml.gg")
|
|
.to(email.clone())
|
|
.subject("email confirmation")
|
|
.text(confirm_body)
|
|
.build()
|
|
.unwrap()
|
|
.into()
|
|
}
|
|
|
|
pub fn send_mail(mailer: &mut SmtpTransport, mail: Mail) -> Result<Response, MailError> {
|
|
let msg = match mail {
|
|
Mail::Recover { email, name, token } => recover(&email, &name, &token),
|
|
Mail::Confirm { email, name, token } => confirm(&email, &name, &token),
|
|
};
|
|
|
|
mailer.send(msg)
|
|
}
|
|
|
|
pub fn confirm_email(tx: &mut Transaction, account: &Account, confirm_token: String) -> Result<(String, Uuid), Error> {
|
|
let query = "
|
|
UPDATE emails
|
|
SET confirmed = true, updated_at = now()
|
|
WHERE confirm_token = $1
|
|
AND account = $2
|
|
RETURNING id, email, account
|
|
";
|
|
|
|
let result = tx
|
|
.query(query, &[&confirm_token, &account.id])?;
|
|
|
|
let row = result.iter().next()
|
|
.ok_or(format_err!("confirm_token not found {:?}", confirm_token))?;
|
|
|
|
let _id: Uuid = row.get(0);
|
|
let email: String = row.get(1);
|
|
let account: Uuid = row.get(2);
|
|
|
|
return Ok((email, account));
|
|
}
|
|
|
|
pub fn select(db: &Db, email: &String) -> Result<Email, Error> {
|
|
let query = "
|
|
SELECT id, email, account, confirmed
|
|
FROM emails
|
|
WHERE email = $1;
|
|
";
|
|
|
|
let result = db
|
|
.query(query, &[&email])?;
|
|
|
|
let row = result.iter().next()
|
|
.ok_or(err_msg("email found"))?;
|
|
|
|
let id: Uuid = row.get(0);
|
|
let email: String = row.get(1);
|
|
let account: Uuid = row.get(2);
|
|
let confirmed: bool = row.get(3);
|
|
|
|
return Ok(Email { id, email, account, confirmed });
|
|
}
|
|
|
|
pub fn select_account(db: &Db, account: Uuid) -> Result<Option<Email>, Error> {
|
|
let query = "
|
|
SELECT id, email, account, confirmed
|
|
FROM emails
|
|
WHERE account = $1;
|
|
";
|
|
|
|
let result = db
|
|
.query(query, &[&account])?;
|
|
|
|
let row = match result.iter().next() {
|
|
Some(r) => r,
|
|
None => return Ok(None),
|
|
};
|
|
|
|
let id: Uuid = row.get(0);
|
|
let email: String = row.get(1);
|
|
let account: Uuid = row.get(2);
|
|
let confirmed: bool = row.get(3);
|
|
|
|
return Ok(Some(Email { id, email, account, confirmed }));
|
|
}
|
|
|
|
pub fn set_recovery(tx: &mut Transaction, email: &String) -> Result<String, Error> {
|
|
let mut rng = thread_rng();
|
|
let recover_token: String = iter::repeat(())
|
|
.map(|()| rng.sample(Alphanumeric))
|
|
.take(64)
|
|
.collect();
|
|
|
|
let query = "
|
|
UPDATE emails
|
|
SET recover_token = $1, recover_token_expiry = now() + interval '2 days'
|
|
WHERE email = $2
|
|
AND confirmed = true
|
|
RETURNING id, email, account
|
|
";
|
|
|
|
let result = tx
|
|
.query(query, &[&recover_token, &email])?;
|
|
|
|
let row = result.iter().next()
|
|
.ok_or(format_err!("no confirmed email found {:?}", email))?;
|
|
|
|
let _id: Uuid = row.get(0);
|
|
let _email: String = row.get(1);
|
|
let _account: Uuid = row.get(2);
|
|
|
|
return Ok(recover_token);
|
|
}
|
|
|
|
pub fn get_recovery(tx: &mut Transaction, recover_token: &String) -> Result<Email, Error> {
|
|
// set a new token when recovering to prevent multiple access
|
|
let mut rng = thread_rng();
|
|
let new_token: String = iter::repeat(())
|
|
.map(|()| rng.sample(Alphanumeric))
|
|
.take(64)
|
|
.collect();
|
|
|
|
let query = "
|
|
UPDATE emails
|
|
SET recover_token = $1, recover_token_expiry = now()
|
|
WHERE recover_token = $2
|
|
AND recover_token_expiry > now()
|
|
AND confirmed = true
|
|
RETURNING id, email, account, confirmed;
|
|
";
|
|
|
|
let result = tx
|
|
.query(query, &[&new_token, &recover_token])?;
|
|
|
|
let row = result.iter().next()
|
|
.ok_or(err_msg("no confirmed email found"))?;
|
|
|
|
let id: Uuid = row.get(0);
|
|
let email: String = row.get(1);
|
|
let account: Uuid = row.get(2);
|
|
let confirmed: bool = row.get(3);
|
|
|
|
return Ok(Email { id, email, account, confirmed });
|
|
}
|
|
|
|
pub fn set(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid, String), Error> {
|
|
let id = Uuid::new_v4();
|
|
|
|
let mut rng = thread_rng();
|
|
let confirm_token: String = iter::repeat(())
|
|
.map(|()| rng.sample(Alphanumeric))
|
|
.take(64)
|
|
.collect();
|
|
|
|
let recover_token: String = iter::repeat(())
|
|
.map(|()| rng.sample(Alphanumeric))
|
|
.take(64)
|
|
.collect();
|
|
|
|
let insert_query = "
|
|
INSERT INTO emails (id, account, email, confirm_token, confirmed, recover_token)
|
|
VALUES ($1, $2, $3, $4, false, $5)
|
|
RETURNING id;
|
|
";
|
|
|
|
let update_query = "
|
|
UPDATE emails
|
|
SET email = $1, confirm_token = $2, confirmed = false, recover_token = $3
|
|
WHERE account = $4
|
|
RETURNING id;
|
|
";
|
|
|
|
let result = match tx.query(insert_query, &[&id, &account, &email, &confirm_token, &recover_token]) {
|
|
Ok(r) => r,
|
|
// email update probably
|
|
Err(_) => {
|
|
match tx.query(update_query, &[&email, &confirm_token, &recover_token, &account]) {
|
|
Ok(r) => r,
|
|
Err(e) => {
|
|
warn!("{:?}", e);
|
|
return Err(err_msg("no email set"));
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
match result.iter().next() {
|
|
Some(row) => row,
|
|
None => return Err(err_msg("no email set")),
|
|
};
|
|
|
|
return Ok((id, confirm_token));
|
|
}
|
|
|
|
pub fn listen(rx: Receiver<Mail>) -> SmtpTransport {
|
|
let sender = env::var("MAIL_ADDRESS")
|
|
.expect("MAIL_ADDRESS must be set");
|
|
|
|
let password = env::var("MAIL_PASSWORD")
|
|
.expect("MAIL_PASSWORD must be set");
|
|
|
|
let domain = env::var("MAIL_DOMAIN")
|
|
.expect("MAIL_DOMAIN must be set");
|
|
|
|
let mut mailer = SmtpClient::new_simple("smtp.gmail.com").unwrap()
|
|
.hello_name(ClientId::Domain(domain))
|
|
.credentials(Credentials::new(sender, password))
|
|
.smtp_utf8(true)
|
|
.authentication_mechanism(Mechanism::Plain)
|
|
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
|
.transport();
|
|
|
|
info!("mail connected");
|
|
|
|
// loop {
|
|
// match rx.recv() {
|
|
// Ok(m) => match send_mail(&mut mailer, m) {
|
|
// Ok(r) => info!("{:?}", r),
|
|
// Err(e) => warn!("{:?}", e),
|
|
// },
|
|
// Err(e) => {
|
|
// error!("{:?}", e);
|
|
// panic!("mail thread cannot continue");
|
|
// },
|
|
// };
|
|
// }
|
|
|
|
// Explicitly close the SMTP transaction as we enabled connection reuse
|
|
// mailer.close();
|
|
return mailer;
|
|
}
|
|
|