feat: create code for login

This commit is contained in:
Fernando Araoz 2024-12-31 07:25:19 -05:00
parent a3d8e07c26
commit 9ad50fd4f0
9 changed files with 237 additions and 26 deletions

1
backend/.env.example Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=sqlite://./db.sqlite

1
backend/.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
db.sqlite
.env

131
backend/Cargo.lock generated
View File

@ -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"

View File

@ -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"] }

View File

@ -1 +1,2 @@
-- Add migration script here
DROP TABLE monke;

View File

@ -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
);

View File

@ -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<rocket::Orbit>) {
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])
}

View File

@ -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<Result<(), String>>;
#[post("/login", data = "<credentials>")]
pub async fn login(
credentials: Json<LoginCredentials>,
mut db: Connection<MainDb>,
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(())));
}

View File

@ -0,0 +1 @@
pub mod auth;