From 1619570b2891a225b0414edaa42028ed06ed0568 Mon Sep 17 00:00:00 2001 From: jknoptrix_ Date: Sun, 4 Jun 2023 22:42:35 +0500 Subject: [PATCH] validators, form_process rewrite, form_config --- .env | 8 ++- Cargo.lock | 1 + Cargo.toml | 1 + src/check_config.rs | 18 +++--- src/email_validator/mod.rs | 22 ++++++++ src/form_config/mod.rs | 113 +++++++++++++++++++++++++++++++++++++ src/form_process.rs | 90 ++++++++++++++--------------- src/help | 90 +++++++++++++++++++++++++++++ src/input_validator/mod.rs | 11 ++++ src/main.rs | 17 +++++- 10 files changed, 310 insertions(+), 61 deletions(-) create mode 100644 src/email_validator/mod.rs create mode 100644 src/form_config/mod.rs create mode 100644 src/help create mode 100644 src/input_validator/mod.rs diff --git a/.env b/.env index d4c6d0b..b2b701c 100644 --- a/.env +++ b/.env @@ -1,5 +1,7 @@ -SERVER_IP=127.0.0.1:8080 -SMTP_USER=your_smtp_user +# !! LOCALHOST: 127.0.0.1:8080 +# !! IP SHOULD BE DECLARED IN FORMAT IP:PORT +SERVER_IP=your_ip SMTP_PASS=your_smtp_pass SMTP_HOST=your_smtp_host -DATABASE_URL=postgres://user:password@host/database \ No newline at end of file +DATABASE_URL=postgres://user:password@host/database +SMTP_USER=your_smtp_user \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 17234b3..68f2d45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1970,6 +1970,7 @@ dependencies = [ "futures-util", "lettre 0.10.4", "lettre_email", + "regex", "serde_json", "tera", ] diff --git a/Cargo.toml b/Cargo.toml index 5365609..4301268 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ futures = "0.3.28" futures-util = "0.3.28" lettre = "0.10.4" lettre_email = "0.9.4" +regex = "1.8.3" serde_json = "1.0.96" tera = "1.11.0" diff --git a/src/check_config.rs b/src/check_config.rs index 9502c95..a6957c9 100644 --- a/src/check_config.rs +++ b/src/check_config.rs @@ -8,28 +8,28 @@ use crate::log::info; use crate::time::current_time; const DEFAULT_CONFIG: &str = -r#"SERVER_IP=your_ip -// !! LOCALHOST: 127.0.0.1:8080 -// !! IP SHOULD BE DECLARED IN FORMAT IP:PORT +r#"# !! LOCALHOST: 127.0.0.1:8080 +# !! IP SHOULD BE DECLARED IN FORMAT IP:PORT +SERVER_IP=your_ip SMTP_USER=your_smtp_user SMTP_PASS=your_smtp_pass SMTP_HOST=your_smtp_host DATABASE_URL=postgres://user:password@host/database"#; -pub fn check_config() { +pub fn check_config() -> Result<(), std::io::Error> { let now = current_time(); let env_path = Path::new(".env"); match env_path.exists() { false => { // create .env file with default values if it does not exist - fs::write(env_path, DEFAULT_CONFIG).expect("Failed to create .env file"); + fs::write(env_path, DEFAULT_CONFIG)?; log_info!(&now, "Created .env file. Please configure it before running the program again."); thread::sleep(Duration::from_secs(10)); std::process::exit(0); } true => { // check for missing values and add them to the file if necessary - let mut config = fs::read_to_string(env_path).expect("Failed to read .env file"); + let mut config = fs::read_to_string(env_path)?; let mut updated = false; for line in DEFAULT_CONFIG.lines() { let key = line.split('=').next().unwrap(); @@ -44,7 +44,7 @@ pub fn check_config() { } match updated { true => { - fs::write(env_path, config).expect("Failed to update .env file"); + fs::write(env_path, config)?; log_info!(&now, "\x1B[1m\x1b[32mUpdated .env file with missing values. Please configure them before running the program again.\x1B[0m"); thread::sleep(Duration::from_secs(10)); std::process::exit(0); @@ -53,4 +53,6 @@ pub fn check_config() { } } } -} + + Ok(()) +} \ No newline at end of file diff --git a/src/email_validator/mod.rs b/src/email_validator/mod.rs new file mode 100644 index 0000000..fcdc46b --- /dev/null +++ b/src/email_validator/mod.rs @@ -0,0 +1,22 @@ +use regex::Regex; + +pub trait EmailValidator { + fn is_valid(&self, email: &str) -> bool; +} + +pub struct EmailRegexValidator { + regex: Regex, +} + +impl EmailRegexValidator { + pub fn new() -> Self { + let regex = Regex::new(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); + Self { regex } + } +} + +impl EmailValidator for EmailRegexValidator { + fn is_valid(&self, email: &str) -> bool { + self.regex.is_match(email) + } +} diff --git a/src/form_config/mod.rs b/src/form_config/mod.rs new file mode 100644 index 0000000..f20b960 --- /dev/null +++ b/src/form_config/mod.rs @@ -0,0 +1,113 @@ +use crate::email_validator::{EmailRegexValidator, EmailValidator}; +use crate::input_validator::NonEmptyInputValidator; +use std::env; +use tera::Context; + +pub trait FormConfig { + fn set_email(&mut self, email: String); + fn set_name(&mut self, name: String); + fn set_message_body(&mut self, message_body: String); + fn smtp_user(&self) -> String; + fn smtp_pass(&self) -> String; + fn smtp_host(&self) -> String; + fn input_validator(&self) -> &NonEmptyInputValidator; + fn message_printed(&mut self) -> &mut bool; + fn email(&self) -> String; + fn name(&self) -> String; + fn message_body(&self) -> String; + fn context(&mut self) -> &mut Context; + fn email_validator(&self) -> &dyn EmailValidator; +} + +pub struct FormConfigImpl { + smtp_user: String, + smtp_pass: String, + smtp_host: String, + email_validator: EmailRegexValidator, + input_validator: NonEmptyInputValidator, + message_printed: bool, + email: String, + name: String, + message_body: String, + context: Context, +} + +impl FormConfigImpl { + pub fn new() -> Self { + let smtp_user = env::var("SMTP_USER").expect("SMTP_USER must be set"); + let smtp_pass = env::var("SMTP_PASS").expect("SMTP_PASS must be set"); + let smtp_host = env::var("SMTP_HOST").expect("SMTP_HOST must be set"); + let email_validator = EmailRegexValidator::new(); + let input_validator = NonEmptyInputValidator; + let mut context = Context::new(); + context.insert("name", "User"); + context.insert("context", "Rust Form"); + + Self { + smtp_user, + smtp_pass, + smtp_host, + email_validator, + input_validator, + message_printed: false, + email: String::new(), + name: String::new(), + message_body: String::new(), + context, + } + } +} + +impl FormConfig for FormConfigImpl { + fn smtp_user(&self) -> String { + self.smtp_user.clone() + } + + fn smtp_pass(&self) -> String { + self.smtp_pass.clone() + } + + fn smtp_host(&self) -> String { + self.smtp_host.clone() + } + + fn email_validator(&self) -> &dyn EmailValidator { + &self.email_validator + } + + fn input_validator(&self) -> &NonEmptyInputValidator { + &self.input_validator + } + + fn message_printed(&mut self) -> &mut bool { + &mut self.message_printed + } + + fn email(&self) -> String { + self.email.clone() + } + + fn name(&self) -> String { + self.name.clone() + } + + fn message_body(&self) -> String { + self.message_body.clone() + } + + fn context(&mut self) -> &mut Context { + &mut self.context + } + + fn set_email(&mut self, email: String) { + self.email = email; + } + + fn set_name(&mut self, name: String) { + self.name = name; + } + + fn set_message_body(&mut self, message_body: String) { + self.message_body = message_body; + } +} diff --git a/src/form_process.rs b/src/form_process.rs index 8d5a75e..c033024 100644 --- a/src/form_process.rs +++ b/src/form_process.rs @@ -1,81 +1,77 @@ +use crate::input_validator::InputValidator; +use crate::form_config::{FormConfig, FormConfigImpl}; +use crate::{log_info, log_warn, log_error}; +use crate::log::{info, error, warn}; +use crate::time::current_time; + use actix_web::{web, HttpResponse}; use lettre::{Message, SmtpTransport, Transport}; use lettre::transport::smtp::authentication::Credentials; -use tera::{Context, Tera}; -use std::env; +use tera::{Tera}; #[allow(non_snake_case)] pub async fn process_form(form: web::Form>) -> HttpResponse { - - let mut context = Context::new(); // create a new Tera context - context.insert("name", "User"); - context.insert("context", "Rust Form"); - - // defining SMTP server credentials as static variables - let SMTP_USER = env::var("SMTP_USER").expect("SMTP_USER must be set"); // get the SMTP_USER from the .env file - let SMTP_PASS = env::var("SMTP_PASS").expect("SMTP_PASS must be set"); // get the SMTP_PASS from the .env file - let SMTP_HOST = env::var("SMTP_HOST").expect("SMTP_HOST must be set"); // get the SMTP_HOST from the .env file | WITHOUT SSL:// OR TLS://!!! + let now = current_time(); + let mut config = FormConfigImpl::new(); - let mut email = String::new(); - let mut name = String::new(); - let mut message_body = String::new(); - - for (key, value) in form.into_inner() { // iterate over the form data - match value.is_empty() { - true => { - context.insert("error", &format!("{} cannot be empty", key)); - match (name.is_empty(), email.is_empty(), message_body.is_empty()) { + for (key, value) in form.into_inner() { + match config.input_validator().is_valid(&value) { + false => { + config.context().insert("error", &format!("{} cannot be empty", key)); + match (config.name().is_empty(), config.email().is_empty(), config.message_body().is_empty()) { (true, true, true) => { - context.insert("error", "smtp is not magic, type smth"); - println!("User is bruh"); + //if !*config.message_printed() { + config.context().insert("error", "smtp is not magic, type smth"); + log_error!(&now, "User didn't entered anything for: {}", key); + // *config.message_printed() = true; + //} }, - _ => println!("User didn't entered {}", key), + _ => log_warn!(&now, "User didn't entered {}", key), } continue; } - false => { - context.insert(key.as_str(), &value); - println!("User entered {} for {}", value, key); + true => { + config.context().insert(key.as_str(), &value); + log_info!(&now, "User entered {} for {}", value, key); match key.as_str() { - "email" => email = value, - "name" => name = value, - "message" => message_body = value, + "email" => config.set_email(value), + "name" => config.set_name(value), + "message" => config.set_message_body(value), _ => (), } } } } - // creating let with SMTP credentials - let credentials = Credentials::new(SMTP_USER.to_string(), SMTP_PASS.to_string()); + - // creating let for an SMTP transport - // i was trying to make relay(SMTP_SERVER) but it looks like that relay doesn't like this idk why - let mailer = SmtpTransport::relay(&SMTP_HOST) + let credentials = Credentials::new(config.smtp_user(), config.smtp_pass()); + let mailer = SmtpTransport::relay(&config.smtp_host()) .unwrap() .credentials(credentials) .build(); - match (email.is_empty() || !email.contains('@'), SMTP_USER.is_empty() || !SMTP_USER.contains('@')) { // validate the email address and SMTP username - (true, _) => context.insert("error", "Invalid email address"), - (_, true) => context.insert("error", "Invalid SMTP username"), - (false, false) => { - let message = Message::builder() // create an email message - .from(SMTP_USER.parse().unwrap()) - .to(email.parse().unwrap()) + match (config.email_validator().is_valid(&config.email()), config.email_validator().is_valid(&config.smtp_user())) { + (false, _) => config.context().insert("error", "Invalid email address"), + (_, false) => config.context().insert("error", "Invalid SMTP username"), + (true, true) => { + let message = Message::builder() + .from(config.smtp_user().parse().unwrap()) + .to(config.email().parse().unwrap()) .subject("Form Submission") .body(format!( "Thank you for your submission, {}!\n\nYour message:\n{}", - name, message_body + config.name(), config.message_body() )) .unwrap(); - // send the email message match mailer.send(&message) { - Ok(_) => println!("Email sended: {}", email), - Err(e) => eprintln!("Error sending email: {:?}", e), + Ok(_) => log_info!(&now, "Email sended: {}", config.email()), + Err(e) => log_error!(&now, "Error sending email: {:?}", e), } } } - let body = Tera::one_off(include_str!("templates/form.tera"), &context, false).unwrap(); // render the template with the context - HttpResponse::Ok().body(body) // return the rendered template as the response body + + + let body = Tera::one_off(include_str!("templates/form.tera"), &config.context(), false).unwrap(); + HttpResponse::Ok().body(body) } diff --git a/src/help b/src/help new file mode 100644 index 0000000..0ad27fe --- /dev/null +++ b/src/help @@ -0,0 +1,90 @@ +use crate::email_validator::{EmailRegexValidator, EmailValidator}; +use crate::input_validator::{NonEmptyInputValidator, InputValidator}; +use crate::form_config::{FormConfig, FormConfigImpl}; +use crate::{log_info, log_warn, log_error}; +use crate::log::{info, error, warn}; + +use actix_web::{web, HttpResponse}; +use lettre::{Message, SmtpTransport, Transport}; +use lettre::transport::smtp::authentication::Credentials; +use tera::{Context, Tera}; +use std::env; + +#[allow(non_snake_case)] +pub async fn process_form(form: web::Form>) -> HttpResponse { + + let SMTP_USER = env::var("SMTP_USER").expect("SMTP_USER must be set"); + let SMTP_PASS = env::var("SMTP_PASS").expect("SMTP_PASS must be set"); + let SMTP_HOST = env::var("SMTP_HOST").expect("SMTP_HOST must be set"); + let email_validator = EmailRegexValidator::new(); + let input_validator = NonEmptyInputValidator; + let mut message_printed = false; + let mut email = String::new(); + let mut name = String::new(); + let mut message_body = String::new(); + let mut context = Context::new(); + context.insert("name", "User"); + context.insert("context", "Rust Form"); + let now = current_time(); + + for (key, value) in form.into_inner() { + match input_validator.is_valid(&value) { + false => { + context.insert("error", &format!("{} cannot be empty", key)); + match (name.is_empty(), email.is_empty(), message_body.is_empty()) { + (true, true, true) => { + if !message_printed { // im so sorry but i have to do that lol + context.insert("error", "smtp is not magic, type smth"); + println!("User didn't entered anything"); + message_printed = true; + } + }, + _ => println!("User didn't entered {}", key), + } + continue; + } + true => { + context.insert(key.as_str(), &value); + println!("User entered {} for {}", value, key); + match key.as_str() { + "email" => email = value, + "name" => name = value, + "message" => message_body = value, + _ => (), + } + } + } + } + + + let credentials = Credentials::new(SMTP_USER.to_string(), SMTP_PASS.to_string()); + let mailer = SmtpTransport::relay(&SMTP_HOST) + .unwrap() + .credentials(credentials) + .build(); + + match (email_validator.is_valid(&email), email_validator.is_valid(&SMTP_USER)) { + (false, _) => context.insert("error", "Invalid email address"), + (_, false) => context.insert("error", "Invalid SMTP username"), + (true, true) => { + let message = Message::builder() + .from(SMTP_USER.parse().unwrap()) + .to(email.parse().unwrap()) + .subject("Form Submission") + .body(format!( + "Thank you for your submission, {}!\n\nYour message:\n{}", + name, message_body + )) + .unwrap(); + + // send the email message + match mailer.send(&message) { + Ok(_) => println!("Email sended: {}", email), + Err(e) => eprintln!("Error sending email: {:?}", e), + } + } + } + + let body = Tera::one_off(include_str!("templates/form.tera"), &context, false).unwrap(); + HttpResponse::Ok().body(body) +} \ No newline at end of file diff --git a/src/input_validator/mod.rs b/src/input_validator/mod.rs new file mode 100644 index 0000000..d7bffdb --- /dev/null +++ b/src/input_validator/mod.rs @@ -0,0 +1,11 @@ +pub trait InputValidator { + fn is_valid(&self, input: &str) -> bool; +} + +pub struct NonEmptyInputValidator; + +impl InputValidator for NonEmptyInputValidator { + fn is_valid(&self, input: &str) -> bool { + !input.is_empty() + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0732e02..43e54b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,32 @@ +mod email_validator; +mod input_validator; mod error_handler; mod form_process; mod render_index; mod check_config; +mod form_config; mod init; mod time; mod log; // mod uploads; use dotenv::dotenv; use std::{env, str::FromStr, net::SocketAddr}; +use crate::log::info; use crate::init::{create_server, init_logger}; +use crate::time::current_time; #[actix_web::main] -async fn main() -> std::io::Result<()> { - init_logger(); - check_config::check_config(); // call check_config function +async fn main() -> Result<(), Box> { dotenv().ok(); // load the .env file + init_logger(); + let _ = check_config::check_config(); // call check_config function + let now = current_time(); + log_info!(&now, "SMTP_USER: {:?}", env::var("SMTP_USER")); + log_info!(&now, "SMTP_HOST: {:?}", env::var("SMTP_HOST")); + log_info!(&now, "SMTP_PASS: {:?}", env::var("SMTP_PASS")); + log_info!(&now, "SERVER_IP: {:?}", env::var("SERVER_IP")); + let server_ip = match env::var("SERVER_IP") { Ok(ip) => match ip.is_empty() { true => "NO_IP_CONFIGURED".to_string(),