From 9ad50fd4f0628c84b90026a44d5761d884dd5e92 Mon Sep 17 00:00:00 2001 From: Fernando Araoz Date: Tue, 31 Dec 2024 07:25:19 -0500 Subject: [PATCH] feat: create code for login --- backend/.env.example | 1 + backend/.gitignore | 1 + backend/Cargo.lock | 131 ++++++++++++++++++ backend/Cargo.toml | 3 + ..._init.sql => 20241231030841_init.down.sql} | 1 + backend/migrations/20241231030841_init.up.sql | 6 + backend/src/main.rs | 36 ++--- backend/src/modules/auth/mod.rs | 83 +++++++++++ backend/src/modules/mod.rs | 1 + 9 files changed, 237 insertions(+), 26 deletions(-) create mode 100644 backend/.env.example rename backend/migrations/{20241231030841_init.sql => 20241231030841_init.down.sql} (61%) create mode 100644 backend/migrations/20241231030841_init.up.sql create mode 100644 backend/src/modules/auth/mod.rs create mode 100644 backend/src/modules/mod.rs diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..1e795d6 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +DATABASE_URL=sqlite://./db.sqlite diff --git a/backend/.gitignore b/backend/.gitignore index 2d920c8..055b055 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ /target db.sqlite +.env diff --git a/backend/Cargo.lock b/backend/Cargo.lock index bec7f23..23cdc55 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -80,6 +80,67 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -147,6 +208,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" name = "backend" version = "0.1.0" dependencies = [ + "argon2", + "env_logger", + "log", "rocket", "rocket_db_pools", "serde", @@ -202,6 +266,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -254,6 +327,12 @@ dependencies = [ "inout", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "const-oid" version = "0.9.6" @@ -436,6 +515,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -801,6 +903,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.32" @@ -1001,6 +1109,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.14" @@ -1291,6 +1405,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -2552,6 +2677,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 1dca04d..986736b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +argon2 = "0.5.3" +env_logger = "0.11.6" +log = "0.4.22" rocket = { version = "0.5.1", features = ["json", "secrets"] } rocket_db_pools = { version = "0.2.0", features = ["sqlx_sqlite"] } serde = { version = "1.0.217", features = ["derive"] } diff --git a/backend/migrations/20241231030841_init.sql b/backend/migrations/20241231030841_init.down.sql similarity index 61% rename from backend/migrations/20241231030841_init.sql rename to backend/migrations/20241231030841_init.down.sql index 8ddc1d3..596ae02 100644 --- a/backend/migrations/20241231030841_init.sql +++ b/backend/migrations/20241231030841_init.down.sql @@ -1 +1,2 @@ -- Add migration script here +DROP TABLE monke; diff --git a/backend/migrations/20241231030841_init.up.sql b/backend/migrations/20241231030841_init.up.sql new file mode 100644 index 0000000..ff8914d --- /dev/null +++ b/backend/migrations/20241231030841_init.up.sql @@ -0,0 +1,6 @@ +-- Add migration script here +CREATE TABLE monke ( + monke_id INTEGER PRIMARY KEY AUTOINCREMENT, + monke_name TEXT NOT NULL, + monke_password TEXT NOT NULL +); diff --git a/backend/src/main.rs b/backend/src/main.rs index 0b46366..d00c535 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -4,44 +4,27 @@ use rocket::{ request::{FromRequest, Outcome}, Request, Rocket, }; -use rocket_db_pools::sqlx::{self, Row}; -use rocket_db_pools::{Connection, Database}; +use rocket_db_pools::sqlx::{self}; +use rocket_db_pools::Database; #[macro_use] extern crate rocket; +mod modules; + // so i was trying out claude.ai, and as a joke i talked to it as monke, // and it answered like monke. so i thought it would be funny to, // instead of plain, boring "User", just use "Monke" :D /// Stores info about a Monke struct Monke { - pub user_id: String, + pub monke_id: usize, + pub monke_name: String, } #[derive(Database)] #[database("main")] -struct MainDb(sqlx::SqlitePool); - -#[rocket::async_trait] -impl fairing::Fairing for MainDb { - fn info(&self) -> fairing::Info { - fairing::Info { - name: "Database Migrations", - kind: fairing::Kind::Liftoff, - } - } - - async fn on_liftoff(&self, _rocket: &Rocket) { - let db = self; - - // monke run migrations here - sqlx::migrate!() - .run(&**db) - .await - .expect("Failed to migrate db"); - } -} +pub struct MainDb(sqlx::SqlitePool); #[rocket::async_trait] impl<'r> FromRequest<'r> for Monke { @@ -61,7 +44,8 @@ impl<'r> FromRequest<'r> for Monke { Outcome::Error((Status::Unauthorized, ())) } else { Outcome::Success(Monke { - user_id: cookie.value().to_string(), + monke_id: cookie.value().parse().unwrap(), + monke_name: "Test".into(), }) } } @@ -79,5 +63,5 @@ fn index() -> &'static str { async fn rocket() -> _ { rocket::build() .attach(MainDb::init()) - .mount("/", routes![index]) + .mount("/api", routes![index, modules::auth::login]) } diff --git a/backend/src/modules/auth/mod.rs b/backend/src/modules/auth/mod.rs new file mode 100644 index 0000000..9422e7a --- /dev/null +++ b/backend/src/modules/auth/mod.rs @@ -0,0 +1,83 @@ +use argon2::{password_hash, Argon2, PasswordHash, PasswordVerifier}; +use rocket::{ + http::{CookieJar, Status}, + serde::json::Json, +}; +use rocket_db_pools::Connection; +use serde::Deserialize; + +use crate::MainDb; + +const COOKIE_INDEX: &'static str = "monke_secret"; + +#[derive(Deserialize)] +pub struct LoginCredentials { + username: String, + password: String, +} + +type ApiResponse = Json>; + +#[post("/login", data = "")] +pub async fn login( + credentials: Json, + mut db: Connection, + cookies: &CookieJar<'_>, +) -> (Status, ApiResponse) { + // check them db + let result = sqlx::query!( + "SELECT * FROM monke WHERE monke_name=?", + credentials.username + ) + .fetch_one(&mut **db) + .await; + + let result = match result { + Ok(r) => r, + Err(sqlx::Error::RowNotFound) => { + return (Status::BadRequest, Json(Err("Invalid credentials".into()))); + } + Err(e) => { + log::error!("error fetching db: {:?}", e); + + return ( + Status::InternalServerError, + Json(Err("Server error".into())), + ); + } + }; + + // check them credentials + let parsed_hash = match PasswordHash::new(&result.monke_password) { + Ok(r) => r, + Err(e) => { + log::error!("argon2 hash parse error: {:?}", e); + return ( + Status::InternalServerError, + Json(Err(String::from("Server error"))), + ); + } + }; + match Argon2::default().verify_password(credentials.password.as_bytes(), &parsed_hash) { + Ok(_) => {} + Err(password_hash::Error::Password) => { + return ( + Status::BadRequest, + Json(Err(String::from("Invalid credentials"))), + ) + } + Err(e) => { + log::error!("error verifying argon2 password: {:?}", e); + return ( + Status::InternalServerError, + Json(Err(String::from("Server error"))), + ); + } + }; + + // create cookie + cookies.add_private((COOKIE_INDEX, result.monke_id.to_string())); + + // send ok + return (Status::Ok, Json(Ok(()))); +} diff --git a/backend/src/modules/mod.rs b/backend/src/modules/mod.rs new file mode 100644 index 0000000..0e4a05d --- /dev/null +++ b/backend/src/modules/mod.rs @@ -0,0 +1 @@ +pub mod auth;