server (fs): reform

This commit is contained in:
MedzikUser 2022-05-01 20:34:28 +02:00
parent bb8a42b080
commit 8bd4d6f107
No known key found for this signature in database
GPG Key ID: A5FAC1E185C112DB
8 changed files with 126 additions and 136 deletions

View File

@ -9,8 +9,8 @@ use homedisk_types::{
use crate::middleware::{create_token, validate_json}; use crate::middleware::{create_token, validate_json};
pub async fn handle( pub async fn handle(
db: Extension<Database>, Extension(db): Extension<Database>,
config: Extension<Config>, Extension(config): Extension<Config>,
request: Result<Json<Request>, JsonRejection>, request: Result<Json<Request>, JsonRejection>,
) -> Result<Json<Response>, ServerError> { ) -> Result<Json<Response>, ServerError> {
let request = validate_json::<Request>(request)?; let request = validate_json::<Request>(request)?;
@ -26,14 +26,14 @@ pub async fn handle(
} }
} }
Err(err) => match err { Err(err) => {
Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), return match err {
_ => { Error::UserNotFound => Err(ServerError::AuthError(AuthError::UserNotFound)),
return Err(ServerError::AuthError(AuthError::UnknowError( _ => Err(ServerError::AuthError(AuthError::UnknowError(
err.to_string(), err.to_string(),
))) ))),
} }
}, }
}; };
Ok(Json(response)) Ok(Json(response))

View File

@ -11,8 +11,8 @@ use homedisk_types::{
use crate::middleware::{create_token, validate_json}; use crate::middleware::{create_token, validate_json};
pub async fn handle( pub async fn handle(
db: Extension<Database>, Extension(db): Extension<Database>,
config: Extension<Config>, Extension(config): Extension<Config>,
request: Result<Json<Request>, JsonRejection>, request: Result<Json<Request>, JsonRejection>,
) -> Result<Json<Response>, ServerError> { ) -> Result<Json<Response>, ServerError> {
let request = validate_json::<Request>(request)?; let request = validate_json::<Request>(request)?;

View File

@ -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 },
}

View File

@ -1,81 +1,64 @@
use std::fs; use std::fs;
use crate::fs::validate_path;
use axum::{extract::rejection::JsonRejection, Extension, Json}; use axum::{extract::rejection::JsonRejection, Extension, Json};
use axum_auth::AuthBearer; use axum_auth::AuthBearer;
use byte_unit::Byte; use byte_unit::Byte;
use homedisk_database::{Database, Error}; use homedisk_database::Database;
use homedisk_types::{ use homedisk_types::{
config::types::Config, config::types::Config,
errors::{AuthError, FsError, ServerError}, errors::{FsError, ServerError},
fs::list::{FileInfo, Request, Response}, 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( pub async fn handle(
db: Extension<Database>, Extension(db): Extension<Database>,
config: Extension<Config>, Extension(config): Extension<Config>,
AuthBearer(token): AuthBearer, AuthBearer(token): AuthBearer,
request: Result<Json<Request>, JsonRejection>, request: Result<Json<Request>, JsonRejection>,
) -> Result<Json<Response>, ServerError> { ) -> Result<Json<Response>, ServerError> {
let Json(request) = validate_json::<Request>(request)?; let Json(request) = validate_json::<Request>(request)?;
let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?;
// `path` cannot contain `..` // validate the `path` can be used
// to prevent attack attempts because by using a `..` you can access the previous folder validate_path(&request.path)?;
if request.path.contains("..") {
return Err(ServerError::FsError(FsError::ReadDir( // search for a user by UUID from a token
"the `path` must not contain `..`".to_string(), 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(Json(Response { files, dirs }))
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))
} }

View File

@ -1,3 +1,5 @@
use homedisk_types::errors::{FsError, ServerError};
pub mod list; pub mod list;
pub mod upload; pub mod upload;
@ -8,3 +10,15 @@ pub fn app() -> axum::Router {
.route("/list", post(list::handle)) .route("/list", post(list::handle))
.route("/upload", post(upload::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(())
}

View File

@ -1,76 +1,60 @@
use std::{fs, path::Path}; use std::{fs, path::Path};
use crate::fs::validate_path;
use axum::{extract::rejection::JsonRejection, Extension, Json}; use axum::{extract::rejection::JsonRejection, Extension, Json};
use axum_auth::AuthBearer; use axum_auth::AuthBearer;
use homedisk_database::{Database, Error}; use homedisk_database::Database;
use homedisk_types::{ use homedisk_types::{
config::types::Config, config::types::Config,
errors::{AuthError, FsError, ServerError}, errors::{FsError, ServerError},
fs::upload::{Request, Response}, fs::upload::{Request, Response},
}; };
use crate::middleware::{validate_json, validate_jwt}; use crate::middleware::{find_user, validate_json, validate_jwt};
pub async fn handle( pub async fn handle(
db: Extension<Database>, Extension(db): Extension<Database>,
config: Extension<Config>, Extension(config): Extension<Config>,
AuthBearer(token): AuthBearer, AuthBearer(token): AuthBearer,
request: Result<Json<Request>, JsonRejection>, request: Result<Json<Request>, JsonRejection>,
) -> Result<Json<Response>, ServerError> { ) -> Result<Json<Response>, ServerError> {
let Json(request) = validate_json::<Request>(request)?; let Json(request) = validate_json::<Request>(request)?;
let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?;
// `path` cannot contain `..` // validate the `path` can be used
// to prevent attack attempts because by using a `..` you can access the previous folder validate_path(&request.path)?;
if request.path.contains("..") {
return Err(ServerError::FsError(FsError::ReadDir( // search for a user by UUID from a token
"the `path` must not contain `..`".to_string(), 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 { // create a directory where the file will be placed
Ok(res) => { // e.g. path ==> `/secret/files/images/screenshot.png`
// get file content // directories up to `/home/homedisk/{username}/secret/files/images/` will be created
let content = base64::decode(request.content) match path.parent() {
.map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?; Some(prefix) => fs::create_dir_all(&prefix).unwrap(),
None => (),
}
let path = format!( // write file
"{path}/{username}/{filepath}", fs::write(&path, &content)
path = config.storage.path, .map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?;
username = res.username,
filepath = request.path
);
let path = Path::new(&path);
// check if the file currently exists to avoid overwriting it Ok(Json(Response { uploaded: true }))
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))
} }

View File

@ -1,4 +1,3 @@
use axum::Extension;
use homedisk_database::{Database, User}; use homedisk_database::{Database, User};
use homedisk_types::errors::{AuthError, ServerError}; use homedisk_types::errors::{AuthError, ServerError};
use rust_utilities::crypto::jsonwebtoken::{Claims, Token}; use rust_utilities::crypto::jsonwebtoken::{Claims, Token};
@ -12,7 +11,7 @@ pub fn create_token(user: &User, secret: &[u8], expires: i64) -> Result<String,
} }
} }
pub async fn find_user(db: Extension<Database>, user_id: String) -> Result<User, ServerError> { pub async fn find_user(db: Database, user_id: String) -> Result<User, ServerError> {
match db.find_user_by_id(user_id).await { match db.find_user_by_id(user_id).await {
Ok(user) => Ok(user), Ok(user) => Ok(user),
Err(err) => match err { Err(err) => match err {

View File

@ -39,6 +39,26 @@ impl User {
password, 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)] #[cfg(test)]