From 9ca54103e554d9581746c65d3e498d25f15fcd8e Mon Sep 17 00:00:00 2001 From: shockrah Date: Sun, 13 Nov 2022 12:38:09 -0800 Subject: [PATCH] - Removing admin features from core service binary API Because this feature is going to be optional we're going to provide this as a seperate service as the two have entirely different goals anyway --- clippable-svc/src/admin/apikey.rs | 51 ------------ clippable-svc/src/admin/mod.rs | 76 ------------------ clippable-svc/src/admin/response.rs | 35 --------- clippable-svc/src/admin/util.rs | 14 ---- clippable-svc/src/db.rs | 117 ---------------------------- clippable-svc/src/main.rs | 16 ---- clippable-svc/src/sec.rs | 26 ------- 7 files changed, 335 deletions(-) delete mode 100644 clippable-svc/src/admin/apikey.rs delete mode 100644 clippable-svc/src/admin/mod.rs delete mode 100644 clippable-svc/src/admin/response.rs delete mode 100644 clippable-svc/src/admin/util.rs delete mode 100644 clippable-svc/src/db.rs delete mode 100644 clippable-svc/src/sec.rs diff --git a/clippable-svc/src/admin/apikey.rs b/clippable-svc/src/admin/apikey.rs deleted file mode 100644 index 4393422..0000000 --- a/clippable-svc/src/admin/apikey.rs +++ /dev/null @@ -1,51 +0,0 @@ -use rocket::request::{Outcome, Request, FromRequest}; -use rocket::async_trait; -use rocket::http::Status; - -use crate::db::{self, DB_PATH}; - -pub struct ApiKey { - // These are used by rocket's driver code/decl macros however cargo - // is not able to check those as the code is generated at compile time. - // The dead code thing is just to stifle pointless warnings - #[allow(dead_code)] - uid: String, - #[allow(dead_code)] - key: String -} - -#[derive(Debug)] -pub enum ApiKeyError { - Missing, - Invalid, -} - - -#[async_trait] -impl<'r> FromRequest<'r> for ApiKey { - type Error = ApiKeyError; - async fn from_request(req: &'r Request<'_>) -> Outcome { - let key = req.headers().get_one("ADMIN-API-KEY"); - let uid = req.headers().get_one("ADMIN-API-UID"); - - if key.is_none() || uid.is_none() { - return Outcome::Failure((Status::Forbidden, ApiKeyError::Missing)); - } - - let (key, uid) = (key.unwrap(), uid.unwrap()); - - println!("Path to use for db file {:?}", DB_PATH.to_string()); - let db = db::Database::load(DB_PATH.as_str().into()).unwrap(); - if let Some(stored) = db.get(uid) { - if stored == key { - return Outcome::Success(ApiKey { - key: key.into(), - uid: uid.into() - }) - } - return Outcome::Failure((Status::Forbidden, ApiKeyError::Invalid)) - } - - return Outcome::Failure((Status::Forbidden, ApiKeyError::Invalid)) - } -} diff --git a/clippable-svc/src/admin/mod.rs b/clippable-svc/src/admin/mod.rs deleted file mode 100644 index 1d82851..0000000 --- a/clippable-svc/src/admin/mod.rs +++ /dev/null @@ -1,76 +0,0 @@ -#[cfg(feature = "admin")] - -mod apikey; -mod response; -mod util; - -use std::collections::HashMap; -use std::io::Result; -use std::fs; -use std::path::{Path, PathBuf}; - -use rocket::data::{Data, ToByteUnit}; -use rocket::serde::json::Json; -use rocket_dyn_templates::Template; -use rocket::response::Redirect; -use response::{bad_request, ok}; - -use apikey::ApiKey; -use response::ActionResponse; -use crate::common::get_clips_dir; - -#[get("/")] -pub async fn login_dashboard_redirect() -> Redirect { - Redirect::to("/admin/dashboard") -} - -#[get("/dashboard")] -pub async fn login_dashboard() -> Template { - // This page is basically just a login form - // However the rest of the form is present on this page, just hidden - let h: HashMap = HashMap::new(); // does not allocate - return Template::render("admin", &h); -} - -#[post("/dashboard")] -pub async fn dashboard(_key: ApiKey) -> Json { - // Assuming the api key check doesn't fail we can reply with Ok - // at the application level - ok() -} - - -#[post("/upload-video//", data = "")] -pub async fn updload_video(_key: ApiKey, category: PathBuf, filename: PathBuf, data: Data<'_>) --> Result> { - /* - * Uploads must have BOTH a valid filename and a category - * Without the category the server will simply not find - * the correct endpoint to reach and thus will 404 - */ - if util::valid_filename(&filename) == false { - return Ok(bad_request(Some("Invalid filename(s)"))); - } - - let clips = get_clips_dir(); - fs::create_dir_all(Path::new(&clips).join(&category))?; - - /* - * We allow up to 200 Megaytes per upload as most short - * clips are not going to be very large anyway and this - * should be a reasonably high limit for those that want - * to upload "large" clips - * */ - let filepath = Path::new(&clips).join(category).join(filename); - data.open(250.megabytes()).into_file(filepath).await?; - Ok(ok()) -} - -#[delete("/remove-video//")] -pub async fn remove_video(_key: ApiKey, category: PathBuf, filename: PathBuf) --> Result> { - let clips = get_clips_dir(); - let path = Path::new(&clips).join(&category).join(&filename); - fs::remove_file(path)?; - Ok(ok()) -} diff --git a/clippable-svc/src/admin/response.rs b/clippable-svc/src/admin/response.rs deleted file mode 100644 index 51e53b2..0000000 --- a/clippable-svc/src/admin/response.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Module handles general responses for the admin feature - * Primarily these are responses for Admin related actions - * like fetching video's, updating videos and deleting them - * as well - */ -use serde::Serialize; -use rocket::serde::json::Json; - -const FAIL: &'static str = "fail"; -const OK: &'static str = "fail"; - - -#[derive(Serialize)] -pub struct ActionResponse { - status: &'static str, - code: i32, - details: Option<&'static str> -} - -pub fn ok() -> Json { - Json(ActionResponse { - status: OK, - code: 200, - details: None - }) -} - -pub fn bad_request(text: Option<&'static str>) -> Json { - Json(ActionResponse { - status: FAIL, - code: 400, - details: text - }) -} \ No newline at end of file diff --git a/clippable-svc/src/admin/util.rs b/clippable-svc/src/admin/util.rs deleted file mode 100644 index eb9e305..0000000 --- a/clippable-svc/src/admin/util.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::path::PathBuf; - -pub fn valid_filename(p: &PathBuf) -> bool { - // Checks if a given filename is actually valid or not - let mut valid = false; - let s = p.file_name().unwrap_or_default().to_string_lossy(); - for e in [".mp4", ".webm", ".mkv"] { - if s.ends_with(e) { - valid = true; - break; - } - } - valid -} \ No newline at end of file diff --git a/clippable-svc/src/db.rs b/clippable-svc/src/db.rs deleted file mode 100644 index 1bac8af..0000000 --- a/clippable-svc/src/db.rs +++ /dev/null @@ -1,117 +0,0 @@ -#[cfg(feature = "admin")] -// This module defines a tiny async interface for the "database" that this -// project uses for interfacing with the key store - -// WARN: at the moment there are no guarantees as far as data integrity is -// concerned. This means there are no real transactions -use std::env; -use std::fs::OpenOptions; -use std::io::{BufWriter, BufReader}; -use std::path::PathBuf; -use std::collections::HashMap; -use rocket::serde::{Serialize, Deserialize}; - -lazy_static! { - pub static ref DB_PATH: String = { - env::var("DB_PATH").unwrap_or("keys.db".into()) - }; -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Database { - // uid's while random are fine to release as public as the key is more - // important however ideally neither should be release. Furthermore - // the frontend assists in keeping these secret by treating both as - // password fields as they are both randomly generated via a script - - // uid -> key - users: HashMap, - #[serde(skip)] - path: PathBuf -} - -impl Database { - // Opens a handle to a database file - // if none is found then one is created with the new path - // if there is one then the existing database is used - // any thing else is invalid and causes this to return Err - pub fn new(path: PathBuf) -> Result { - let file = OpenOptions::new() - .write(true) - .create(true) - .open(&path)?; - let writer = BufWriter::new(&file); - - // Dummy value to write in place - let empty = Database { users: HashMap::new(), path: "".into() }; - serde_json::to_writer(writer, &empty)?; - - Ok(empty) - } - - pub fn load(path: PathBuf) -> Result { - let file = OpenOptions::new() - .read(true) - .open(&path)?; - let reader = BufReader::new(&file); - let mut data: Database = serde_json::from_reader(reader)?; - - data.path = path; - return Ok(data); - } - - pub fn get(&self, uid: &str) -> Option<&String> { - return self.users.get(uid); - } - - fn write(&self) -> Result<(), std::io::Error> { - let file = OpenOptions::new() - .write(true) - .open(&self.path)?; - let writer = BufWriter::new(file); - serde_json::to_writer(writer, &self.path)?; - return Ok(()) - } - - pub fn remove(&mut self, uid: &str) -> Result<(), std::io::Error> { - self.users.remove_entry(uid); - self.write() - } - pub fn add(&mut self, key: &str, value: &str) -> Result<(), std::io::Error> { - println!("{:?}", self.path); - self.users.insert(key.into(), value.into()); - self.write() - } -} - - -#[cfg(test)] -mod db_tests { - use super::Database; - - const DB: &'static str = "new.db"; - - #[test] - fn load_db() { - match Database::new(DB.into()) { - Ok(db) => println!("Loaded new sample.db: {:?}", db), - Err(e) => panic!("Error fetching database: {}", e) - } - } - - #[test] - fn add_simple_entries() { - match Database::load(DB.into()) { - Ok(mut db) => db.add("key", "value").unwrap(), - Err(e) => println!("Error adding entries: {}", e) - } - } - - #[test] - fn remove_simple_entries() { - match Database::load(DB.into()) { - Ok(mut db) => db.remove("key").unwrap(), - Err(e) => println!("Error removing simple entries: {}", e) - } - } -} diff --git a/clippable-svc/src/main.rs b/clippable-svc/src/main.rs index 9344389..c762c56 100644 --- a/clippable-svc/src/main.rs +++ b/clippable-svc/src/main.rs @@ -10,15 +10,6 @@ mod video; mod thumbnail; mod common; -#[cfg(feature = "admin")] -mod admin; - -#[cfg(feature = "admin")] -mod sec; - -#[cfg(feature = "admin")] -mod db; - #[rocket::main] async fn main() { // rid ourselves of random emoji's in logs @@ -33,13 +24,6 @@ async fn main() { .mount("/api", routes![category::list, video::list]) // json .mount("/thumbnail", routes![thumbnail::get]) // images .mount("/video", routes![video::get_video]) // videos - .mount("/admin", routes![ - admin::login_dashboard_redirect, - admin::login_dashboard, - admin::dashboard, - admin::updload_video, - admin::remove_video - ]) .attach(Template::fairing()) .launch().await; } else { diff --git a/clippable-svc/src/sec.rs b/clippable-svc/src/sec.rs deleted file mode 100644 index 9fbd375..0000000 --- a/clippable-svc/src/sec.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[cfg(feature = "admin")] -// This module concerns itself with the encrypting/decrypting of passwords -// as well as the storage of those items -use rocket::tokio::io::AsyncReadExt; -use rocket::tokio::fs; - -async fn random_string() -> Result { - // First we read in some bytes from /dev/urandom - let mut handle = fs::File::open("/dev/urandom").await?; - let mut buffer = [0;32]; - handle.read(&mut buffer[..]).await?; - - Ok(base64::encode_config(buffer, base64::URL_SAFE_NO_PAD)) -} - - -#[cfg(test)] -mod sec_api_tests { - use rocket::tokio; - use super::random_string; - - #[tokio::test] - async fn generate_string() { - println!("{:?}", random_string().await); - } -}