server (fs): reform
This commit is contained in:
parent
bb8a42b080
commit
8bd4d6f107
|
@ -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))
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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 },
|
||||
}
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue