Working but untested captcha api
This commit is contained in:
parent
4c398a40f6
commit
fa9b2b6fc1
8 changed files with 107 additions and 12 deletions
7
justfile
7
justfile
|
@ -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
13
server/Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
})
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Reference in a new issue