Working but untested captcha api

This commit is contained in:
Imbus 2023-10-21 05:11:42 +02:00
parent 4c398a40f6
commit fa9b2b6fc1
8 changed files with 107 additions and 12 deletions

View file

@ -4,14 +4,15 @@ runtime := "podman"
dev: start-debug
@echo "Cd into client and run 'npm run dev' to start the client in dev mode."
# Builds the client with npm (result in client/dist
[private]
npm-install directory:
cd {{directory}} && npm install
# Builds the client with npm (result in client/dist)
[private]
npm-build: (npm-install "client-solid")
cd client && npm run build
npm-build directory: (npm-install directory)
cd {{directory}} && npm run build
@echo "Built client at {{directory}}/dist"
# Builds a debug container
[private]

13
server/Cargo.lock generated
View file

@ -391,6 +391,18 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "biosvg"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51c50785b88aca88dc4417a3aede395dac83b5031286f02523ddf4839c59e7f8"
dependencies = [
"once_cell",
"rand",
"regex",
"thiserror",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -1780,6 +1792,7 @@ dependencies = [
"actix-files",
"actix-web",
"argon2",
"biosvg",
"chrono",
"clap",
"dotenvy",

View file

@ -9,6 +9,7 @@ edition = "2021"
actix-files = "0.6.2"
actix-web = "4.4.0"
argon2 = { version = "0.5.2", features = ["zeroize"] }
biosvg = "0.1.3"
chrono = { version = "0.4.31", features = ["serde"] }
clap = { version = "4.4.5", features = ["derive"] }
dotenvy = "0.15.7"

View file

@ -17,25 +17,27 @@ pub async fn db_get_latest_posts(pool: &SqlitePool, limit: i64, offset: i64) ->
// Inserts a new post to the database
pub async fn db_new_post(post: NewPost, pool: &SqlitePool) -> Option<Post> {
let q2 = sqlx::query!(
let insert_query = sqlx::query!(
"INSERT INTO posts (user_id, content) VALUES (1, ?)",
post.content
)
.execute(pool)
.await;
if q2.is_err() {
let s = q2.err().unwrap();
if insert_query.is_err() {
let s = insert_query.err().unwrap();
warn!("Error inserting post into database: {}", s);
return None;
}
let q = sqlx::query_as!(
// Dips into the database to get the post we just inserted
let post = sqlx::query_as!(
Post,
"SELECT * FROM posts WHERE id = (SELECT MAX(id) FROM posts)"
)
.fetch_one(pool)
.await
.ok()?;
Some(q)
Some(post)
}

View file

@ -11,6 +11,7 @@ mod routes;
mod state;
use routes::{get_posts, login, new_post, register};
use state::CaptchaState;
use state::ServerState;
#[actix_web::main]
@ -18,6 +19,7 @@ async fn main() -> std::io::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
let data = ServerState::new().await;
let capt_db = CaptchaState::new();
info!("Spinning up server on http://localhost:8080");
HttpServer::new(move || {
@ -31,7 +33,8 @@ async fn main() -> std::io::Result<()> {
.service(new_post)
.service(login)
.service(register)
.app_data(Data::new(data.clone())),
.app_data(Data::new(data.clone()))
.app_data(Data::new(capt_db.clone())),
)
.service(Files::new("/", "./public").index_file("index.html"))
})

View file

@ -41,7 +41,7 @@ pub struct User {
// Note that these are optional
/// Query parameters for the /posts endpoint
#[derive(Debug, Serialize, Deserialize)]
struct QueryParams {
pub struct QueryParams {
limit: Option<i64>,
offset: Option<i64>,
}

View file

@ -1,14 +1,16 @@
use crate::jwt::token_factory;
use crate::state::CaptchaState;
use crate::ServerState;
use actix_web::web::Data;
use actix_web::{post, web::Json, HttpResponse, Responder, Result};
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::rand_core::{OsRng, RngCore};
use argon2::password_hash::SaltString;
use argon2::password_hash::*;
use argon2::Argon2;
use argon2::PasswordHasher;
use argon2::PasswordVerifier;
use biosvg::BiosvgBuilder;
use log::*;
use serde::{Deserialize, Serialize};
@ -101,7 +103,7 @@ pub async fn login(data: Json<LoginData>, state: Data<ServerState>) -> Result<im
println!("{:?}", token);
return Ok(HttpResponse::Ok().json(LoginResponse {
username: data.username.clone(),
token: token,
token,
}));
}
Err(_) => {
@ -110,3 +112,59 @@ pub async fn login(data: Json<LoginData>, state: Data<ServerState>) -> Result<im
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CaptchaResponse {
captcha_svg: String,
captcha_id: i32,
}
/// Request a captcha from the captcha service
#[post("/captcha")]
pub async fn captcha_request(cstate: Data<CaptchaState>) -> Result<impl Responder> {
// This might block the thread a bit too long
let (answer, svg) = get_captcha();
let id = rand_core::OsRng.next_u32() as i32;
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"));
}
}
}
/// Returns a new captcha in the form of a tuple (answer, svg)
fn get_captcha() -> (String, String) {
BiosvgBuilder::new()
.length(4)
.difficulty(6)
.colors(vec![
"#0078D6".to_string(),
"#aa3333".to_string(),
"#f08012".to_string(),
"#33aa00".to_string(),
"#aa33aa".to_string(),
])
.build()
.unwrap()
}

View file

@ -1,8 +1,25 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::Mutex;
use sqlx::Pool;
use sqlx::Sqlite;
use sqlx::SqlitePool;
use sqlx::{self, sqlite};
#[derive(Clone)]
pub struct CaptchaState {
pub capthca_db: Arc<Mutex<BTreeMap<i32, String>>>,
}
impl CaptchaState {
pub fn new() -> Self {
Self {
capthca_db: Arc::new(Mutex::new(BTreeMap::new())),
}
}
}
#[derive(Clone)]
pub struct ServerState {
pub pool: Pool<Sqlite>,