From dea4ac1fb3fb083b988aa3b2fcf9e641feedbfb3 Mon Sep 17 00:00:00 2001 From: Imbus Date: Wed, 15 Nov 2023 16:05:07 +0100 Subject: [PATCH] Registration endpoint fixing --- server/Cargo.lock | 1 + server/Cargo.toml | 1 + server/src/db.rs | 9 +++-- server/src/main.rs | 11 ++++++ server/src/routes/users.rs | 80 ++++++++++++++++++++++++-------------- server/src/state.rs | 29 ++++++++------ server/src/util/mod.rs | 3 ++ server/src/util/util.rs | 19 +++++++++ 8 files changed, 109 insertions(+), 44 deletions(-) create mode 100644 server/src/util/mod.rs create mode 100644 server/src/util/util.rs diff --git a/server/Cargo.lock b/server/Cargo.lock index 9917c48..bfc7374 100755 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1851,6 +1851,7 @@ dependencies = [ "jsonwebtoken", "lipsum", "log", + "rand", "serde", "serde_json", "sled", diff --git a/server/Cargo.toml b/server/Cargo.toml index ed54483..d3046e3 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -17,6 +17,7 @@ env_logger = "0.10.0" jsonwebtoken = "8.3.0" lipsum = "0.9.0" log = "0.4.20" +rand = "0.8.5" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" sled = { version = "0.34.7" } diff --git a/server/src/db.rs b/server/src/db.rs index 89c643e..3c52b93 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -6,7 +6,7 @@ use argon2::{ use log::{info, warn}; use sqlx::PgPool; -// Gets the latest posts from the database, ordered by created_at +/// Gets the latest posts from the database, ordered by created_at pub async fn db_get_latest_posts(pool: &PgPool, limit: i64, offset: i64) -> Vec { sqlx::query_as!( Post, @@ -19,7 +19,7 @@ pub async fn db_get_latest_posts(pool: &PgPool, limit: i64, offset: i64) -> Vec< .unwrap() } -// Gets the post with id from the database +/// Gets the post with id from the database pub async fn db_get_post(id: i64, pool: &PgPool) -> Option { sqlx::query_as!(Post, "SELECT * FROM posts WHERE id = $1", id) .fetch_one(pool) @@ -27,7 +27,7 @@ pub async fn db_get_post(id: i64, pool: &PgPool) -> Option { .ok() } -// Inserts a new post to the database +/// Inserts a new post to the database pub async fn db_new_post(userid: i64, content: &str, pool: &PgPool) -> Option { info!("User with id {} submitted a post", userid); @@ -57,6 +57,7 @@ pub async fn db_new_post(userid: i64, content: &str, pool: &PgPool) -> Option bool { let exists = sqlx::query!("SELECT username FROM users WHERE username = $1", username) .fetch_one(pool) @@ -67,6 +68,7 @@ pub async fn db_user_exists(username: String, pool: &PgPool) -> bool { exists.is_some() } +/// Checks if the user exists and if the password is correct pub async fn db_user_login(username: String, password: String, pool: &PgPool) -> Option { let username = username.clone(); let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username) @@ -95,6 +97,7 @@ pub async fn db_user_login(username: String, password: String, pool: &PgPool) -> } } +/// Creates a new user if the username is not already taken pub async fn db_new_user(username: String, password: String, pool: &PgPool) -> Option { // First check if the user already exists match db_user_exists(username.clone(), pool).await { diff --git a/server/src/main.rs b/server/src/main.rs index 3ed8abd..beba099 100755 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,10 +8,12 @@ mod db; mod jwt; mod routes; mod state; +mod util; use routes::{get_posts, login, new_post, post_by_id, register}; use state::CaptchaState; use state::ServerState; +use util::hex_string; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -20,6 +22,15 @@ async fn main() -> std::io::Result<()> { let data = ServerState::new().await; let capt_db = CaptchaState::new(); + #[cfg(debug_assertions)] + { + for _ in 0..10 { + let s = hex_string(10); + info!("Adding captcha key: {}", &s); + capt_db.capthca_db.lock().unwrap().insert(s); + } + } + info!("Spinning up server on http://localhost:8080"); HttpServer::new(move || { App::new() diff --git a/server/src/routes/users.rs b/server/src/routes/users.rs index 5b7d999..c5e7f02 100755 --- a/server/src/routes/users.rs +++ b/server/src/routes/users.rs @@ -5,8 +5,6 @@ use crate::ServerState; use actix_web::web::Data; use actix_web::{post, web::Json, HttpResponse, Responder, Result}; -use argon2::password_hash::rand_core::RngCore; -use argon2::password_hash::*; use biosvg::BiosvgBuilder; use log::*; use serde::{Deserialize, Serialize}; @@ -18,7 +16,7 @@ pub struct LoginData { } #[derive(Debug, Serialize, Deserialize)] -pub struct LoginResponse { +pub struct AuthResponse { username: String, token: String, } @@ -34,10 +32,31 @@ pub struct RegisterData { pub async fn register( data: Json, state: Data, + captcha_state: Data, ) -> Result { - db_new_user(data.username.clone(), data.password.clone(), &state.pool).await; - info!("User: {} registered", data.username); - Ok(HttpResponse::Ok().json("User registered")) + if !captcha_state + .capthca_db + .lock() + .unwrap() + .remove(&data.captcha) + { + info!("User failed to register, captcha was wrong"); + return Ok(HttpResponse::BadRequest().json("Error")); + } + + match db_new_user(data.username.clone(), data.password.clone(), &state.pool).await { + Some(user) => { + info!("User: {} registered", &user.username); + Ok(HttpResponse::Ok().json(AuthResponse { + username: user.username.clone(), + token: token_factory(&user.username).unwrap(), + })) + } + None => { + info!("User \"{}\" already exists", data.username); + return Ok(HttpResponse::BadRequest().json("Error")); + } + } } #[post("/login")] @@ -46,7 +65,7 @@ pub async fn login(data: Json, state: Data) -> Result { - return Ok(HttpResponse::Ok().json(LoginResponse { + return Ok(HttpResponse::Ok().json(AuthResponse { username: data.username.clone(), token: token_factory(&data.username).unwrap(), })); @@ -67,35 +86,38 @@ pub struct CaptchaResponse { /// Request a captcha from the captcha service #[post("/captcha")] pub async fn captcha_request(cstate: Data) -> Result { + unimplemented!("Captcha is currently disabled"); + return Ok(HttpResponse::InternalServerError().json("Error")); + // This might block the thread a bit too long - let (answer, svg) = get_captcha(); + // let (answer, svg) = get_captcha(); - let id = rand_core::OsRng.next_u32() as i32; + // let id = rand_core::OsRng.next_u32() as i32; - let cresponse = CaptchaResponse { - captcha_svg: svg.clone(), - captcha_id: id, - }; + // let cresponse = CaptchaResponse { + // captcha_svg: svg.clone(), + // captcha_id: id, + // }; // This is bad in about every way i can think of // It might just be better to hit the database every time, and let the database // handle rng and maybe set a trigger to delete old captchas - match cstate.capthca_db.lock() { - Ok(mut db) => { - if (db.len() as i32) > 100 { - // To prevent the database from growing too large - // Replace with a proper LRU cache or circular buffer - db.remove(&(id % 100)); // This is terrible - } - db.insert(id, answer.clone()); // We do not care about collisions - return Ok(HttpResponse::Ok().json(cresponse)); - } - Err(_) => { - // This shouldnt happen - error!("Failed to lock captcha database"); - return Ok(HttpResponse::InternalServerError().json("Error")); - } - } + // match cstate.capthca_db.lock() { + // Ok(mut db) => { + // if (db.len() as i32) > 100 { + // // To prevent the database from growing too large + // // Replace with a proper LRU cache or circular buffer + // db.remove(&(id % 100)); // This is terrible + // } + // db.insert(id, answer.clone()); // We do not care about collisions + // return Ok(HttpResponse::Ok().json(cresponse)); + // } + // Err(_) => { + // // This shouldnt happen + // error!("Failed to lock captcha database"); + // return Ok(HttpResponse::InternalServerError().json("Error")); + // } + // } } /// Returns a new captcha in the form of a tuple (answer, svg) diff --git a/server/src/state.rs b/server/src/state.rs index 8414449..aafca2b 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::sync::Arc; use std::sync::Mutex; @@ -9,13 +9,14 @@ use sqlx::PgPool; #[derive(Clone)] pub struct CaptchaState { - pub capthca_db: Arc>>, + // pub capthca_db: Arc>>, + pub capthca_db: Arc>>, } impl CaptchaState { pub fn new() -> Self { Self { - capthca_db: Arc::new(Mutex::new(BTreeMap::new())), + capthca_db: Arc::new(Mutex::new(BTreeSet::new())), } } } @@ -62,6 +63,7 @@ impl ServerState { #[cfg(debug_assertions)] async fn debug_setup(pool: &PgPool) -> Result<(), sqlx::Error> { use lipsum::lipsum; + use rand::prelude::*; use sqlx::query; use crate::db::db_new_user; @@ -69,26 +71,29 @@ async fn debug_setup(pool: &PgPool) -> Result<(), sqlx::Error> { db_new_user("user".to_string(), "pass".to_string(), pool).await; // Check if the demo post already exists - let posted = query!("SELECT * FROM posts WHERE id = 1",) + let no_posts = query!("SELECT * FROM posts WHERE id = 1",) .fetch_one(pool) .await - .ok(); + .ok() + .is_none(); // If the demo user already has a post, don't insert another one - if !posted.is_some() { - // This requires that the user with id 1 exists in the user table - query!("INSERT INTO posts (user_id, content) VALUES (1, 'Hello world! The demo username is user and the password is pass.')",) - .execute(pool) - .await?; + if no_posts { + let mut rng = rand::thread_rng(); - for _ in 0..10 { + // This requires that the user with id 1 exists in the user table + for _ in 0..100 { query!( "INSERT INTO posts (user_id, content) VALUES (1, $1)", - lipsum(50) + lipsum(rng.gen_range(10..100)) ) .execute(pool) .await?; } + + query!("INSERT INTO posts (user_id, content) VALUES (1, 'Hello world! The demo username is user and the password is pass.')",) + .execute(pool) + .await?; } Ok(()) diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs new file mode 100644 index 0000000..c8183a1 --- /dev/null +++ b/server/src/util/mod.rs @@ -0,0 +1,3 @@ +mod util; + +pub use util::*; diff --git a/server/src/util/util.rs b/server/src/util/util.rs new file mode 100644 index 0000000..b50a703 --- /dev/null +++ b/server/src/util/util.rs @@ -0,0 +1,19 @@ +use rand::{Rng, RngCore}; + +// This will do for now +pub fn hex_string(length: usize) -> String { + let mut rng = rand::thread_rng(); + let mut bytes = vec![0u8; length]; + rng.fill(&mut bytes[..]); + bytes.iter().map(|b| format!("{:X}", b)).collect::()[..length].to_string() +} + +mod tests { + use super::*; + + #[test] + fn test_random_hex_string() { + let s = hex_string(16); + assert_eq!(s.len(), 16); + } +}