mirror of
https://github.com/MedzikUser/HomeDisk.git
synced 2024-08-14 21:46:53 +00:00
server (fs): reform
This commit is contained in:
parent
bb8a42b080
commit
8bd4d6f107
8 changed files with 126 additions and 136 deletions
|
@ -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))
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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 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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in a new issue