From 3c36122b8622d384453e6e146b38b1235dc6aeda Mon Sep 17 00:00:00 2001 From: MedzikUser Date: Sun, 24 Apr 2022 21:31:50 +0200 Subject: [PATCH] http (fs): add file upload --- Cargo.lock | 1 + config.toml | 4 +++ database/src/sqlite.rs | 10 ++----- server/Cargo.toml | 1 + server/src/fs/mod.rs | 7 +++++ server/src/fs/upload.rs | 62 +++++++++++++++++++++++++++++++++++++++ server/src/lib.rs | 2 ++ types/src/config/types.rs | 6 ++++ types/src/database/mod.rs | 2 +- types/src/errors/fs.rs | 16 ++++++++++ types/src/errors/mod.rs | 16 ++++++++-- types/src/fs/mod.rs | 1 + types/src/fs/upload.rs | 12 ++++++++ types/src/lib.rs | 1 + 14 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 server/src/fs/mod.rs create mode 100644 server/src/fs/upload.rs create mode 100644 types/src/errors/fs.rs create mode 100644 types/src/fs/mod.rs create mode 100644 types/src/fs/upload.rs diff --git a/Cargo.lock b/Cargo.lock index dc5384f..c2d3bcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,6 +622,7 @@ version = "0.0.0" dependencies = [ "axum", "axum-auth", + "base64", "homedisk-database", "homedisk-types", "hyper", diff --git a/config.toml b/config.toml index 4813cd4..2a5cf27 100644 --- a/config.toml +++ b/config.toml @@ -11,3 +11,7 @@ cors = [ [jwt] secret = "secret key used to sign tokens" # secret used to sign tokens expires = 24 # validity of token in hours + +# Storage +[storage] +path = "/home/homedisk" # the folder where the user files will be stored diff --git a/database/src/sqlite.rs b/database/src/sqlite.rs index c3b3c59..239c69b 100644 --- a/database/src/sqlite.rs +++ b/database/src/sqlite.rs @@ -180,10 +180,7 @@ mod tests { let user = User::new("medzik", "Qwerty1234!"); - let res = db - .find_user_by_id(user.id) - .await - .expect("find user"); + let res = db.find_user_by_id(user.id).await.expect("find user"); assert_eq!(res.password, user.password) } @@ -196,10 +193,7 @@ mod tests { let other_user = User::new("other_user", "secretpassphrase123!"); - let err = db - .find_user_by_id(other_user.id) - .await - .unwrap_err(); + let err = db.find_user_by_id(other_user.id).await.unwrap_err(); assert_eq!(err.to_string(), "user not found") } diff --git a/server/Cargo.toml b/server/Cargo.toml index a4c2656..c2d6edb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -15,3 +15,4 @@ homedisk-database = { path = "../database" } homedisk-types = { path = "../types", features = ["axum"] } axum-auth = "0.2.0" jsonwebtoken = "8.1.0" +base64 = "0.13.0" diff --git a/server/src/fs/mod.rs b/server/src/fs/mod.rs new file mode 100644 index 0000000..f22c877 --- /dev/null +++ b/server/src/fs/mod.rs @@ -0,0 +1,7 @@ +pub mod upload; + +pub fn app() -> axum::Router { + use axum::routing::post; + + axum::Router::new().route("/upload", post(upload::handle)) +} diff --git a/server/src/fs/upload.rs b/server/src/fs/upload.rs new file mode 100644 index 0000000..2f3747e --- /dev/null +++ b/server/src/fs/upload.rs @@ -0,0 +1,62 @@ +use std::{fs, path::Path}; + +use axum::{extract::rejection::JsonRejection, Extension, Json}; +use axum_auth::AuthBearer; +use homedisk_database::{Database, Error}; +use homedisk_types::{ + config::types::Config, + errors::{AuthError, FsError, ServerError}, + fs::upload::{Request, Response}, +}; + +use crate::middleware::{validate_json, validate_jwt}; + +pub async fn handle( + db: Extension, + config: Extension, + AuthBearer(token): AuthBearer, + request: Result, JsonRejection>, +) -> Result, ServerError> { + let Json(request) = validate_json::(request)?; + let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; + + let response = match db.find_user_by_id(token.claims.sub).await { + Ok(res) => { + // get file content + let content = base64::decode(request.content) + .map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?; + + // check if file already exists + if Path::new(&request.path).exists() { + return Err(ServerError::FsError(FsError::FileAlreadyExists)); + } + + let folder_path = format!( + "{path}/{username}", + path = config.storage.path, + username = res.username, + ); + let path = format!("{folder_path}/{filepath}", filepath = request.path); + + // create user directory + fs::create_dir_all(&folder_path).unwrap(); + + // write file + fs::write(&path, content) + .map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?; + + Response { uploaded: true } + } + + Err(err) => match err { + Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), + _ => { + return Err(ServerError::AuthError(AuthError::UnknowError( + err.to_string(), + ))) + } + }, + }; + + Ok(Json(response)) +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 93a5eb1..f3f585e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod fs; pub mod middleware; mod error; @@ -25,6 +26,7 @@ pub async fn serve( let app = Router::new() .route("/health-check", get(health_check)) .nest("/auth", auth::app()) + .nest("/fs", fs::app()) .layer(CorsLayer::new().allow_origin(Origin::list(origins))) .layer(Extension(db)) .layer(Extension(config)); diff --git a/types/src/config/types.rs b/types/src/config/types.rs index 5d026e4..8667bf5 100644 --- a/types/src/config/types.rs +++ b/types/src/config/types.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; pub struct Config { pub http: ConfigHTTP, pub jwt: ConfigJWT, + pub storage: ConfigStorage, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -18,3 +19,8 @@ pub struct ConfigJWT { pub secret: String, pub expires: i64, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConfigStorage { + pub path: String, +} diff --git a/types/src/database/mod.rs b/types/src/database/mod.rs index 09912bd..2478900 100644 --- a/types/src/database/mod.rs +++ b/types/src/database/mod.rs @@ -1,3 +1,3 @@ mod user; -pub use {user::*}; +pub use user::*; diff --git a/types/src/errors/fs.rs b/types/src/errors/fs.rs new file mode 100644 index 0000000..6996982 --- /dev/null +++ b/types/src/errors/fs.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)] +pub enum Error { + #[error("file already exists")] + FileAlreadyExists, + + #[error("file already exists")] + WriteFile(String), + + #[error("base64 - {0}")] + Base64(String), + + #[error("unknow error")] + UnknowError(String), +} diff --git a/types/src/errors/mod.rs b/types/src/errors/mod.rs index 8f643c9..e8048db 100644 --- a/types/src/errors/mod.rs +++ b/types/src/errors/mod.rs @@ -1,6 +1,8 @@ mod auth; +mod fs; pub use auth::Error as AuthError; +pub use fs::Error as FsError; use serde::{Deserialize, Serialize}; @@ -10,6 +12,9 @@ pub enum ServerError { #[error("auth error: {0}")] AuthError(#[from] AuthError), + #[error("fs error: {0}")] + FsError(#[from] FsError), + #[error("too may requests, please slow down")] TooManyRequests, @@ -35,15 +40,20 @@ impl axum::response::IntoResponse for ServerError { use axum::http::StatusCode; let status = match self { - Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS, Self::AuthError(ref err) => match err { AuthError::UserNotFound => StatusCode::BAD_REQUEST, AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE, AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR, - AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR, AuthError::InvalidToken => StatusCode::BAD_REQUEST, + AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR, }, - + Self::FsError(ref err) => match err { + FsError::FileAlreadyExists => StatusCode::BAD_REQUEST, + FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR, + FsError::Base64(_) => StatusCode::BAD_REQUEST, + FsError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR, + }, + Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS, Self::MissingJsonContentType => StatusCode::BAD_REQUEST, Self::JsonDataError => StatusCode::BAD_REQUEST, Self::JsonSyntaxError => StatusCode::BAD_REQUEST, diff --git a/types/src/fs/mod.rs b/types/src/fs/mod.rs new file mode 100644 index 0000000..709ebba --- /dev/null +++ b/types/src/fs/mod.rs @@ -0,0 +1 @@ +pub mod upload; diff --git a/types/src/fs/upload.rs b/types/src/fs/upload.rs new file mode 100644 index 0000000..498c22e --- /dev/null +++ b/types/src/fs/upload.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Request { + pub content: String, + pub path: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Response { + pub uploaded: bool, +} diff --git a/types/src/lib.rs b/types/src/lib.rs index a619bd3..075ea5c 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -5,3 +5,4 @@ pub mod macros; #[cfg(feature = "database")] pub mod database; +pub mod fs;