diff --git a/client/src/components/login.jsx b/client/src/components/login.jsx index 5e8c6876..4d822807 100644 --- a/client/src/components/login.jsx +++ b/client/src/components/login.jsx @@ -12,11 +12,10 @@ const addState = connect( } = state; function submitLogin(name, password) { postData('/login', { name, password }) - .then(res => { - if (!res.ok) return errorToast(res); - res.text() - }) - .then(res => { + .then(res => res.json()) + .then(data => { + if (!data.success) return errorToast(data.error_message); + console.log(data.response); ws.connect(); }) .catch(error => errorToast(error)); @@ -24,11 +23,10 @@ const addState = connect( function submitRegister(name, password, code) { postData('/register', { name, password, code }) - .then(res => { - if (!res.ok) return errorToast(res); - res.text() - }) - .then(res => { + .then(res => res.json()) + .then(data => { + if (!data.success) return errorToast(data.error_message); + console.log(data.response); ws.connect(); }) .catch(error => errorToast(error)); diff --git a/server/src/account.rs b/server/src/account.rs index 7232351a..29acc8e4 100644 --- a/server/src/account.rs +++ b/server/src/account.rs @@ -8,6 +8,7 @@ use serde_cbor::{from_slice}; use postgres::transaction::Transaction; +use net::MnmlHttpError; use names::{name as generate_name}; use construct::{Construct, construct_recover, construct_spawn}; use instance::{Instance, instance_delete}; @@ -75,7 +76,7 @@ pub fn from_token(db: &Db, token: String) -> Result { Ok(Account { id, name, balance, subscribed }) } -pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result { +pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result { let query = " SELECT id, password, name, balance, subscribed FROM accounts @@ -96,7 +97,7 @@ pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result Result Resul Ok(name) } -pub fn create(name: &String, password: &String, code: &String, tx: &mut Transaction) -> Result { +pub fn create(name: &String, password: &String, code: &String, tx: &mut Transaction) -> Result { if password.len() < PASSWORD_MIN_LEN { - return Err(err_msg("password must be at least 12 characters")); + return Err(MnmlHttpError::PasswordUnacceptable); } if code.to_lowercase() != "grep842" { - return Err(err_msg("https://discord.gg/YJJgurM")); + return Err(MnmlHttpError::InvalidCode); } if name.len() == 0 { - return Err(err_msg("account name not supplied")); + return Err(MnmlHttpError::AccountNameNotProvided); } let id = Uuid::new_v4(); @@ -246,7 +247,7 @@ pub fn create(name: &String, password: &String, code: &String, tx: &mut Transact match result.iter().next() { Some(row) => row, - None => return Err(err_msg("account not created")), + None => return Err(MnmlHttpError::DbError), }; // 3 constructs for a team and 1 to swap diff --git a/server/src/net.rs b/server/src/net.rs index 12eb66d7..12bae55a 100644 --- a/server/src/net.rs +++ b/server/src/net.rs @@ -5,9 +5,11 @@ use iron::headers::{Cookie as CookieHdr, SetCookie}; use iron::prelude::*; use iron::status; use iron::typemap::Key; +use iron::mime::Mime; use iron::{typemap, BeforeMiddleware,AfterMiddleware}; use persistent::Read; use router::Router; +use serde::{Serialize, Deserialize}; // use warden::{warden}; // use pubsub::{pg_listen}; @@ -18,7 +20,7 @@ use payments::{stripe}; pub const TOKEN_HEADER: &str = "x-auth-token"; -#[derive(Fail, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Fail, Debug, Serialize, Deserialize)] pub enum MnmlHttpError { // User Facing Errors #[fail(display="internal server error")] @@ -30,26 +32,84 @@ pub enum MnmlHttpError { #[fail(display="bad request")] BadRequest, #[fail(display="account name taken or invalid")] + AccountNameNotProvided, + #[fail(display="account name not provided")] + AccountNotFound, + #[fail(display="account not found")] AccountNameTaken, + #[fail(display="password does not match")] + PasswordNotMatch, #[fail(display="password unacceptable. must be > 11 characters")] PasswordUnacceptable, + #[fail(display="incorrect token. refresh or logout of existing sessions")] + TokenDoesNotMatch, #[fail(display="invalid code. https://discord.gg/YJJgurM")] InvalidCode, } +impl From for MnmlHttpError { + fn from(_err: bcrypt::BcryptError) -> Self { + MnmlHttpError::ServerError + } +} + +impl From for MnmlHttpError { + fn from(_err: postgres::Error) -> Self { + MnmlHttpError::DbError + } +} + +impl From for MnmlHttpError { + fn from(_err: failure::Error) -> Self { + MnmlHttpError::ServerError + } +} + +#[derive(Serialize, Deserialize)] +struct JsonResponse { + response: Option, + success: bool, + error_message: Option +} + +impl JsonResponse { + fn success(response: String) -> Self { + JsonResponse { response: Some(response), success: true, error_message: None } + } + + fn error(msg: String) -> Self { + JsonResponse { response: None, success: false, error_message: Some(msg) } + } +} + +fn iron_response (status: status::Status, message: String) -> Response { + let content_type = "application/json".parse::().unwrap(); + let msg = match status { + status::Ok => JsonResponse::success(message), + _ => JsonResponse::error(message) + }; + let msg_out = serde_json::to_string(&msg).unwrap(); + return Response::with((content_type, status, msg_out)); +} + impl From for IronError { fn from(m_err: MnmlHttpError) -> Self { let (err, res) = match m_err { - MnmlHttpError::ServerError => (m_err.compat(), status::InternalServerError), + MnmlHttpError::ServerError | MnmlHttpError::DbError => (m_err.compat(), status::InternalServerError), - MnmlHttpError::Unauthorized => (m_err.compat(), status::Unauthorized), - MnmlHttpError::BadRequest => (m_err.compat(), status::BadRequest), - MnmlHttpError::AccountNameTaken => (m_err.compat(), status::BadRequest), - MnmlHttpError::PasswordUnacceptable => (m_err.compat(), status::BadRequest), - MnmlHttpError::InvalidCode => (m_err.compat(), status::Unauthorized), - }; - IronError::new(err, res) + MnmlHttpError::AccountNameNotProvided | + MnmlHttpError::AccountNameTaken | + MnmlHttpError::AccountNotFound | + MnmlHttpError::BadRequest | + MnmlHttpError::PasswordUnacceptable => (m_err.compat(), status::BadRequest), + + MnmlHttpError::PasswordNotMatch | + MnmlHttpError::InvalidCode | + MnmlHttpError::TokenDoesNotMatch | + MnmlHttpError::Unauthorized => (m_err.compat(), status::Unauthorized), + }; + IronError { error: Box::new(err), response: iron_response(res, m_err.to_string()) } } } @@ -70,7 +130,7 @@ impl BeforeMiddleware for AuthMiddleware { if cookie.name() == TOKEN_HEADER { match account::from_token(&db, cookie.value().to_string()) { Ok(a) => req.extensions.insert::(a), - Err(_) => return Err(IronError::from(MnmlHttpError::Unauthorized)), + Err(_) => return Err(IronError::from(MnmlHttpError::TokenDoesNotMatch)), }; } } @@ -110,8 +170,8 @@ fn token_res(token: String) -> Response { .max_age(Duration::weeks(1)) // 1 week aligns with db set .finish(); - let mut res = Response::with(status::Ok); - res.headers.set(SetCookie(vec![v.to_string()])); + let mut res = iron_response(status::Ok, "token_res".to_string()); + res.headers.set(SetCookie(vec![v.to_string()])); return res; } @@ -141,7 +201,7 @@ fn register(req: &mut Request) -> IronResult { }, Err(e) => { warn!("{:?}", e); - Err(IronError::from(MnmlHttpError::BadRequest)) + Err(IronError::from(e)) } } } @@ -170,7 +230,7 @@ fn login(req: &mut Request) -> IronResult { }, Err(e) => { warn!("{:?}", e); - Err(IronError::from(MnmlHttpError::Unauthorized)) + Err(IronError::from(e)) } } } @@ -186,7 +246,7 @@ fn logout(req: &mut Request) -> IronResult { tx.commit().or(Err(MnmlHttpError::ServerError))?; - let mut res = Response::with(status::Ok); + let mut res = iron_response(status::Ok, "token_res".to_string()); res.headers.set(SetCookie(vec![AUTH_CLEAR.to_string()])); Ok(res)