From 8bd4d6f1071143e63b1ee3607a4e13a6e51eca93 Mon Sep 17 00:00:00 2001 From: MedzikUser Date: Sun, 1 May 2022 20:34:28 +0200 Subject: [PATCH] server (fs): reform --- server/src/auth/login.rs | 16 +++--- server/src/auth/register.rs | 4 +- server/src/auth/types.rs | 10 ---- server/src/fs/list.rs | 103 +++++++++++++++-------------------- server/src/fs/mod.rs | 14 +++++ server/src/fs/upload.rs | 92 +++++++++++++------------------ server/src/middleware/jwt.rs | 3 +- types/src/database/user.rs | 20 +++++++ 8 files changed, 126 insertions(+), 136 deletions(-) delete mode 100644 server/src/auth/types.rs diff --git a/server/src/auth/login.rs b/server/src/auth/login.rs index 6e0e2c6..0fd2494 100644 --- a/server/src/auth/login.rs +++ b/server/src/auth/login.rs @@ -9,8 +9,8 @@ use homedisk_types::{ use crate::middleware::{create_token, validate_json}; pub async fn handle( - db: Extension, - config: Extension, + Extension(db): Extension, + Extension(config): Extension, request: Result, JsonRejection>, ) -> Result, ServerError> { let request = validate_json::(request)?; @@ -26,14 +26,14 @@ pub async fn handle( } } - Err(err) => match err { - Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), - _ => { - return Err(ServerError::AuthError(AuthError::UnknowError( + Err(err) => { + return match err { + Error::UserNotFound => Err(ServerError::AuthError(AuthError::UserNotFound)), + _ => Err(ServerError::AuthError(AuthError::UnknowError( err.to_string(), - ))) + ))), } - }, + } }; Ok(Json(response)) diff --git a/server/src/auth/register.rs b/server/src/auth/register.rs index 957723f..41fbf65 100644 --- a/server/src/auth/register.rs +++ b/server/src/auth/register.rs @@ -11,8 +11,8 @@ use homedisk_types::{ use crate::middleware::{create_token, validate_json}; pub async fn handle( - db: Extension, - config: Extension, + Extension(db): Extension, + Extension(config): Extension, request: Result, JsonRejection>, ) -> Result, ServerError> { let request = validate_json::(request)?; diff --git a/server/src/auth/types.rs b/server/src/auth/types.rs deleted file mode 100644 index f7a9b06..0000000 --- a/server/src/auth/types.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[derive(Debug, Clone)] -pub struct Request { - pub username: String, - pub password: String, -} - -#[derive(Debug, Clone)] -pub enum ResponseLogin { - LoggedIn { access_token: String }, -} diff --git a/server/src/fs/list.rs b/server/src/fs/list.rs index a37b42a..532fc35 100644 --- a/server/src/fs/list.rs +++ b/server/src/fs/list.rs @@ -1,81 +1,64 @@ use std::fs; +use crate::fs::validate_path; use axum::{extract::rejection::JsonRejection, Extension, Json}; use axum_auth::AuthBearer; use byte_unit::Byte; -use homedisk_database::{Database, Error}; +use homedisk_database::Database; use homedisk_types::{ config::types::Config, - errors::{AuthError, FsError, ServerError}, + errors::{FsError, ServerError}, fs::list::{FileInfo, Request, Response}, }; -use crate::middleware::{validate_json, validate_jwt}; +use crate::middleware::{find_user, validate_json, validate_jwt}; pub async fn handle( - db: Extension, - config: Extension, + Extension(db): Extension, + 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)?; - // `path` cannot contain `..` - // to prevent attack attempts because by using a `..` you can access the previous folder - if request.path.contains("..") { - return Err(ServerError::FsError(FsError::ReadDir( - "the `path` must not contain `..`".to_string(), - ))); + // validate the `path` can be used + validate_path(&request.path)?; + + // search for a user by UUID from a token + let user = find_user(db, token.claims.sub).await?; + + // directory where the file will be placed + let path = format!( + "{user_dir}/{req_dir}", + user_dir = user.user_dir(&config.storage.path), + req_dir = request.path + ); + + let paths = fs::read_dir(&path) + .map_err(|err| ServerError::FsError(FsError::ReadDir(err.to_string())))?; + + let mut files = vec![]; + let mut dirs = vec![]; + + for f in paths { + let f = f.map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?; + let metadata = f + .metadata() + .map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?; + + let name = f.path().display().to_string().replace(&path, ""); + let file_size = Byte::from_bytes(metadata.len().into()).get_appropriate_unit(true); + + if metadata.is_dir() { + dirs.push(name) + } else { + files.push(FileInfo { + name, + size: file_size.to_string(), + }) + } } - let response = match db.find_user_by_id(token.claims.sub).await { - Ok(res) => { - let user_path = format!( - "{path}/{username}/{request_path}", - path = config.storage.path, - username = res.username, - request_path = request.path, - ); - - let paths = fs::read_dir(&user_path) - .map_err(|err| ServerError::FsError(FsError::ReadDir(err.to_string())))?; - - let mut files = vec![]; - let mut dirs = vec![]; - - for path in paths { - let path = path - .map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?; - let metadata = path - .metadata() - .map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?; - - let name = path.path().display().to_string().replace(&user_path, ""); - let filesize = Byte::from_bytes(metadata.len().into()).get_appropriate_unit(true); - - if metadata.is_dir() { - dirs.push(name) - } else { - files.push(FileInfo { - name, - size: filesize.to_string(), - }) - } - } - - Response { files, dirs } - } - - Err(err) => { - return match err { - Error::UserNotFound => Err(ServerError::AuthError(AuthError::UserNotFound)), - _ => Err(ServerError::AuthError(AuthError::UnknowError( - err.to_string(), - ))), - } - } - }; - - Ok(Json(response)) + Ok(Json(Response { files, dirs })) } diff --git a/server/src/fs/mod.rs b/server/src/fs/mod.rs index 650c651..ca82bf1 100644 --- a/server/src/fs/mod.rs +++ b/server/src/fs/mod.rs @@ -1,3 +1,5 @@ +use homedisk_types::errors::{FsError, ServerError}; + pub mod list; pub mod upload; @@ -8,3 +10,15 @@ pub fn app() -> axum::Router { .route("/list", post(list::handle)) .route("/upload", post(upload::handle)) } + +pub fn validate_path(path: &str) -> Result<(), ServerError> { + // `path` cannot contain `..` + // to prevent attack attempts because by using a `..` you can access the previous folder + if path.contains("..") { + return Err(ServerError::FsError(FsError::ReadDir( + "the `path` must not contain `..`".to_string(), + ))); + } + + Ok(()) +} diff --git a/server/src/fs/upload.rs b/server/src/fs/upload.rs index 4e24c74..e9ef2de 100644 --- a/server/src/fs/upload.rs +++ b/server/src/fs/upload.rs @@ -1,76 +1,60 @@ use std::{fs, path::Path}; +use crate::fs::validate_path; use axum::{extract::rejection::JsonRejection, Extension, Json}; use axum_auth::AuthBearer; -use homedisk_database::{Database, Error}; +use homedisk_database::Database; use homedisk_types::{ config::types::Config, - errors::{AuthError, FsError, ServerError}, + errors::{FsError, ServerError}, fs::upload::{Request, Response}, }; -use crate::middleware::{validate_json, validate_jwt}; +use crate::middleware::{find_user, validate_json, validate_jwt}; pub async fn handle( - db: Extension, - config: Extension, + Extension(db): Extension, + 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)?; - // `path` cannot contain `..` - // to prevent attack attempts because by using a `..` you can access the previous folder - if request.path.contains("..") { - return Err(ServerError::FsError(FsError::ReadDir( - "the `path` must not contain `..`".to_string(), - ))); + // validate the `path` can be used + validate_path(&request.path)?; + + // search for a user by UUID from a token + let user = find_user(db, token.claims.sub).await?; + + // get file content + let content = base64::decode(request.content) + .map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?; + + // directory where the file will be placed + let dir = format!( + "{user_dir}/{req_dir}", + user_dir = user.user_dir(&config.storage.path), + req_dir = request.path + ); + let path = Path::new(&dir); + + // check if the file currently exists to avoid overwriting it + if path.exists() { + return Err(ServerError::FsError(FsError::FileAlreadyExists)); } - 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())))?; + // create a directory where the file will be placed + // e.g. path ==> `/secret/files/images/screenshot.png` + // directories up to `/home/homedisk/{username}/secret/files/images/` will be created + match path.parent() { + Some(prefix) => fs::create_dir_all(&prefix).unwrap(), + None => (), + } - let path = format!( - "{path}/{username}/{filepath}", - path = config.storage.path, - username = res.username, - filepath = request.path - ); - let path = Path::new(&path); + // write file + fs::write(&path, &content) + .map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?; - // check if the file currently exists to avoid overwriting it - if path.exists() { - return Err(ServerError::FsError(FsError::FileAlreadyExists)); - } - - // create a directory where the file will be placed - // e.g. path ==> `/secret/files/images/screenshot.png` - // directories up to `/home/homedisk/{username}/secret/files/images/` will be created - match path.parent() { - Some(prefix) => fs::create_dir_all(&prefix).unwrap(), - None => (), - } - - // 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)) + Ok(Json(Response { uploaded: true })) } diff --git a/server/src/middleware/jwt.rs b/server/src/middleware/jwt.rs index 5871dae..e524642 100644 --- a/server/src/middleware/jwt.rs +++ b/server/src/middleware/jwt.rs @@ -1,4 +1,3 @@ -use axum::Extension; use homedisk_database::{Database, User}; use homedisk_types::errors::{AuthError, ServerError}; use rust_utilities::crypto::jsonwebtoken::{Claims, Token}; @@ -12,7 +11,7 @@ pub fn create_token(user: &User, secret: &[u8], expires: i64) -> Result, user_id: String) -> Result { +pub async fn find_user(db: Database, user_id: String) -> Result { match db.find_user_by_id(user_id).await { Ok(user) => Ok(user), Err(err) => match err { diff --git a/types/src/database/user.rs b/types/src/database/user.rs index 73aae9a..387b2ab 100644 --- a/types/src/database/user.rs +++ b/types/src/database/user.rs @@ -39,6 +39,26 @@ impl User { password, } } + + /// User directory + /// function returns the directory where the user file is located + /// e.g. + /// ``` + /// use homedisk_types::database::User; + /// + /// let user = User::new("medzik", "whatever"); + /// + /// user.user_dir("/home/homedisk"); // will return `/home/homedisk/medzik` + /// ``` + pub fn user_dir(&self, storage: &str) -> String { + let path = format!( + "{path}/{username}", + path = storage, + username = self.username, + ); + + path + } } #[cfg(test)]