From 0021ada63946aaa3ef1383f19e0694e81726e36a Mon Sep 17 00:00:00 2001 From: Paul Makles <paulmakles@gmail.com> Date: Sat, 25 Jan 2020 11:33:35 +0000 Subject: [PATCH] Clean up errors, add /users route. --- src/auth.rs | 16 +++++++++------- src/routes/account.rs | 44 ++++++++++++++++++++++++------------------- src/routes/mod.rs | 4 +++- src/routes/user.rs | 18 ++++++++++++++++++ 4 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 src/routes/user.rs diff --git a/src/auth.rs b/src/auth.rs index 8fbdc91..349311b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -2,11 +2,13 @@ use rocket::Outcome; use rocket::http::Status; use rocket::request::{self, Request, FromRequest}; -use bson::{ bson, doc, ordered::OrderedDocument, oid::ObjectId }; +use bson::{ bson, doc, ordered::OrderedDocument }; +use ulid::Ulid; + use crate::database; pub struct User( - pub ObjectId, + pub Ulid, pub String, pub OrderedDocument, ); @@ -24,20 +26,20 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { let keys: Vec<_> = request.headers().get("x-auth-token").collect(); match keys.len() { - 0 => Outcome::Failure((Status::BadRequest, AuthError::Missing)), + 0 => Outcome::Failure((Status::Forbidden, AuthError::Missing)), 1 => { let key = keys[0]; let col = database::get_db().collection("users"); - let result = col.find_one(Some( doc! { "auth_token": key } ), None).unwrap(); + let result = col.find_one(doc! { "access_token": key }, None).unwrap(); if let Some(user) = result { Outcome::Success(User( - user.get_object_id("_id").unwrap().clone(), - user.get_str("username").unwrap().to_owned(), + Ulid::from_string(user.get_str("_id").unwrap()).unwrap(), + user.get_str("username").unwrap().to_string(), user )) } else { - Outcome::Failure((Status::BadRequest, AuthError::Invalid)) + Outcome::Failure((Status::Forbidden, AuthError::Invalid)) } }, _ => Outcome::Failure((Status::BadRequest, AuthError::BadCount)), diff --git a/src/routes/account.rs b/src/routes/account.rs index 689a2f1..8f9147a 100644 --- a/src/routes/account.rs +++ b/src/routes/account.rs @@ -18,13 +18,6 @@ fn gen_token(l: usize) -> String { .collect::<String>() } -#[get("/")] -pub fn root(user: User) -> String { - let User ( id, username, _doc ) = user; - - format!("hello, {}! [id: {}]", username, id) -} - #[derive(Serialize, Deserialize)] pub struct Create { username: String, @@ -72,7 +65,7 @@ pub fn create(info: Json<Create>) -> JsonValue { } if let Ok(hashed) = hash(info.password.clone(), 10) { - let access_token = gen_token(64); + let access_token = gen_token(92); let code = gen_token(48); match col.insert_one(doc! { @@ -120,8 +113,8 @@ pub fn verify_email(code: String) -> JsonValue { if let Some(u) = col.find_one(doc! { "email_verification.code": code.clone() }, None).expect("Failed user lookup") { - let ev = u.get_document("email_verification").expect("Missing email_verification on user object!"); - let expiry = ev.get_utc_datetime("expiry").expect("Missing expiry date on email_verification!"); + let ev = u.get_document("email_verification").expect("DOC[email_verification]"); + let expiry = ev.get_utc_datetime("expiry").expect("DOC[expiry]"); if Utc::now() > *expiry { json!({ @@ -129,7 +122,7 @@ pub fn verify_email(code: String) -> JsonValue { "error": "Token has expired!", }) } else { - let target = ev.get_str("target").expect("Missing target email on email_verification!"); + let target = ev.get_str("target").expect("DOC[target]"); col.update_one( doc! { "_id": u.get_str("_id").expect("Failed to retrieve user id.") }, doc! { @@ -179,22 +172,35 @@ pub fn resend_email(info: Json<Resend>) -> JsonValue { if let Some(u) = col.find_one(doc! { "email_verification.target": info.email.clone() }, None).expect("Failed user lookup") { - let ev = u.get_document("email_verification").expect("Missing email_verification on user object!"); - let rate_limit = ev.get_utc_datetime("rate_limit").expect("Missing rate_limit on email_verification!"); + let ev = u.get_document("email_verification").expect("DOC[email_verification]"); + let expiry = ev.get_utc_datetime("expiry").expect("DOC[expiry]"); + let rate_limit = ev.get_utc_datetime("rate_limit").expect("DOC[rate_limit]"); if Utc::now() < *rate_limit { json!({ "success": false, - "error": "Hit rate limit! Please try again in a minute or so." + "error": "Hit rate limit! Please try again in a minute or so.", }) } else { + let mut new_expiry = UtcDatetime(Utc::now() + chrono::Duration::days(1)); + if info.email.clone() != u.get_str("email").expect("DOC[email]") { + if Utc::now() > *expiry { + return json!({ + "success": "false", + "error": "For security reasons, please login and change your email again.", + }) + } + + new_expiry = UtcDatetime(*expiry); + } + let code = gen_token(48); col.update_one( doc! { "_id": u.get_str("_id").expect("Failed to retrieve user id.") }, doc! { "$set": { "email_verification.code": code.clone(), - "email_verification.expiry": UtcDatetime(Utc::now() + chrono::Duration::days(1)), + "email_verification.expiry": new_expiry, "email_verification.rate_limit": UtcDatetime(Utc::now() + chrono::Duration::minutes(1)), }, }, @@ -210,7 +216,7 @@ pub fn resend_email(info: Json<Resend>) -> JsonValue { }), false => json!({ "success": false, - "error": "Failed to send email! Likely an issue with the backend API." + "error": "Failed to send email! Likely an issue with the backend API.", }) } } @@ -238,16 +244,16 @@ pub fn login(info: Json<Login>) -> JsonValue { if let Some(u) = col.find_one(doc! { "email": info.email.clone() }, None).expect("Failed user lookup") { - match verify(info.password.clone(), u.get_str("password").expect("Missing password in user object!")) + match verify(info.password.clone(), u.get_str("password").expect("DOC[password]")) .expect("Failed to check hash of password.") { true => { let token = match u.get_str("access_token") { Ok(t) => t.to_string(), Err(_) => { - let token = gen_token(64); + let token = gen_token(92); col.update_one( - doc! { "_id": u.get_str("_id").expect("Missing id in user object!") }, + doc! { "_id": u.get_str("_id").expect("DOC[id]") }, doc! { "$set": { "access_token": token.clone() } }, None ).expect("Failed to update user object"); diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 14f1a24..8b2aeb5 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,8 +1,10 @@ use rocket::Rocket; mod account; +mod user; pub fn mount(rocket: Rocket) -> Rocket { rocket - .mount("/api/account", routes![ account::root, account::create, account::verify_email, account::resend_email, account::login ]) + .mount("/api/account", routes![ account::create, account::verify_email, account::resend_email, account::login ]) + .mount("/api/users", routes![ user::me ]) } diff --git a/src/routes/user.rs b/src/routes/user.rs new file mode 100644 index 0000000..7515ea2 --- /dev/null +++ b/src/routes/user.rs @@ -0,0 +1,18 @@ +use crate::auth::User; + +use rocket_contrib::json::{ JsonValue }; +use bson::{ doc }; + +#[get("/@me")] +pub fn me(user: User) -> JsonValue { + let User ( id, username, doc ) = user; + + json!({ + "id": id.to_string(), + "username": username, + "email": doc.get_str("email").expect("Missing email in user object!"), + "verified": doc.get_document("email_verification").expect("DOC[email_verification]") + .get_bool("verified").expect("DOC[verified]"), + "created_timestamp": id.datetime().timestamp(), + }) +} -- GitLab