Compare commits
2 Commits
cb49ba0e8d
...
9ad50fd4f0
Author | SHA1 | Date | |
---|---|---|---|
9ad50fd4f0 | |||
a3d8e07c26 |
1
backend/.env.example
Normal file
1
backend/.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
DATABASE_URL=sqlite://./db.sqlite
|
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@ -1 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
|
db.sqlite
|
||||||
|
.env
|
||||||
|
|
||||||
|
1469
backend/Cargo.lock
generated
1469
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,5 +4,12 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[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
3
backend/Rocket.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[default.databases.main]
|
||||||
|
url = "./db.sqlite"
|
||||||
|
|
2
backend/migrations/20241231030841_init.down.sql
Normal file
2
backend/migrations/20241231030841_init.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
DROP TABLE monke;
|
6
backend/migrations/20241231030841_init.up.sql
Normal file
6
backend/migrations/20241231030841_init.up.sql
Normal 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
|
||||||
|
);
|
@ -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]
|
#[macro_use]
|
||||||
extern crate rocket;
|
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("/")]
|
#[get("/")]
|
||||||
fn index() -> &'static str {
|
fn index() -> &'static str {
|
||||||
"Hello, world!"
|
"Hello, world!"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
async fn rocket() -> _ {
|
||||||
rocket::build().mount("/", routes![index])
|
rocket::build()
|
||||||
|
.attach(MainDb::init())
|
||||||
|
.mount("/api", routes![index, modules::auth::login])
|
||||||
}
|
}
|
||||||
|
83
backend/src/modules/auth/mod.rs
Normal file
83
backend/src/modules/auth/mod.rs
Normal 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(())));
|
||||||
|
}
|
1
backend/src/modules/mod.rs
Normal file
1
backend/src/modules/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod auth;
|
Loading…
Reference in New Issue
Block a user