From 0bfdc305b19891fb2b84f96314e4b03d8b2b1d75 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Wed, 24 May 2023 16:19:46 +0200 Subject: [PATCH] Basic create user account --- README.md | 2 +- geneit_backend/Cargo.lock | 32 ++++++++++++++ geneit_backend/Cargo.toml | 4 +- geneit_backend/src/app_config.rs | 26 ++++++++--- geneit_backend/src/constants.rs | 5 +++ geneit_backend/src/controllers.rs | 3 ++ .../src/controllers/auth_controller.rs | 43 +++++++++++++++++++ geneit_backend/src/db_connection.rs | 25 +++++++++++ geneit_backend/src/lib.rs | 6 +++ geneit_backend/src/main.rs | 13 +++++- geneit_backend/src/models.rs | 34 +++++++++++++++ geneit_backend/src/services/mod.rs | 3 ++ geneit_backend/src/services/users_service.rs | 22 ++++++++++ geneit_backend/src/utils.rs | 3 ++ geneit_backend/src/utils/time_utils.rs | 11 +++++ 15 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 geneit_backend/src/controllers/auth_controller.rs create mode 100644 geneit_backend/src/db_connection.rs create mode 100644 geneit_backend/src/models.rs create mode 100644 geneit_backend/src/services/mod.rs create mode 100644 geneit_backend/src/services/users_service.rs create mode 100644 geneit_backend/src/utils.rs create mode 100644 geneit_backend/src/utils/time_utils.rs diff --git a/README.md b/README.md index e997646..8ff7e36 100644 --- a/README.md +++ b/README.md @@ -33,5 +33,5 @@ diesel migation run > Note: You can access the database directly using this command: > > ```bash -> PGPASSWORD=pass psql -h localhost -p 5432 -U user -d bioclimsol +> PGPASSWORD=pass psql -h localhost -p 5432 -U user -d geneit > ``` \ No newline at end of file diff --git a/geneit_backend/Cargo.lock b/geneit_backend/Cargo.lock index d0de695..f04cac2 100644 --- a/geneit_backend/Cargo.lock +++ b/geneit_backend/Cargo.lock @@ -68,6 +68,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "actix-remote-ip" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7629b357d4705cf3f1e31f989f48ecd56027112f7d52dcf06dd96ee197065f8e" +dependencies = [ + "actix-web", + "futures-util", + "log", +] + [[package]] name = "actix-router" version = "0.5.1" @@ -285,6 +296,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "autocfg" version = "1.1.0" @@ -584,6 +601,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -603,16 +631,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] name = "geneit_backend" version = "0.1.0" dependencies = [ + "actix-remote-ip", "actix-web", + "anyhow", "clap", "diesel", "env_logger", diff --git a/geneit_backend/Cargo.toml b/geneit_backend/Cargo.toml index 2ea4cfc..3ca33cd 100644 --- a/geneit_backend/Cargo.toml +++ b/geneit_backend/Cargo.toml @@ -10,6 +10,8 @@ log = "0.4.17" env_logger = "0.10.0" clap = { version = "4.3.0", features = ["derive", "env"] } lazy_static = "1.4.0" +anyhow = "1.0.71" actix-web = "4.3.1" diesel = { version = "2.0.4", features = ["postgres"] } -serde = { version = "1.0.163", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0.163", features = ["derive"] } +actix-remote-ip = "0.1.0" \ No newline at end of file diff --git a/geneit_backend/src/app_config.rs b/geneit_backend/src/app_config.rs index 9c1ee1a..e6253bc 100644 --- a/geneit_backend/src/app_config.rs +++ b/geneit_backend/src/app_config.rs @@ -16,17 +16,25 @@ pub struct AppConfig { #[clap(short, long, env)] pub proxy_ip: Option, - /// PostgreSQL connexion chain - #[clap(long, env, default_value = "postgres://localhost/geneit")] - db_chain: String, + /// PostgreSQL database host + #[clap(long, env, default_value = "localhost")] + db_host: String, + + /// PostgreSQL database port + #[clap(long, env, default_value_t = 5432)] + db_port: u16, /// PostgreSQL username #[clap(long, env, default_value = "user")] db_username: String, /// PostgreSQL password - #[clap(long, env, default_value = "user")] + #[clap(long, env, default_value = "pass")] db_password: String, + + /// PostgreSQL database name + #[clap(long, env, default_value = "geneit")] + db_name: String, } lazy_static::lazy_static! { @@ -40,4 +48,12 @@ impl AppConfig { pub fn get() -> &'static AppConfig { &ARGS } -} \ No newline at end of file + + /// Get full db connection chain + pub fn db_connection_chain(&self) -> String { + format!( + "postgres://{}:{}@{}:{}/{}", + self.db_username, self.db_password, self.db_host, self.db_port, self.db_name + ) + } +} diff --git a/geneit_backend/src/constants.rs b/geneit_backend/src/constants.rs index 8940bed..eb42d62 100644 --- a/geneit_backend/src/constants.rs +++ b/geneit_backend/src/constants.rs @@ -8,6 +8,11 @@ impl SizeConstraint { pub fn new(min: usize, max: usize) -> Self { Self { min, max } } + + pub fn validate(&self, val: &str) -> bool { + let len = val.trim().len(); + len >= self.min && len <= self.max + } } #[derive(Debug, Clone, serde::Serialize)] diff --git a/geneit_backend/src/controllers.rs b/geneit_backend/src/controllers.rs index 5d03849..4b69ed9 100644 --- a/geneit_backend/src/controllers.rs +++ b/geneit_backend/src/controllers.rs @@ -1 +1,4 @@ +//! # API controller + +pub mod auth_controller; pub mod config_controller; diff --git a/geneit_backend/src/controllers/auth_controller.rs b/geneit_backend/src/controllers/auth_controller.rs new file mode 100644 index 0000000..51f4d7a --- /dev/null +++ b/geneit_backend/src/controllers/auth_controller.rs @@ -0,0 +1,43 @@ +use crate::constants::StaticConstraints; +use crate::services::users_service; +use actix_remote_ip::RemoteIP; +use actix_web::error::ErrorInternalServerError; +use actix_web::{web, HttpResponse}; + +#[derive(serde::Deserialize)] +pub struct CreateAccountBody { + name: String, + email: String, +} + +/// Create a new account +pub async fn create_account( + _remote_ip: RemoteIP, + req: web::Json, +) -> actix_web::Result { + // TODO : rate limiting + + // TODO : check if email is valid + + // Check parameters + let constraints = StaticConstraints::default(); + if !constraints.user_name_len.validate(&req.name) || !constraints.mail_len.validate(&req.email) + { + return Ok(HttpResponse::BadRequest().json("Size constraints were not respected!")); + } + + // TODO : check the mail address + + // Create the account + let user_id = users_service::create_account(&req.name, &req.email) + .await + .map_err(|e| { + log::error!("Failed to create user! {e}"); + ErrorInternalServerError(e) + })?; + + // TODO : trigger reset password (send mail) + + // Account successfully created + Ok(HttpResponse::Created().finish()) +} diff --git a/geneit_backend/src/db_connection.rs b/geneit_backend/src/db_connection.rs new file mode 100644 index 0000000..7992e41 --- /dev/null +++ b/geneit_backend/src/db_connection.rs @@ -0,0 +1,25 @@ +//! # Database connection management + +use crate::app_config::AppConfig; +use diesel::{Connection, PgConnection}; +use std::cell::RefCell; +thread_local! { + static POSTGRES_CONNECTION: RefCell> = RefCell::new(None); +} + +/// Execute a request on the database +pub fn execute(cb: E) -> anyhow::Result +where + E: FnOnce(&mut PgConnection) -> anyhow::Result, +{ + // Establish connection if required + if POSTGRES_CONNECTION.with(|i| i.borrow().is_none()) { + let database_url = AppConfig::get().db_connection_chain(); + let conn = PgConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)); + + POSTGRES_CONNECTION.with(|i| *i.borrow_mut() = Some(conn)) + } + + POSTGRES_CONNECTION.with(|i| cb(i.borrow_mut().as_mut().unwrap())) +} diff --git a/geneit_backend/src/lib.rs b/geneit_backend/src/lib.rs index 7349817..2a0f26d 100644 --- a/geneit_backend/src/lib.rs +++ b/geneit_backend/src/lib.rs @@ -1,4 +1,10 @@ pub mod app_config; pub mod constants; pub mod controllers; +pub mod services; +pub mod utils; + +// Diesel specific +pub mod db_connection; +pub mod models; pub mod schema; diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index 7203acc..a408992 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -1,6 +1,8 @@ +use actix_remote_ip::RemoteIPConfig; +use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; use geneit_backend::app_config::AppConfig; -use geneit_backend::controllers::config_controller; +use geneit_backend::controllers::{auth_controller, config_controller}; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -10,12 +12,21 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() + .wrap(Logger::default()) + .app_data(web::Data::new(RemoteIPConfig { + proxy: AppConfig::get().proxy_ip.clone(), + })) // Config controller .route("/", web::get().to(config_controller::home)) .route( "/config/static", web::get().to(config_controller::static_config), ) + // Auth controller + .route( + "/auth/create_account", + web::post().to(auth_controller::create_account), + ) }) .bind(AppConfig::get().listen_address.as_str())? .run() diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs new file mode 100644 index 0000000..9f67302 --- /dev/null +++ b/geneit_backend/src/models.rs @@ -0,0 +1,34 @@ +use crate::schema::users; +use diesel::prelude::*; + +/// User ID holder +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct UserID(i32); + +#[derive(Queryable, Debug)] +pub struct User { + id: i32, + name: String, + email: String, + password: Option, + reset_password_token: Option, + time_create: i64, + time_gen_reset_token: i64, + time_activate: i64, + active: bool, + admin: bool, +} + +impl User { + pub fn id(&self) -> UserID { + UserID(self.id) + } +} + +#[derive(Insertable)] +#[diesel(table_name = users)] +pub struct NewUser<'a> { + pub name: &'a str, + pub email: &'a str, + pub time_create: i64, +} diff --git a/geneit_backend/src/services/mod.rs b/geneit_backend/src/services/mod.rs new file mode 100644 index 0000000..2121797 --- /dev/null +++ b/geneit_backend/src/services/mod.rs @@ -0,0 +1,3 @@ +//! # Backend services + +pub mod users_service; diff --git a/geneit_backend/src/services/users_service.rs b/geneit_backend/src/services/users_service.rs new file mode 100644 index 0000000..0e337c7 --- /dev/null +++ b/geneit_backend/src/services/users_service.rs @@ -0,0 +1,22 @@ +//! # Users service + +use crate::db_connection; +use crate::models::{NewUser, User}; +use crate::schema::users; +use crate::utils::time_utils::time; +use diesel::prelude::*; + +/// Create a new account +pub async fn create_account(name: &str, email: &str) -> anyhow::Result { + db_connection::execute(|conn| { + let res = diesel::insert_into(users::table) + .values(&NewUser { + name: name.trim(), + email: email.trim(), + time_create: time() as i64, + }) + .get_result(conn)?; + + Ok(res) + }) +} diff --git a/geneit_backend/src/utils.rs b/geneit_backend/src/utils.rs new file mode 100644 index 0000000..0e2ae9a --- /dev/null +++ b/geneit_backend/src/utils.rs @@ -0,0 +1,3 @@ +//! # App utilities + +pub mod time_utils; diff --git a/geneit_backend/src/utils/time_utils.rs b/geneit_backend/src/utils/time_utils.rs new file mode 100644 index 0000000..7c035af --- /dev/null +++ b/geneit_backend/src/utils/time_utils.rs @@ -0,0 +1,11 @@ +//! # Time utilities + +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Get the current time since epoch +pub fn time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +}