Restructure code, add comments, add logging
This commit is contained in:
parent
543be99f67
commit
a100bcb552
|
@ -25,6 +25,15 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
|
@ -79,9 +88,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281"
|
||||
checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
@ -193,9 +202,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.71"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -374,6 +383,19 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible_collections"
|
||||
version = "0.4.3"
|
||||
|
@ -666,6 +688,12 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
|
||||
|
||||
[[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.14"
|
||||
|
@ -1042,6 +1070,7 @@ dependencies = [
|
|||
"argon2",
|
||||
"bincode",
|
||||
"captcha",
|
||||
"env_logger",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"rand_core 0.6.3",
|
||||
|
@ -1265,6 +1294,8 @@ version = "1.5.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
|
@ -1460,9 +1491,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8"
|
||||
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1644,6 +1675,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
|
@ -2014,6 +2054,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
[package]
|
||||
name = "pharmacy"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-rc.1", features = ["json"] }
|
||||
log = "0.4.14"
|
||||
serde = "1.0.130"
|
||||
bincode = "1.3.3"
|
||||
sled = "0.34.7"
|
||||
|
@ -16,3 +15,5 @@ rand_core = { version = "0.6", features = ["std"] }
|
|||
uuid = { version = "0.8", features = ["serde", "v5"] }
|
||||
rand = "0.8.4"
|
||||
captcha = "0.0.8"
|
||||
log = "0.4.0"
|
||||
env_logger = "0.9.0"
|
||||
|
|
|
@ -7,10 +7,13 @@ A simple server for dealing with products & orders for the Catgirl Pharmacy.
|
|||
- [x] Fetch info with JS
|
||||
- [x] Fix DB lock error
|
||||
- [x] Keep track of orders
|
||||
- [ ] Add proper error handling
|
||||
- [x] Add page for managing orders
|
||||
- [x] Return Json data
|
||||
- [ ] Add proper error handling
|
||||
- [ ] Cleanup code
|
||||
- [ ] Add comments
|
||||
- [x] Add comments
|
||||
- [/] Add logging (need to log some API components)
|
||||
- [ ] Format properly
|
||||
|
||||
## API:
|
||||
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
use rocket::serde::json::Json;
|
||||
use rocket::{form::Form, State};
|
||||
|
||||
use rand::Rng;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::*;
|
||||
use crate::structures::*;
|
||||
|
||||
use argon2::{
|
||||
password_hash::{PasswordHasher, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
|
||||
use std::env;
|
||||
|
||||
// Endpoint to add a new product
|
||||
#[post("/api/new", data = "<request>")]
|
||||
pub fn new_product(
|
||||
db: &State<sled::Db>,
|
||||
argon2: &State<Argon2>,
|
||||
salt: &State<SaltString>,
|
||||
request: Form<ProductRequest>,
|
||||
) -> Json<Status> {
|
||||
/* Need to rework the password mechanism so it's not stored in plaintext serverside */
|
||||
|
||||
let password = request.password.as_bytes(); // Turn the provided passord into a bytes array
|
||||
let hashed_password = argon2
|
||||
.hash_password(password, &salt.as_ref())
|
||||
.unwrap()
|
||||
.to_string(); // Hash the password
|
||||
let mut admin_password = env::var("ADMIN_PASSWD").unwrap(); // Get the set admin password from environment variables
|
||||
admin_password = argon2
|
||||
.hash_password(admin_password.as_bytes(), &salt.as_ref())
|
||||
.unwrap()
|
||||
.to_string(); // Hash the admin password
|
||||
|
||||
if hashed_password == admin_password {
|
||||
// Check if the password is correct
|
||||
let new_product: Product = Product {
|
||||
// Create a new Product object from the supplied info
|
||||
short_name: request.short_name.clone(),
|
||||
full_name: request.full_name.clone(),
|
||||
price_usd: request.price_usd,
|
||||
stock: request.stock,
|
||||
};
|
||||
register_product(db, &new_product.short_name, &new_product); // Register the new product in the database
|
||||
return Json(Status {
|
||||
// Return a JSON status
|
||||
status: "success".to_string(),
|
||||
reason: "registered new product".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
return Json(Status {
|
||||
// Return a JSON status fail
|
||||
status: "fail".to_string(),
|
||||
reason: "wrong password".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Endpoint to place a new order
|
||||
#[post("/api/order", data = "<order>")]
|
||||
pub fn new_order(order: Form<OrderRequest<'_>>) -> String {
|
||||
let mut rng = rand::thread_rng(); // Get a new random number
|
||||
let to_hash = format!(
|
||||
"{}{}{}",
|
||||
order.user_message.to_string(),
|
||||
order.encryption_key.to_string(),
|
||||
rng.gen::<f64>().to_string()
|
||||
); // Create a has from various components of the order + a random number
|
||||
let db_order: Order = Order {
|
||||
name: order.name.to_string(),
|
||||
message: order.user_message.to_string(),
|
||||
encryption_key: order.encryption_key.to_string(),
|
||||
email: order.email.to_string(),
|
||||
payment_type: order.payment_type.clone(),
|
||||
uuid: Uuid::new_v5(&Uuid::NAMESPACE_X500, to_hash.as_bytes()),
|
||||
status: OrderStatus::Submitted,
|
||||
};
|
||||
make_order(&db_order);
|
||||
format!(
|
||||
"Thank you for ordering from Catgirl Pharmacy!\nYour order Uuid is {}",
|
||||
db_order.uuid.to_hyphenated().to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/api/order/<uuid>")]
|
||||
pub fn order_info(uuid: &str) -> Json<Order> {
|
||||
let order = read_order(uuid).unwrap().unwrap();
|
||||
return Json(order);
|
||||
}
|
||||
|
||||
#[post("/api/orders", data = "<auth>")]
|
||||
pub fn all_orders(
|
||||
auth: Form<Authenticate>,
|
||||
argon2: &State<Argon2>,
|
||||
salt: &State<SaltString>,
|
||||
) -> Result<Json<Vec<Order>>, Json<Status>> {
|
||||
let entered_password = auth.password.as_bytes();
|
||||
let hashed_password = argon2
|
||||
.hash_password(entered_password, &salt.as_ref())
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut admin_password = env::var("ADMIN_PASSWD").unwrap();
|
||||
admin_password = argon2
|
||||
.hash_password(admin_password.as_bytes(), &salt.as_ref())
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
if admin_password == hashed_password {
|
||||
let all_orders = read_all_orders();
|
||||
let mut return_string = String::new();
|
||||
let mut orders_list: Vec<Order> = Vec::new();
|
||||
for x in all_orders {
|
||||
return_string = format!("{}\n{:?}", return_string, x);
|
||||
orders_list.push(x);
|
||||
}
|
||||
|
||||
return Ok(Json(orders_list));
|
||||
} else {
|
||||
return Err(Json(Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "wrong password".to_string(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/update", data = "<update>")]
|
||||
pub fn update_product(
|
||||
db: &State<sled::Db>,
|
||||
argon2: &State<Argon2>,
|
||||
salt: &State<SaltString>,
|
||||
update: Form<Update<'_>>,
|
||||
) -> Json<Status> {
|
||||
let password = update.password.as_bytes();
|
||||
let hashed_password = argon2
|
||||
.hash_password(password, &salt.as_ref())
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut admin_password = env::var("ADMIN_PASSWD").unwrap();
|
||||
admin_password = argon2
|
||||
.hash_password(admin_password.as_bytes(), &salt.as_ref())
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
if admin_password == hashed_password {
|
||||
match update.field {
|
||||
"price_usd" => {
|
||||
let mut new_product = read_product(db, &update.product).unwrap().unwrap();
|
||||
new_product.price_usd = update.value;
|
||||
register_product(db, &update.product, &new_product);
|
||||
return Json(Status {
|
||||
status: "success".to_string(),
|
||||
reason: "price changed".to_string(),
|
||||
});
|
||||
}
|
||||
"stock" => {
|
||||
let mut new_product = read_product(db, &update.product).unwrap().unwrap();
|
||||
new_product.stock = update.value as u32;
|
||||
register_product(db, &update.product, &new_product);
|
||||
return Json(Status {
|
||||
status: "success".to_string(),
|
||||
reason: "stock changed".to_string(),
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Json(Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "field not found".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
return Json(Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "wrong password".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[get("/api/stock/<product>")]
|
||||
pub fn get_stock(db: &State<sled::Db>, product: &str) -> String {
|
||||
read_product(db, &product)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.stock
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[get("/api/price/<product>")]
|
||||
pub fn get_price(db: &State<sled::Db>, product: &str) -> String {
|
||||
read_product(db, &product)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.price_usd
|
||||
.to_string()
|
||||
}
|
|
@ -3,19 +3,21 @@ extern crate captcha;
|
|||
|
||||
use std::fs::File;
|
||||
|
||||
use captcha::filters::{Grid, Noise, Wave};
|
||||
use captcha::Captcha;
|
||||
use captcha::filters::{Noise, Wave, Grid};
|
||||
use std::path::Path;
|
||||
|
||||
use rocket::{serde::json::Json, form::Form, http::CookieJar, http::Cookie};
|
||||
use crate::structures::{Status, CaptchaAnswer};
|
||||
use crate::structures::{CaptchaAnswer, Status};
|
||||
use rocket::{form::Form, http::Cookie, http::CookieJar, serde::json::Json};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
// Create a new captcha image
|
||||
fn create_captcha() -> Captcha {
|
||||
let mut captcha = Captcha::new(); // create empty captcha
|
||||
|
||||
captcha.add_chars(7)
|
||||
captcha
|
||||
.add_chars(7)
|
||||
.apply_filter(Noise::new(0.2))
|
||||
.apply_filter(Grid::new(41, 67))
|
||||
.add_text_area()
|
||||
|
@ -24,34 +26,38 @@ fn create_captcha() -> Captcha {
|
|||
|
||||
let captcha_text = captcha.chars_as_string();
|
||||
|
||||
captcha.save(Path::new(&("/tmp/captcha".to_owned()+&captcha_text+".png")))
|
||||
.expect("save failed");// save captcha
|
||||
captcha
|
||||
.save(Path::new(
|
||||
&("/tmp/captcha".to_owned() + &captcha_text + ".png"),
|
||||
))
|
||||
.expect("save failed"); // save captcha
|
||||
|
||||
return captcha;
|
||||
}
|
||||
|
||||
// API to get a new captcha
|
||||
#[get("/api/captcha")]
|
||||
pub fn return_captcha(cookies: &CookieJar<'_>) -> File {
|
||||
let captcha = create_captcha();
|
||||
let captcha_text = captcha.chars_as_string();
|
||||
let captcha = create_captcha(); // Create a new captcha
|
||||
let captcha_text = captcha.chars_as_string(); // Store the captcha answer text
|
||||
|
||||
let file = File::open(&("/tmp/captcha".to_owned()+&captcha_text+".png"));
|
||||
let file = File::open(&("/tmp/captcha".to_owned() + &captcha_text + ".png")); // Open the captcha image file
|
||||
|
||||
println!("created new captcha {}", captcha_text);
|
||||
cookies.add(Cookie::new("token",
|
||||
Uuid::new_v5(
|
||||
&Uuid::NAMESPACE_X500,
|
||||
&captcha_text.as_bytes())
|
||||
.to_simple()
|
||||
.to_string())
|
||||
);
|
||||
println!("created new captcha {}", captcha_text); // Print the captcha text
|
||||
cookies.add(Cookie::new(
|
||||
"token", // Add a cookie to store the captcha answer
|
||||
Uuid::new_v5(&Uuid::NAMESPACE_X500, &captcha_text.as_bytes())
|
||||
.to_simple()
|
||||
.to_string(),
|
||||
));
|
||||
|
||||
return file.unwrap();
|
||||
return file.unwrap(); // Return the captcha image
|
||||
}
|
||||
|
||||
// Check if the provided captcha answer is correct
|
||||
#[post("/api/captcha", data = "<answer>")]
|
||||
pub fn check_captcha(answer: Form<CaptchaAnswer>) -> Json<Status> {
|
||||
return Json( Status {
|
||||
return Json(Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "meow".to_string(),
|
||||
});
|
||||
|
|
|
@ -1,64 +1,94 @@
|
|||
// File for database functions
|
||||
use crate::structures::*;
|
||||
|
||||
// no idea tbh
|
||||
type MyErrorType = Box<dyn std::error::Error>;
|
||||
|
||||
// Open the products database
|
||||
pub fn open() -> sled::Db {
|
||||
sled::open("products_db").unwrap()
|
||||
}
|
||||
|
||||
// Register a new product
|
||||
pub fn register_product(db: &sled::Db, short_name: &str, product: &Product) {
|
||||
let bytes = bincode::serialize(&product).unwrap();
|
||||
db.insert(short_name, bytes).unwrap();
|
||||
info!("Registering new product {:?}", &product);
|
||||
let bytes = bincode::serialize(&product).unwrap(); //Serialize the data
|
||||
db.insert(short_name, bytes).unwrap(); // Insert the product using the short name as a key
|
||||
db.flush().unwrap();
|
||||
}
|
||||
|
||||
// Read info about a product
|
||||
pub fn read_product(db: &sled::Db, short_name: &str) -> std::result::Result<Option<Product>, MyErrorType> {
|
||||
let entry = db.get(short_name)?;
|
||||
pub fn read_product(
|
||||
db: &sled::Db,
|
||||
short_name: &str,
|
||||
) -> std::result::Result<Option<Product>, MyErrorType> {
|
||||
info!("Reading product from shortcode {}", short_name);
|
||||
let entry = db.get(short_name)?; // Get a product entry out of the database using the short name
|
||||
if let Some(product_entry) = entry {
|
||||
// If it can read it, deserialize the entry then return it
|
||||
info!("Succesfully read product from products_db");
|
||||
let read_product: Product = bincode::deserialize(&product_entry)?;
|
||||
db.flush().unwrap();
|
||||
Ok(Some(read_product))
|
||||
} else {
|
||||
db.flush().unwrap();
|
||||
warn!("Failed to find product {} in products_db", short_name);
|
||||
db.flush().unwrap(); // Otherwise error
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new order
|
||||
pub fn make_order(order: &Order) {
|
||||
let db = sled::open("order_db").unwrap();
|
||||
let bytes = bincode::serialize(&order).unwrap();
|
||||
db.insert(order.uuid.to_hyphenated().to_string(), bytes).unwrap();
|
||||
info!(
|
||||
"Creating new order with Uuid {}",
|
||||
&order.uuid.to_hyphenated().to_string()
|
||||
);
|
||||
let db = sled::open("order_db").unwrap(); // Open the orders database
|
||||
let bytes = bincode::serialize(&order).unwrap(); // Serialize the new order
|
||||
db.insert(order.uuid.to_hyphenated().to_string(), bytes)
|
||||
.unwrap(); // Insert using the UUID as key
|
||||
db.flush().unwrap();
|
||||
}
|
||||
|
||||
// Read an order from UUID
|
||||
pub fn read_order(uuid: &str) -> std::result::Result<Option<Order>, MyErrorType> {
|
||||
let db = sled::open("order_db").unwrap();
|
||||
let entry = db.get(uuid)?;
|
||||
info!("Reading order with Uuid {}", uuid);
|
||||
let db = sled::open("order_db").unwrap(); // Open the orders database
|
||||
let entry = db.get(uuid)?; // Read an entry using UUID as a key
|
||||
if let Some(order_entry) = entry {
|
||||
let read_order: Order = bincode::deserialize(&order_entry)?;
|
||||
let read_order: Order = bincode::deserialize(&order_entry)?; // If succesful, deserialize & return
|
||||
info!("Succesfully deserialized order");
|
||||
db.flush().unwrap();
|
||||
Ok(Some(read_order))
|
||||
} else {
|
||||
// Fail
|
||||
warn!("Failed on reading order from orders_db with Uuid {}", uuid);
|
||||
db.flush().unwrap();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Read all orders pushed to the database
|
||||
pub fn read_all_orders() -> Vec<Order> {
|
||||
let db = sled::open("order_db").unwrap();
|
||||
let first_key: &[u8] = &db.first().unwrap().unwrap().0;
|
||||
let mut iter = db.range(first_key..);
|
||||
let mut all_orders: Vec<Order> = Vec::new();
|
||||
info!("Reading all orders in orders_db");
|
||||
let db = sled::open("order_db").unwrap(); // Open the orders database
|
||||
|
||||
let first_key: &[u8] = &db.first().unwrap().unwrap().0; // Get the first key from the database
|
||||
let iter = db.range(first_key..); // Create an iterator of all keys from the first one
|
||||
let mut all_orders: Vec<Order> = Vec::new(); // Create a new vector to store the orders
|
||||
|
||||
for order in iter {
|
||||
// Iterate over the orders
|
||||
if let order_entry = order.unwrap().1 {
|
||||
let read_order: Order = bincode::deserialize(&order_entry).unwrap();
|
||||
// If we can unwrap it:
|
||||
let read_order: Order = bincode::deserialize(&order_entry).unwrap(); // Deserialize the entry
|
||||
db.flush().unwrap();
|
||||
all_orders.push(read_order);
|
||||
all_orders.push(read_order); // Add the entry to the vector
|
||||
} else {
|
||||
// Fail
|
||||
info!("Could not find any more orders");
|
||||
db.flush().unwrap();
|
||||
}
|
||||
}
|
||||
return all_orders;
|
||||
return all_orders; // Return the orders
|
||||
}
|
||||
|
|
196
src/main.rs
196
src/main.rs
|
@ -1,186 +1,70 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
use rocket::{form::Form, State, Request, Response, http::Header};
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{http::Header, Request, Response};
|
||||
|
||||
use uuid::Uuid;
|
||||
use rand::Rng;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod api;
|
||||
mod captcha;
|
||||
mod database;
|
||||
mod structures;
|
||||
|
||||
use crate::api::*;
|
||||
use crate::captcha::*;
|
||||
|
||||
use argon2::{
|
||||
password_hash::{
|
||||
rand_core::OsRng,
|
||||
PasswordHash, PasswordHasher, PasswordVerifier, SaltString
|
||||
},
|
||||
Argon2
|
||||
password_hash::{rand_core::OsRng, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
|
||||
use std::env;
|
||||
|
||||
mod database;
|
||||
use crate::database::*;
|
||||
|
||||
mod structures;
|
||||
use crate::structures::*;
|
||||
|
||||
mod captcha;
|
||||
use crate::captcha::*;
|
||||
|
||||
pub struct CORS;
|
||||
|
||||
// Setup CORS header
|
||||
#[rocket::async_trait]
|
||||
impl Fairing for CORS {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "Attaching CORS headers to responses",
|
||||
kind: Kind::Response
|
||||
kind: Kind::Response,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
|
||||
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Methods", "POST, GET, PATCH, OPTIONS"));
|
||||
response.set_header(Header::new(
|
||||
"Access-Control-Allow-Methods",
|
||||
"POST, GET, PATCH, OPTIONS",
|
||||
));
|
||||
response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/new", data = "<request>")]
|
||||
fn new_product(db: &State<sled::Db>, argon2: &State<Argon2>, salt: &State<SaltString>, request: Form<ProductRequest>) -> Json<Status> {
|
||||
let password = request.password.as_bytes();
|
||||
let hashed_password = argon2.hash_password(password, &salt.as_ref()).unwrap().to_string();
|
||||
let mut admin_password = env::var("ADMIN_PASSWD").unwrap();
|
||||
admin_password = argon2.hash_password(admin_password.as_bytes(), &salt.as_ref()).unwrap().to_string();
|
||||
|
||||
if hashed_password == admin_password {
|
||||
let new_product: Product = Product {
|
||||
short_name: request.short_name.clone(),
|
||||
full_name: request.full_name.clone(),
|
||||
price_usd: request.price_usd,
|
||||
stock: request.stock,
|
||||
};
|
||||
register_product(db, &new_product.short_name, &new_product);
|
||||
return Json( Status {
|
||||
status: "success".to_string(),
|
||||
reason: "registered new product".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
return Json( Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "wrong password".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[post("/api/order", data = "<order>")]
|
||||
fn new_order(order: Form<OrderRequest<'_>>) -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
let to_hash = format!("{}{}{}", order.user_message.to_string(), order.encryption_key.to_string(), rng.gen::<f64>().to_string());
|
||||
let db_order: Order = Order {
|
||||
name: order.name.to_string(),
|
||||
message: order.user_message.to_string(),
|
||||
encryption_key: order.encryption_key.to_string(),
|
||||
email: order.email.to_string(),
|
||||
payment_type: order.payment_type.clone(),
|
||||
uuid: Uuid::new_v5(&Uuid::NAMESPACE_X500, to_hash.as_bytes()),
|
||||
};
|
||||
make_order(&db_order);
|
||||
format!("Thank you for ordering from Catgirl Pharmacy!\nYour order Uuid is {}", db_order.uuid.to_hyphenated().to_string())
|
||||
}
|
||||
|
||||
#[get("/api/order/<uuid>")]
|
||||
fn order_info(uuid: &str) -> Json<Order> {
|
||||
let order = read_order(uuid).unwrap().unwrap();
|
||||
return Json(order);
|
||||
}
|
||||
|
||||
#[post("/api/orders", data="<auth>")]
|
||||
fn all_orders(auth: Form<Authenticate>, argon2: &State<Argon2>, salt: &State<SaltString>) -> Result<Json<Vec<Order>>, Json<Status>>{
|
||||
let entered_password = auth.password.as_bytes();
|
||||
let hashed_password = argon2.hash_password(entered_password, &salt.as_ref()).unwrap().to_string();
|
||||
let mut admin_password = env::var("ADMIN_PASSWD").unwrap();
|
||||
admin_password = argon2.hash_password(admin_password.as_bytes(), &salt.as_ref()).unwrap().to_string();
|
||||
|
||||
if admin_password == hashed_password {
|
||||
let all_orders = read_all_orders();
|
||||
let mut return_string = String::new();
|
||||
let mut orders_list: Vec<Order> = Vec::new();
|
||||
for x in all_orders {
|
||||
return_string = format!("{}\n{:?}",return_string, x);
|
||||
orders_list.push(x);
|
||||
}
|
||||
|
||||
return Ok(Json(orders_list));
|
||||
} else {
|
||||
return Err(Json( Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "wrong password".to_string(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/update", data = "<update>")]
|
||||
fn update_product(db: &State<sled::Db>, argon2: &State<Argon2>, salt: &State<SaltString>, update: Form<Update<'_>>) -> Json<Status> {
|
||||
let password = update.password.as_bytes();
|
||||
let hashed_password = argon2.hash_password(password, &salt.as_ref()).unwrap().to_string();
|
||||
let mut admin_password = env::var("ADMIN_PASSWD").unwrap();
|
||||
admin_password = argon2.hash_password(admin_password.as_bytes(), &salt.as_ref()).unwrap().to_string();
|
||||
|
||||
if admin_password == hashed_password {
|
||||
|
||||
match update.field {
|
||||
"price_usd" => {
|
||||
let mut new_product = read_product(db, &update.product).unwrap().unwrap();
|
||||
new_product.price_usd = update.value;
|
||||
register_product(db, &update.product, &new_product);
|
||||
return Json( Status {
|
||||
status: "success".to_string(),
|
||||
reason: "price changed".to_string(),
|
||||
});
|
||||
}
|
||||
"stock" => {
|
||||
let mut new_product = read_product(db, &update.product).unwrap().unwrap();
|
||||
new_product.stock = update.value as u32;
|
||||
register_product(db, &update.product, &new_product);
|
||||
return Json( Status {
|
||||
status: "success".to_string(),
|
||||
reason: "stock changed".to_string(),
|
||||
});
|
||||
}
|
||||
_ => return Json( Status { status: "fail".to_string(), reason: "field not found".to_string() }),
|
||||
}
|
||||
};
|
||||
return Json( Status {
|
||||
status: "fail".to_string(),
|
||||
reason: "wrong password".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[get("/api/stock/<product>")]
|
||||
fn get_stock(db: &State<sled::Db>, product: &str) -> String {
|
||||
read_product(db, &product)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.stock
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[get("/api/price/<product>")]
|
||||
fn get_price(db: &State<sled::Db>, product: &str) -> String {
|
||||
read_product(db, &product)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.price_usd
|
||||
.to_string()
|
||||
}
|
||||
|
||||
// Launch rocket
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
env_logger::init();
|
||||
info!("Starting up Rocket");
|
||||
|
||||
rocket::build()
|
||||
.manage(database::open())
|
||||
.manage(Argon2::default())
|
||||
.manage(SaltString::generate(&mut OsRng))
|
||||
.mount("/",
|
||||
routes![get_stock, get_price, new_order, order_info, all_orders, new_product, update_product, return_captcha, check_captcha])
|
||||
.manage(database::open()) // Manage database state
|
||||
.manage(Argon2::default()) // Manage Argon2 state
|
||||
.manage(SaltString::generate(&mut OsRng)) // Manage RNG state
|
||||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
get_stock,
|
||||
get_price,
|
||||
new_order,
|
||||
order_info,
|
||||
all_orders,
|
||||
new_product,
|
||||
update_product,
|
||||
return_captcha,
|
||||
check_captcha
|
||||
],
|
||||
)
|
||||
.attach(CORS)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
use serde::{Deserialize, Serialize}; use uuid::Uuid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
// Struct for the urers answer to a captcha
|
||||
#[derive(Clone, Debug, FromForm)]
|
||||
pub struct CaptchaAnswer {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
// Return status
|
||||
#[derive(Serialize)]
|
||||
pub struct Status {
|
||||
pub status: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
// User authentication
|
||||
#[derive(Clone, Debug, FromForm)]
|
||||
pub struct Authenticate {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
// Struct for new product
|
||||
#[derive(Clone, Debug, FromForm)]
|
||||
pub struct ProductRequest {
|
||||
pub full_name: String,
|
||||
|
@ -25,6 +30,7 @@ pub struct ProductRequest {
|
|||
pub password: String,
|
||||
}
|
||||
|
||||
// Struct for a stored product
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, FromForm)]
|
||||
pub struct Product {
|
||||
pub full_name: String,
|
||||
|
@ -33,6 +39,7 @@ pub struct Product {
|
|||
pub stock: u32,
|
||||
}
|
||||
|
||||
// User order request
|
||||
#[derive(FromForm)]
|
||||
pub struct OrderRequest<'r> {
|
||||
pub r#name: &'r str,
|
||||
|
@ -42,6 +49,7 @@ pub struct OrderRequest<'r> {
|
|||
pub r#payment_type: PaymentOption,
|
||||
}
|
||||
|
||||
// Struct for a stored order
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Order {
|
||||
pub name: String,
|
||||
|
@ -50,8 +58,10 @@ pub struct Order {
|
|||
pub encryption_key: String,
|
||||
pub payment_type: PaymentOption,
|
||||
pub uuid: Uuid,
|
||||
pub status: OrderStatus,
|
||||
}
|
||||
|
||||
// Struct for a request to update a product
|
||||
#[derive(FromForm)]
|
||||
pub struct Update<'r> {
|
||||
pub r#product: &'r str,
|
||||
|
@ -60,6 +70,16 @@ pub struct Update<'r> {
|
|||
pub r#password: &'r str,
|
||||
}
|
||||
|
||||
// Possible status states for an order
|
||||
#[derive(Debug, PartialEq, FromFormField, Serialize, Clone, Deserialize)]
|
||||
pub enum OrderStatus {
|
||||
Submitted,
|
||||
Processing,
|
||||
Shipping,
|
||||
Complete,
|
||||
}
|
||||
|
||||
// Options for payment
|
||||
#[derive(Debug, PartialEq, FromFormField, Serialize, Clone, Deserialize)]
|
||||
pub enum PaymentOption {
|
||||
XMR,
|
||||
|
|
Loading…
Reference in New Issue