diff --git a/Cargo.lock b/Cargo.lock index c147da5..945c1a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -150,6 +185,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -272,6 +313,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -284,7 +335,13 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "aes-gcm", + "base64 0.22.1", + "hkdf", "percent-encoding", + "rand", + "sha2", + "subtle", "time", "version_check", ] @@ -369,6 +426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -378,6 +436,15 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4" +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "der" version = "0.7.9" @@ -728,6 +795,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1155,6 +1232,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -1502,6 +1588,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "orion" version = "0.17.7" @@ -1719,6 +1811,18 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2055,7 +2159,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] @@ -2381,7 +2485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", - "base64", + "base64 0.21.7", "bitflags 2.6.0", "byteorder", "bytes", @@ -2425,7 +2529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", - "base64", + "base64 0.21.7", "bitflags 2.6.0", "byteorder", "chrono", @@ -2985,6 +3089,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index b9cfe26..7bdbf97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -rocket = "0.5.1" +rocket = { version = "0.5.1", features = ["secrets"] } rocket_dyn_templates = { version = "0.2.0", features = ["tera"] } rocket_db_pools = { version = "0.2.0", features = ["sqlx_postgres"] } sqlx = { version = "0.7", default-features = false, features = [ diff --git a/src/auth/pw.rs b/src/auth/pw.rs index 8e4e298..4d1b42e 100644 --- a/src/auth/pw.rs +++ b/src/auth/pw.rs @@ -7,7 +7,7 @@ const ITERATIONS: u32 = 3; const MEMORY: u32 = 1 << 16; // TODO: make those configurable through the .toml file -pub fn hash(input: &str) -> Result { +pub fn hash<'a>(input: &'a str) -> Result { let password = pwhash::Password::from_slice(input.as_bytes())?; Ok(pwhash::hash_password(&password, ITERATIONS, MEMORY)? diff --git a/src/db/mod.rs b/src/db/mod.rs index 9dfb52a..3f8d259 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,3 +1,4 @@ pub mod doll; pub mod migrate; pub mod schema; +pub mod user; diff --git a/src/db/schema.rs b/src/db/schema.rs index dfd027d..7520053 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -67,3 +67,15 @@ pub struct CreateDollProfile<'a> { pub chassis_id: Option<&'a str>, pub chassis_color: Option<&'a str>, } + +#[derive(Debug)] +pub struct User { + pub id: Uuid, + pub created_at: chrono::DateTime, + pub updated_at: Option>, + + pub username: String, + pub password: String, + + pub enabled: bool, +} diff --git a/src/db/user.rs b/src/db/user.rs new file mode 100644 index 0000000..431bf1f --- /dev/null +++ b/src/db/user.rs @@ -0,0 +1,25 @@ +use uuid::Uuid; + +use crate::db::schema::User; + +use super::schema::DollTagsDb; + +pub async fn get(mut db: DollTagsDb, username: &str) -> sqlx::Result> { + sqlx::query_as!(User, "select * from users where username = $1", username) + .fetch_optional(&mut **db) + .await +} + +pub async fn create( + mut db: DollTagsDb, + username: &str, + hashed_password: &str, +) -> sqlx::Result { + sqlx::query_scalar!( + "insert into users (username, password) values ($1, $2) returning id", + username, + hashed_password + ) + .fetch_one(&mut **db) + .await +} diff --git a/src/main.rs b/src/main.rs index 9b29da2..6ce9675 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use rocket::fs::{relative, FileServer}; use rocket_db_pools::Database; use rocket_dyn_templates::tera::try_get_value; use rocket_dyn_templates::Template; +use routes::form::accounts; use routes::{error_handlers, form, public}; use serde_json::{to_value, Value}; @@ -39,6 +40,10 @@ fn rocket() -> _ { public::show_profile, form::register_tag::show_register, form::register_tag::handle_register, + accounts::show_register, + accounts::handle_register, + accounts::show_login, + accounts::handle_login, ], ) } diff --git a/src/routes/error_handlers.rs b/src/routes/error_handlers.rs index 87883d7..4112db8 100644 --- a/src/routes/error_handlers.rs +++ b/src/routes/error_handlers.rs @@ -28,19 +28,12 @@ impl From for PageResponse { #[response(status = 500)] pub struct Fail(Template); -impl From for Fail { - fn from(value: anyhow::Error) -> Self { +impl From for Fail { + fn from(value: T) -> Self { error!("Internal error: {:?}", value); Fail(Template::render("error/internal", context! {})) } } -impl From for Fail { - fn from(value: sqlx::Error) -> Self { - error!("DB error: {:?}", value); - Fail(Template::render("error/internal", context! {})) - } -} - pub type RawResult = Result; pub type PageResult = RawResult; diff --git a/src/routes/form/accounts.rs b/src/routes/form/accounts.rs new file mode 100644 index 0000000..ab58a55 --- /dev/null +++ b/src/routes/form/accounts.rs @@ -0,0 +1,71 @@ +use rocket::{ + form::{Contextual, Form}, + http::CookieJar, + response::Redirect, + tokio::task, +}; +use rocket_dyn_templates::{context, Template}; + +use crate::{ + auth::pw, + db::{schema::DollTagsDb, user}, + routes::error_handlers::{PageResponse, PageResult}, +}; + +#[derive(Debug, FromForm)] +pub struct AuthForm<'a> { + #[field(validate=len(2..=256))] + pub username: &'a str, + #[field(validate=len(8..))] + pub password: &'a str, +} + +#[get("/login")] +pub fn show_login() -> Template { + todo!("meow") +} + +#[post("/login", data = "
")] +pub async fn handle_login( + db: DollTagsDb, + form: Form>>, + cookies: &CookieJar<'_>, +) -> PageResult { + let miss = || PageResponse::Page(Template::render("login", context! {failure: true})); + + let values = match &form.value { + None => return Ok(miss()), + Some(v) => v, + }; + + let user_in_db = user::get(db, &values.username).await?; + let user = match user_in_db { + None => return Ok(miss()), + Some(v) => v, + }; + + let password = String::from(values.password); + let right_password = + task::spawn_blocking(move || pw::verify(&password, &user.password)).await??; + + if right_password && user.enabled { + cookies.add_private(("user_id", user.id.to_string())); + Ok(Redirect::to("/").into()) + } else { + Ok(miss()) + } +} + +#[get("/register")] +pub fn show_register() -> Template { + todo!("meow") +} + +#[post("/register", data = "<_form>")] +pub async fn handle_register( + _db: DollTagsDb, + _form: Form>, + _cookies: &CookieJar<'_>, +) -> PageResult { + todo!("meow") +} diff --git a/src/routes/form/mod.rs b/src/routes/form/mod.rs index 7b755fb..0d0b9fa 100644 --- a/src/routes/form/mod.rs +++ b/src/routes/form/mod.rs @@ -1 +1,2 @@ +pub mod accounts; pub mod register_tag;