From f01eae65851998bd99f72bfd9ff6dd2fe7532826 Mon Sep 17 00:00:00 2001 From: SL-Lee <45960387+SL-Lee@users.noreply.github.com> Date: Mon, 8 Mar 2021 15:52:17 +0800 Subject: [PATCH] Add validation checks for forms Also refactored code for creating messages to allow multiple messages to be created within a single route --- Cargo.lock | 70 ++++++++++++++++++++++ Cargo.toml | 1 + src/forms.rs | 23 +++++++- src/lib.rs | 20 ++++--- src/scopes/main_scope.rs | 122 +++++++++++++++++++++++++++++---------- 5 files changed, 194 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e14f7d4..63d3b43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,7 @@ dependencies = [ "serde", "serde_json", "tera", + "validator", ] [[package]] @@ -1141,6 +1142,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7280c75fb2e2fc47080ec80ccc481376923acb04501957fc38f935c3de5088" + [[package]] name = "ignore" version = "0.4.17" @@ -1626,6 +1633,30 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.2", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.2", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2460,6 +2491,45 @@ dependencies = [ "v_escape", ] +[[package]] +name = "validator" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d6937c33ec6039d8071bcf72933146b5bbe378d645d8fa59bdadabfc2a249" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", + "validator_types", +] + +[[package]] +name = "validator_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286b4497f270f59276a89ae0ad109d5f8f18c69b613e3fb22b61201aadb0c4d" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a" + [[package]] name = "vcpkg" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index a1456f2..bc98592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ scrypt = "0.6.3" serde = "1.0.123" serde_json = "1.0.64" tera = "1.6.1" +validator = { version = "0.12.0", features = ["derive"] } diff --git a/src/forms.rs b/src/forms.rs index 227be8c..d0b969f 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -1,14 +1,33 @@ use serde::Deserialize; +use validator::Validate; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Validate)] pub struct LoginForm { + #[validate(length( + max = 32, + message = "Username must not be longer than 32 characters." + ))] pub username: String, + #[validate(length( + min = 8, + max = 32, + message = "Password length must be between 8 and 32 characters." + ))] pub password: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Validate)] pub struct SignupForm { + #[validate(length( + max = 32, + message = "Username must not be longer than 32 characters." + ))] pub username: String, + #[validate(length( + min = 8, + max = 32, + message = "Password length must be between 8 and 32 characters." + ))] pub password: String, } diff --git a/src/lib.rs b/src/lib.rs index f6c3e2e..a2250d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ pub fn create_response_for_template( serde_json::from_str::>(messages_cookie.value()) { context.insert("messages", &messages); - messages_cookie.set_value(""); + messages_cookie.set_value("[]"); HttpResponse::Ok().cookie(messages_cookie).take() } else { HttpResponse::Ok() @@ -55,17 +55,20 @@ pub fn create_response_for_template( } } +pub fn get_messages_cookie(req: &HttpRequest) -> Cookie { + req.cookie("messages").unwrap_or( + Cookie::build("messages", "[]") + .same_site(actix_web::cookie::SameSite::Lax) + .finish(), + ) +} + pub fn create_message( - req: &HttpRequest, + messages_cookie: &mut Cookie, message_category: String, message_title: String, message_content: String, -) -> Cookie { - let mut messages_cookie = req.cookie("messages").unwrap_or( - Cookie::build("messages", "[]") - .same_site(actix_web::cookie::SameSite::Lax) - .finish(), - ); +) { let mut messages = serde_json::from_str::>(messages_cookie.value()) .unwrap_or(vec![]); @@ -75,5 +78,4 @@ pub fn create_message( content: message_content, }); messages_cookie.set_value(serde_json::to_string(&messages).unwrap()); - messages_cookie } diff --git a/src/scopes/main_scope.rs b/src/scopes/main_scope.rs index 9115279..48c0a95 100644 --- a/src/scopes/main_scope.rs +++ b/src/scopes/main_scope.rs @@ -10,6 +10,7 @@ use scrypt::{ Scrypt, }; use tera::Tera; +use validator::Validate; use crate::{ create_message, create_response_for_template, @@ -18,7 +19,7 @@ use crate::{ schema::user, }, forms::{LoginForm, SignupForm}, - initialize_context, DbConnectionPool, + get_messages_cookie, initialize_context, DbConnectionPool, }; pub fn get_scope() -> Scope { @@ -73,6 +74,30 @@ async fn process_login( return HttpResponse::Found().header("location", "/").finish(); } + let mut messages_cookie = get_messages_cookie(&req); + + if let Err(validation_errors) = form_data.validate() { + validation_errors.field_errors().iter().for_each( + |(_, &field_errors)| { + field_errors + .iter() + .filter_map(|error| error.message.as_ref()) + .for_each(|error_message| { + create_message( + &mut messages_cookie, + "danger".to_string(), + "Login unsuccessful".to_string(), + error_message.to_string(), + ); + }); + }, + ); + return HttpResponse::Found() + .header("location", "/login") + .cookie(messages_cookie) + .finish(); + } + let db_connection = pool.get().expect("Couldn't get db connection from pool"); let user = user::table @@ -88,36 +113,43 @@ async fn process_login( { Ok(_) => { identity.remember(user.username.clone()); + create_message( + &mut messages_cookie, + "success".to_string(), + "Login successful".to_string(), + "Logged in successfully.".to_string(), + ); HttpResponse::Found() .header("location", "/app") - .cookie(create_message( - &req, - "success".to_string(), - "Login successful".to_string(), - "Logged in successfully.".to_string(), - )) + .cookie(messages_cookie) .finish() } - Err(_) => HttpResponse::Found() - .header("location", "/login") - .cookie(create_message( - &req, + Err(_) => { + create_message( + &mut messages_cookie, "danger".to_string(), "Login unsuccessful".to_string(), "Incorrect username and/or password.".to_string(), - )) - .finish(), + ); + HttpResponse::Found() + .header("location", "/login") + .cookie(messages_cookie) + .finish() + } } } - Err(_) => HttpResponse::Found() - .header("location", "/login") - .cookie(create_message( - &req, + Err(_) => { + create_message( + &mut messages_cookie, "danger".to_string(), "Login unsuccessful".to_string(), "Incorrect username and/or password.".to_string(), - )) - .finish(), + ); + HttpResponse::Found() + .header("location", "/login") + .cookie(messages_cookie) + .finish() + } } } @@ -147,6 +179,30 @@ async fn process_signup( return HttpResponse::Found().header("location", "/").finish(); } + let mut messages_cookie = get_messages_cookie(&req); + + if let Err(validation_errors) = form_data.validate() { + validation_errors.field_errors().iter().for_each( + |(_, &field_errors)| { + field_errors + .iter() + .filter_map(|error| error.message.as_ref()) + .for_each(|error_message| { + create_message( + &mut messages_cookie, + "danger".to_string(), + "Signup unsuccessful".to_string(), + error_message.to_string(), + ); + }); + }, + ); + return HttpResponse::Found() + .header("location", "/signup") + .cookie(messages_cookie) + .finish(); + } + let password = form_data.password.as_bytes(); let salt = SaltString::generate(&mut OsRng); let password_hash = Scrypt @@ -170,27 +226,31 @@ async fn process_signup( { Ok(_) => { identity.remember(form_data.username.clone()); + create_message( + &mut messages_cookie, + "success".to_string(), + "Signup successful".to_string(), + "Signed up in successfully.".to_string(), + ); HttpResponse::Found() .header("location", "/app") - .cookie(create_message( - &req, - "success".to_string(), - "Signup successful".to_string(), - "Signed up in successfully.".to_string(), - )) + .cookie(messages_cookie) .finish() } - Err(_) => HttpResponse::Found() - .header("location", "/signup") - .cookie(create_message( - &req, + Err(_) => { + create_message( + &mut messages_cookie, "danger".to_string(), "Signup unsuccessful".to_string(), "An account with this username already exists. Please try \ again with a different username." .to_string(), - )) - .finish(), + ); + HttpResponse::Found() + .header("location", "/signup") + .cookie(messages_cookie) + .finish() + } } }