Compare commits

..

2 Commits

Author SHA1 Message Date
9ad50fd4f0 feat: create code for login 2024-12-31 07:25:19 -05:00
a3d8e07c26 feat: barebones backend init 2024-12-30 22:13:29 -05:00
10 changed files with 1621 additions and 15 deletions

1
backend/.env.example Normal file
View File

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

3
backend/.gitignore vendored
View File

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

1469
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,5 +4,12 @@ version = "0.1.0"
edition = "2021"
[dependencies]
rocket = "0.5.1"
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"] }
serde_json = "1.0.134"
sqlx = { version = "0.7", features = ["macros", "migrate"] }

3
backend/Rocket.toml Normal file
View File

@ -0,0 +1,3 @@
[default.databases.main]
url = "./db.sqlite"

View File

@ -0,0 +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

@ -1,12 +1,67 @@
use rocket::{
fairing,
http::Status,
request::{FromRequest, Outcome},
Request, Rocket,
};
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 monke_id: usize,
pub monke_name: String,
}
#[derive(Database)]
#[database("main")]
pub struct MainDb(sqlx::SqlitePool);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Monke {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
// monke get cookies from request
let cookies = request.cookies();
// monke look for auth cookie
match cookies.get_private("monke_session") {
Some(cookie) => {
let auth_db = request.rocket().state::<MainDb>().unwrap();
// in real app, monke validate token here
if cookie.value().is_empty() {
Outcome::Error((Status::Unauthorized, ()))
} else {
Outcome::Success(Monke {
monke_id: cookie.value().parse().unwrap(),
monke_name: "Test".into(),
})
}
}
None => Outcome::Error((Status::Unauthorized, ())),
}
}
}
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
async fn rocket() -> _ {
rocket::build()
.attach(MainDb::init())
.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;