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(
"the `path` must not contain `..`".to_string(),
)));
}
let response = match db.find_user_by_id(token.claims.sub).await { // search for a user by UUID from a token
Ok(res) => { let user = find_user(db, token.claims.sub).await?;
let user_path = format!(
"{path}/{username}/{request_path}", // directory where the file will be placed
path = config.storage.path, let path = format!(
username = res.username, "{user_dir}/{req_dir}",
request_path = request.path, user_dir = user.user_dir(&config.storage.path),
req_dir = request.path
); );
let paths = fs::read_dir(&user_path) let paths = fs::read_dir(&path)
.map_err(|err| ServerError::FsError(FsError::ReadDir(err.to_string())))?; .map_err(|err| ServerError::FsError(FsError::ReadDir(err.to_string())))?;
let mut files = vec![]; let mut files = vec![];
let mut dirs = vec![]; let mut dirs = vec![];
for path in paths { for f in paths {
let path = path let f = f.map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?;
.map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?; let metadata = f
let metadata = path
.metadata() .metadata()
.map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?; .map_err(|err| ServerError::FsError(FsError::UnknowError(err.to_string())))?;
let name = path.path().display().to_string().replace(&user_path, ""); let name = f.path().display().to_string().replace(&path, "");
let filesize = Byte::from_bytes(metadata.len().into()).get_appropriate_unit(true); let file_size = Byte::from_bytes(metadata.len().into()).get_appropriate_unit(true);
if metadata.is_dir() { if metadata.is_dir() {
dirs.push(name) dirs.push(name)
} else { } else {
files.push(FileInfo { files.push(FileInfo {
name, name,
size: filesize.to_string(), size: file_size.to_string(),
}) })
} }
} }
Response { files, dirs } Ok(Json(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,46 +1,43 @@
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?;
)));
}
let response = match db.find_user_by_id(token.claims.sub).await {
Ok(res) => {
// get file content // get file content
let content = base64::decode(request.content) let content = base64::decode(request.content)
.map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?; .map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?;
let path = format!( // directory where the file will be placed
"{path}/{username}/{filepath}", let dir = format!(
path = config.storage.path, "{user_dir}/{req_dir}",
username = res.username, user_dir = user.user_dir(&config.storage.path),
filepath = request.path req_dir = request.path
); );
let path = Path::new(&path); let path = Path::new(&dir);
// check if the file currently exists to avoid overwriting it // check if the file currently exists to avoid overwriting it
if path.exists() { if path.exists() {
@ -59,18 +56,5 @@ pub async fn handle(
fs::write(&path, &content) fs::write(&path, &content)
.map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?; .map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?;
Response { uploaded: true } Ok(Json(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)]