diff --git a/client-solid/src/Footer.tsx b/client-solid/src/Footer.tsx
new file mode 100644
index 0000000..376e454
--- /dev/null
+++ b/client-solid/src/Footer.tsx
@@ -0,0 +1,55 @@
+import { JSXElement } from "solid-js";
+
+export function Footer(): JSXElement {
+ return (
+
+ );
+}
diff --git a/client-solid/src/Posts.tsx b/client-solid/src/Posts.tsx
index 72b22aa..db1efad 100644
--- a/client-solid/src/Posts.tsx
+++ b/client-solid/src/Posts.tsx
@@ -15,9 +15,9 @@ export function Posts(): JSXElement {
return (
-
+ {/*
{(post): JSXElement => }
-
+ */}
);
}
diff --git a/client-solid/src/Root.tsx b/client-solid/src/Root.tsx
index 717d224..e07d657 100644
--- a/client-solid/src/Root.tsx
+++ b/client-solid/src/Root.tsx
@@ -4,6 +4,7 @@ import { GlobalStateProvider } from "./GlobalState";
import { LoginModal } from "./LoginModal";
import { Navbar } from "./Navbar";
import { Primary } from "./Primary";
+import { Footer } from "./Footer";
function Root(): JSXElement {
return (
@@ -13,9 +14,10 @@ function Root(): JSXElement {
-
>
@@ -25,7 +27,7 @@ function Root(): JSXElement {
function FancyBackground(): JSXElement {
return (
);
}
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);
+ }
+}