diff --git a/bin/install.sh b/bin/install.sh index 0bef7120..a43bfae4 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -46,7 +46,7 @@ sudo cp $MNML_PATH/etc/systemd/system/mnml.service /usr/local/systemd/system/ sudo -u postgres createdb mnml sudo -u postgres createuser --encrypted mnml -echo "DATABASE_URL=postgres://mnml:$MNML_PG_PASSWORD@$MNML_PG_HOST/mnml" | sudo tee -a /etc/mnml/server.conf +echo "DATABASE_URL=postgres://mnml:$MNML_PG_PASSWORD@$MNML_PG_HOST/mnml" | sudo tee -a /etc/mnml/gs.conf sudo -u postgres psql -c "alter user mnml with encrypted password '$MNML_PG_PASSWORD';" cd $MNML_PATH/ops && npm run migrate diff --git a/client/src/components/account.management.jsx b/client/src/components/account.management.jsx index 59d52d94..9f5ae1ba 100644 --- a/client/src/components/account.management.jsx +++ b/client/src/components/account.management.jsx @@ -21,7 +21,7 @@ const addState = connect( postData('/account/password', { current, password }) .then(res => res.json()) .then(data => { - if (!data.success) return errorToast(data.error_message); + if (data.error) return errorToast(data.error); infoToast('Password changed. Reloading...') setTimeout(() => window.location.reload(), 5000); }) @@ -32,7 +32,7 @@ const addState = connect( postData('/account/email', { email }) .then(res => res.json()) .then(data => { - if (!data.success) return errorToast(data.error_message); + if (data.error) return errorToast(data.error); infoToast('Email set. Please confirm your address.'); return true; }) diff --git a/ops/migrations/20190825172701_email.js b/ops/migrations/20190825172701_email.js index 6fd8408b..6f434893 100644 --- a/ops/migrations/20190825172701_email.js +++ b/ops/migrations/20190825172701_email.js @@ -1,9 +1,9 @@ exports.up = async knex => { - await knex.schema.createTable('email', table => { + await knex.schema.createTable('emails', table => { table.timestamps(true, true); table.uuid('id') - .primary(); + .primary() .index(); table.uuid('account') diff --git a/server/src/construct.rs b/server/src/construct.rs index 6a9824ac..884da671 100644 --- a/server/src/construct.rs +++ b/server/src/construct.rs @@ -889,7 +889,7 @@ pub fn construct_spawn(tx: &mut Transaction, account: Uuid, name: String, team: img::molecular_write(construct.img)?; - info!("spawned construct account={:} construct={:?}", account, construct); + info!("spawned construct account={:} name={:?}", account, construct.name); return Ok(construct); } diff --git a/server/src/http.rs b/server/src/http.rs index b845dc19..af157978 100644 --- a/server/src/http.rs +++ b/server/src/http.rs @@ -10,10 +10,11 @@ use iron::modifiers::Redirect; use iron::Url; use iron::{typemap, BeforeMiddleware,AfterMiddleware}; use urlencoded::UrlEncodedQuery; -use persistent::Read; +use persistent::{Read, Write}; use router::Router; use mount::{Mount}; use serde::{Serialize, Deserialize}; +use lettre::{SendableEmail, SmtpClient, SmtpTransport, Transport}; use acp; use account; @@ -295,23 +296,23 @@ fn set_password(req: &mut Request) -> IronResult { } #[derive(Debug,Clone,Deserialize)] -struct SetEmail { +struct EmailSet { email: String, } -fn set_email(req: &mut Request) -> IronResult { +fn email_set(req: &mut Request) -> IronResult { let state = req.get::>().unwrap(); - let params = match req.get::>() { + let params = match req.get::>() { Ok(Some(b)) => b, _ => return Err(IronError::from(MnmlHttpError::BadRequest)), }; - match req.extensions.get::() { - Some(a) => { - let db = state.pool.get().or(Err(MnmlHttpError::DbError))?; - let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?; + let db = state.pool.get().or(Err(MnmlHttpError::DbError))?; + let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?; - let id = match mail::insert(&mut tx, a.id, ¶ms.email) { + let (email, account, token) = match req.extensions.get::() { + Some(a) => { + let (_id, token) = match mail::insert(&mut tx, a.id, ¶ms.email) { Ok(res) => res, Err(e) => { warn!("{:?}", e); @@ -319,19 +320,34 @@ fn set_email(req: &mut Request) -> IronResult { }, }; - info!("email added id={:?} account={:?} email={:?}", id, a, params.email); - - tx.commit().or(Err(MnmlHttpError::ServerError))?; - - Ok(json_response(status::Ok, Json::Message("email set. confirmation required".to_string()))) + (params.email.clone(), a.clone(), token) }, - None => Err(IronError::from(MnmlHttpError::Unauthorized)), - } + None => return Err(IronError::from(MnmlHttpError::Unauthorized)), + }; + + let app_mailer = req.get::>().unwrap(); + let send = match app_mailer.lock().unwrap().mailer.send(mail::confirm(&email, &account.name, &token)) { + Ok(send) => send, + Err(e) => { + warn!("{:?}", e); + return Err(IronError::from(MnmlHttpError::ServerError)); + } + }; + + tx.commit().or(Err(MnmlHttpError::ServerError))?; + + info!("confirmation email sent send={:?} account={:?} email={:?}", send, account, email); + Ok(json_response(status::Ok, Json::Message("email set. confirmation required".to_string()))) } -fn confirm(req: &mut Request) -> IronResult { +fn email_confirm(req: &mut Request) -> IronResult { let state = req.get::>().unwrap(); + let account = match req.extensions.get::() { + Some(a) => a.clone(), + None => return Err(IronError::from(MnmlHttpError::Unauthorized)), + }; + match req.get_ref::() { Ok(ref hashmap) => { let db = state.pool.get().or(Err(MnmlHttpError::DbError))?; @@ -342,12 +358,12 @@ fn confirm(req: &mut Request) -> IronResult { None => return Err(IronError::from(MnmlHttpError::BadRequest)), }; - let confirmation = match mail::confirm_email(&mut tx, token.to_string()) { + let confirmation = match mail::confirm_email(&mut tx, &account, token.to_string()) { Ok(c) => c, Err(_) => return Err(IronError::from(MnmlHttpError::NotFound)) }; - info!("email confirmed email={:?} account={:?}", confirmation.0, confirmation.1); + info!("email confirmed email={:?} account={:?}", confirmation.0, account); tx.commit().or(Err(MnmlHttpError::ServerError))?; Ok(Response::with((status::Found, Redirect(Url::parse("https://mnml.gg").unwrap())))) @@ -360,10 +376,14 @@ const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10; pub struct State { pub pool: PgPool, - // pub events: Events, +} + +pub struct Mailer { + pub mailer: SmtpTransport, } impl Key for State { type Value = State; } +impl Key for Mailer { type Value = Mailer; } fn account_mount() -> Router { let mut router = Router::new(); @@ -372,8 +392,10 @@ fn account_mount() -> Router { router.post("logout", logout, "logout"); router.post("register", register, "register"); router.post("password", set_password, "set_password"); - router.post("email", set_email, "set_email"); - router.post("email/confirm/", confirm, "confirm"); + router.post("email", email_set, "email_set"); + + // it is sent in an email... + router.get("email/confirm", email_confirm, "email_confirm"); router } @@ -385,7 +407,7 @@ fn payment_mount() -> Router { router } -pub fn start(pool: PgPool) { +pub fn start(pool: PgPool, mailer: SmtpTransport) { let mut mounts = Mount::new(); mounts.mount("/api/account/", account_mount()); @@ -394,6 +416,7 @@ pub fn start(pool: PgPool) { let mut chain = Chain::new(mounts); chain.link(Read::::both(State { pool })); + chain.link(Write::::both(Mailer { mailer })); chain.link_before(Read::::one(MAX_BODY_LENGTH)); chain.link_before(AuthMiddleware); chain.link_after(ErrorHandler); diff --git a/server/src/mail.rs b/server/src/mail.rs index 7fd3cb00..3a13977a 100644 --- a/server/src/mail.rs +++ b/server/src/mail.rs @@ -18,6 +18,8 @@ use lettre::smtp::response::Response; use lettre::{SendableEmail, SmtpClient, SmtpTransport, Transport}; use lettre_email::Email; +use account::Account; + pub enum Mail { Recover { email: String, name: String, token: String }, Confirm { email: String, name: String, token: String }, @@ -38,16 +40,17 @@ fn recover(email: String, name: String, token: String) -> SendableEmail { .into() } -fn confirm(email: String, name: String, token: String) -> SendableEmail { +pub 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/{:} - ", name, token); +please click the link below to confirm your email +http://mnml.gg/api/account/email/confirm?confirm_token={:} + +glhf", name, token); Email::builder() .from("machines@mnml.gg") - .to(email) - .subject("recovery phase") + .to(email.clone()) + .subject("email confirmation") .text(confirm_body) .build() .unwrap() @@ -57,25 +60,26 @@ fn confirm(email: String, name: String, token: String) -> SendableEmail { fn send_mail(mailer: &mut SmtpTransport, mail: Mail) -> Result { let msg = match mail { Mail::Recover { email, name, token } => recover(email, name, token), - Mail::Confirm { email, name, token } => confirm(email, name, token), + Mail::Confirm { email, name, token } => confirm(&email, &name, &token), }; mailer.send(msg) } -pub fn confirm_email(tx: &mut Transaction, token: String) -> Result<(String, Uuid), Error> { +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 token = $1 + WHERE confirm_token = $1 + AND account = $2 RETURNING id, email, account "; let result = tx - .query(query, &[&token])?; + .query(query, &[&confirm_token, &account.id])?; let row = result.iter().next() - .ok_or(format_err!("token not found {:?}", token))?; + .ok_or(format_err!("confirm_token not found {:?}", confirm_token))?; let _id: Uuid = row.get(0); let email: String = row.get(1); @@ -84,32 +88,33 @@ pub fn confirm_email(tx: &mut Transaction, token: String) -> Result<(String, Uui return Ok((email, account)); } -pub fn insert(tx: &mut Transaction, account: Uuid, email: &String) -> Result { +pub fn insert(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid, String), Error> { let id = Uuid::new_v4(); let mut rng = thread_rng(); - let token: String = iter::repeat(()) + let confirm_token: String = iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) .take(64) .collect(); let query = " - INSERT INTO emails (id, account, email, token) - VALUES ($1, $2, $3, $4); + INSERT INTO emails (id, account, email, confirm_token) + VALUES ($1, $2, $3, $4) + RETURNING id; "; let result = tx - .query(query, &[&id, &account, &email, &token])?; + .query(query, &[&id, &account, &email, &confirm_token])?; - let row = match result.iter().next() { + match result.iter().next() { Some(row) => row, None => return Err(err_msg("no email inserted")), }; - return Ok(id); + return Ok((id, confirm_token)); } -pub fn listen(rx: Receiver) { +pub fn listen(rx: Receiver) -> SmtpTransport { let sender = env::var("MAIL_ADDRESS") .expect("MAIL_ADDRESS must be set"); @@ -129,20 +134,21 @@ pub fn listen(rx: Receiver) { 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"); - }, - }; - } + // 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; } diff --git a/server/src/main.rs b/server/src/main.rs index a47fc29f..b452f137 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -136,6 +136,7 @@ fn main() { let warden_tick_tx = warden_tx.clone(); let (mail_tx, mail_rx) = unbounded(); + let http_mail_tx = mail_tx.clone(); // create a clone of the tx so ws handler can tell events // about connection status @@ -143,13 +144,13 @@ fn main() { let warden = warden::Warden::new(warden_tx, warden_rx, events.tx.clone(), pool.clone()); let pg_pool = pool.clone(); + let mailer = mail::listen(mail_rx); - spawn(move || http::start(http_pool)); + spawn(move || http::start(http_pool, mailer)); spawn(move || warden.listen()); spawn(move || warden::upkeep_tick(warden_tick_tx)); spawn(move || pg::listen(pg_pool, pg_events_tx)); spawn(move || events.listen()); - spawn(move || mail::listen(mail_rx)); // the main thread becomes this ws listener let rpc_pool = pool.clone();