From 153400959c8900cf8e46275153c8cd98fcf75dd2 Mon Sep 17 00:00:00 2001 From: Bat Date: Fri, 6 Jul 2018 11:51:19 +0200 Subject: [PATCH] Actually validate forms --- .../2018-04-22-093322_create_instances/up.sql | 2 +- src/routes/blogs.rs | 26 +++++++--- src/routes/comments.rs | 47 ++++++++++++----- src/routes/posts.rs | 31 +++++++++--- src/routes/session.rs | 50 ++++++++++--------- src/routes/user.rs | 39 +++++++-------- 6 files changed, 122 insertions(+), 73 deletions(-) diff --git a/migrations/2018-04-22-093322_create_instances/up.sql b/migrations/2018-04-22-093322_create_instances/up.sql index e6689b0f..46fd4a3c 100644 --- a/migrations/2018-04-22-093322_create_instances/up.sql +++ b/migrations/2018-04-22-093322_create_instances/up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +l-- Your SQL goes here CREATE TABLE instances ( id SERIAL PRIMARY KEY, local_domain VARCHAR NOT NULL, diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index 0dc44838..e996e6c2 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -5,7 +5,7 @@ use rocket::{ }; use rocket_contrib::Template; use serde_json; -use validator::{Validate, ValidationError}; +use validator::{Validate, ValidationError, ValidationErrors}; use plume_common::activity_pub::ActivityStream; use plume_common::utils; @@ -66,15 +66,22 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> { } #[post("/blogs/new", data = "")] -fn create(conn: DbConn, data: LenientForm, user: User) -> Redirect { +fn create(conn: DbConn, data: LenientForm, user: User) -> Result { let form = data.get(); let slug = utils::make_actor_id(form.title.to_string()); + let slug_taken_err = Blog::find_local(&*conn, slug.clone()).ok_or(ValidationError::new("existing_slug")); - if Blog::find_local(&*conn, slug.clone()).is_some() || slug.len() == 0 { - Redirect::to(uri!(new)) - } else { + let mut errors = match form.validate() { + Ok(_) => ValidationErrors::new(), + Err(e) => e + }; + if let Err(e) = slug_taken_err { + errors.add("title", e) + } + + if errors.is_empty() { let blog = Blog::insert(&*conn, NewBlog::new_local( - slug.to_string(), + slug.clone(), form.title.to_string(), String::from(""), Instance::local_id(&*conn) @@ -87,7 +94,12 @@ fn create(conn: DbConn, data: LenientForm, user: User) -> Redirect is_owner: true }); - Redirect::to(uri!(details: name = slug)) + Ok(Redirect::to(uri!(details: name = slug.clone()))) + } else { + Err(Template::render("blogs/new", json!({ + "account": user, + "errors": errors.inner() + }))) } } diff --git a/src/routes/comments.rs b/src/routes/comments.rs index 78fd2852..5248e9b9 100644 --- a/src/routes/comments.rs +++ b/src/routes/comments.rs @@ -2,6 +2,7 @@ use rocket::{ request::LenientForm, response::Redirect }; +use rocket_contrib::Template; use serde_json; use validator::Validate; @@ -24,22 +25,44 @@ struct NewCommentForm { } #[post("/~///comment", data = "")] -fn create(blog_name: String, slug: String, data: LenientForm, user: User, conn: DbConn) -> Redirect { +fn create(blog_name: String, slug: String, data: LenientForm, user: User, conn: DbConn) -> Result { let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).unwrap(); let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).unwrap(); let form = data.get(); + form.validate() + .map(|_| { + let (new_comment, id) = NewComment::build() + .content(form.content.clone()) + .in_response_to_id(form.responding_to.clone()) + .post(post.clone()) + .author(user.clone()) + .create(&*conn); - let (new_comment, id) = NewComment::build() - .content(form.content.clone()) - .in_response_to_id(form.responding_to.clone()) - .post(post) - .author(user.clone()) - .create(&*conn); + let instance = Instance::get_local(&*conn).unwrap(); + instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error")) + .expect("We are not compatible with ourselve: local broadcast failed (new comment)"); + broadcast(&user, new_comment, user.get_followers(&*conn)); - let instance = Instance::get_local(&*conn).unwrap(); - instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error")) - .expect("We are not compatible with ourselve: local broadcast failed (new comment)"); - broadcast(&user, new_comment, user.get_followers(&*conn)); + Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id)) + }) + .map_err(|errors| { + // TODO: de-duplicate this code + let comments = Comment::list_by_post(&*conn, post.id); - Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id)) + Template::render("posts/details", json!({ + "author": post.get_authors(&*conn)[0].to_json(&*conn), + "post": post, + "blog": blog, + "comments": comments.into_iter().map(|c| c.to_json(&*conn)).collect::>(), + "n_likes": post.get_likes(&*conn).len(), + "has_liked": user.has_liked(&*conn, &post), + "n_reshares": post.get_reshares(&*conn).len(), + "has_reshared": user.has_reshared(&*conn, &post), + "account": user, + "date": &post.creation_date.timestamp(), + "previous": form.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn)), + "user_fqn": user.get_fqn(&*conn), + "errors": errors + })) + }) } diff --git a/src/routes/posts.rs b/src/routes/posts.rs index a27b275b..7e6cb116 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -4,7 +4,7 @@ use rocket::request::LenientForm; use rocket::response::{Redirect, Flash}; use rocket_contrib::Template; use serde_json; -use validator::{Validate, ValidationError}; +use validator::{Validate, ValidationError, ValidationErrors}; use plume_common::activity_pub::{broadcast, ActivityStream}; use plume_common::utils; @@ -94,22 +94,32 @@ fn valid_slug(title: &str) -> Result<(), ValidationError> { let slug = title.to_string().to_kebab_case(); if slug.len() == 0 { Err(ValidationError::new("empty_slug")) + } else if slug == "new" { + Err(ValidationError::new("invalid_slug")) } else { Ok(()) } } #[post("/~//new", data = "")] -fn create(blog_name: String, data: LenientForm, user: User, conn: DbConn) -> Redirect { +fn create(blog_name: String, data: LenientForm, user: User, conn: DbConn) -> Result { let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).unwrap(); let form = data.get(); let slug = form.title.to_string().to_kebab_case(); + let slug_taken_err = Blog::find_local(&*conn, slug.clone()).ok_or(ValidationError::new("existing_slug")); + + let mut errors = match form.validate() { + Ok(_) => ValidationErrors::new(), + Err(e) => e + }; + if let Err(e) = slug_taken_err { + errors.add("title", e) + } - if !user.is_author_in(&*conn, blog.clone()) { - Redirect::to(uri!(super::blogs::details: name = blog_name)) - } else { - if slug == "new" || Post::find_by_slug(&*conn, slug.clone(), blog.id).is_some() { - Redirect::to(uri!(new: blog = blog_name)) + if errors.is_empty() { + if !user.is_author_in(&*conn, blog.clone()) { + // actually it's not "Ok"… + Ok(Redirect::to(uri!(super::blogs::details: name = blog_name))) } else { let (content, mentions) = utils::md_to_html(form.content.to_string().as_ref()); @@ -135,7 +145,12 @@ fn create(blog_name: String, data: LenientForm, user: User, conn: D let act = post.create_activity(&*conn); broadcast(&user, act, user.get_followers(&*conn)); - Redirect::to(uri!(details: blog = blog_name, slug = slug)) + Ok(Redirect::to(uri!(details: blog = blog_name, slug = slug))) } + } else { + Err(Template::render("posts/new", json!({ + "account": user, + "errors": errors.inner() + }))) } } diff --git a/src/routes/session.rs b/src/routes/session.rs index 1e033e90..4f5062ab 100644 --- a/src/routes/session.rs +++ b/src/routes/session.rs @@ -1,11 +1,10 @@ -use gettextrs::gettext; use rocket::{ http::{Cookie, Cookies, uri::Uri}, - response::{Redirect, status::NotFound}, + response::Redirect, request::{LenientForm,FlashMessage} }; use rocket_contrib::Template; -use validator::{Validate, ValidationError}; +use validator::{Validate, ValidationError, ValidationErrors}; use plume_models::{ db_conn::DbConn, @@ -42,28 +41,33 @@ struct LoginForm { } #[post("/login", data = "")] -fn create(conn: DbConn, data: LenientForm, flash: Option, mut cookies: Cookies) -> Result> { +fn create(conn: DbConn, data: LenientForm, flash: Option, mut cookies: Cookies) -> Result { let form = data.get(); - let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) { - Some(usr) => Ok(usr), - None => match User::find_local(&*conn, form.email_or_name.to_string()) { - Some(usr) => Ok(usr), - None => Err(gettext("Invalid username or password")) - } + let user = User::find_by_email(&*conn, form.email_or_name.to_string()) + .map(|u| Ok(u)) + .unwrap_or_else(|| User::find_local(&*conn, form.email_or_name.to_string()).map(|u| Ok(u)).unwrap_or(Err(()))); + + let mut errors = match form.validate() { + Ok(_) => ValidationErrors::new(), + Err(e) => e }; - match user { - Ok(usr) => { - if usr.auth(form.password.to_string()) { - cookies.add_private(Cookie::new(AUTH_COOKIE, usr.id.to_string())); - Ok(Redirect::to(Uri::new(flash - .and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None }) - .unwrap_or("/".to_owned())) - )) - } else { - Err(NotFound(gettext("Invalid username or password"))) - } - }, - Err(e) => Err(NotFound(String::from(e))) + if let Err(_) = user.clone() { + errors.add("email_or_name", ValidationError::new("invalid_login")) + } else if !user.clone().expect("User not found").auth(form.password.clone()) { + errors.add("email_or_name", ValidationError::new("invalid_login")) + } + + if errors.is_empty() { + cookies.add_private(Cookie::new(AUTH_COOKIE, user.unwrap().id.to_string())); + Ok(Redirect::to(Uri::new(flash + .and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None }) + .unwrap_or("/".to_owned())) + )) + } else { + Err(Template::render("session/login", json!({ + "account": user, + "errors": errors.inner() + }))) } } diff --git a/src/routes/user.rs b/src/routes/user.rs index 0cc0576b..d35a56cb 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -180,29 +180,24 @@ fn passwords_match(form: &NewUserForm) -> Result<(), ValidationError> { } #[post("/users/new", data = "")] -fn create(conn: DbConn, data: LenientForm) -> Result { +fn create(conn: DbConn, data: LenientForm) -> Result { let form = data.get(); - - if form.username.clone().len() < 1 { - Err(String::from("Username is required")) - } else if form.email.clone().len() < 1 { - Err(String::from("Email is required")) - } else if form.password.clone().len() < 8 { - Err(String::from("Password should be at least 8 characters long")) - } else if form.password == form.password_confirmation { - NewUser::new_local( - &*conn, - form.username.to_string(), - form.username.to_string(), - false, - String::from(""), - form.email.to_string(), - User::hash_pass(form.password.to_string()) - ).update_boxes(&*conn); - Ok(Redirect::to(uri!(super::session::new))) - } else { - Err(String::from("Passwords don't match")) - } + form.validate() + .map(|_| { + NewUser::new_local( + &*conn, + form.username.to_string(), + form.username.to_string(), + false, + String::from(""), + form.email.to_string(), + User::hash_pass(form.password.to_string()) + ).update_boxes(&*conn); + Redirect::to(uri!(super::session::new)) + }) + .map_err(|e| Template::render("users/new", json!({ + "errors": e.inner() + }))) } #[get("/@//outbox")]