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};
pub async fn handle(
db: Extension<Database>,
config: Extension<Config>,
Extension(db): Extension<Database>,
Extension(config): Extension<Config>,
request: Result<Json<Request>, JsonRejection>,
) -> Result<Json<Response>, ServerError> {
let request = validate_json::<Request>(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))

View File

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

View File

@ -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(())
}

View File

@ -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<Database>,
config: Extension<Config>,
Extension(db): Extension<Database>,
Extension(config): Extension<Config>,
AuthBearer(token): AuthBearer,
request: Result<Json<Request>, JsonRejection>,
) -> Result<Json<Response>, ServerError> {
let Json(request) = validate_json::<Request>(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 }))
}

View File

@ -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<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 {
Ok(user) => Ok(user),
Err(err) => match err {

View File

@ -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)]