http (fs): add file upload

This commit is contained in:
MedzikUser 2022-04-24 21:31:50 +02:00
parent 1c1074abaf
commit 3c36122b86
No known key found for this signature in database
GPG Key ID: A5FAC1E185C112DB
14 changed files with 129 additions and 12 deletions

1
Cargo.lock generated
View File

@ -622,6 +622,7 @@ version = "0.0.0"
dependencies = [
"axum",
"axum-auth",
"base64",
"homedisk-database",
"homedisk-types",
"hyper",

View File

@ -11,3 +11,7 @@ cors = [
[jwt]
secret = "secret key used to sign tokens" # secret used to sign tokens
expires = 24 # validity of token in hours
# Storage
[storage]
path = "/home/homedisk" # the folder where the user files will be stored

View File

@ -180,10 +180,7 @@ mod tests {
let user = User::new("medzik", "Qwerty1234!");
let res = db
.find_user_by_id(user.id)
.await
.expect("find user");
let res = db.find_user_by_id(user.id).await.expect("find user");
assert_eq!(res.password, user.password)
}
@ -196,10 +193,7 @@ mod tests {
let other_user = User::new("other_user", "secretpassphrase123!");
let err = db
.find_user_by_id(other_user.id)
.await
.unwrap_err();
let err = db.find_user_by_id(other_user.id).await.unwrap_err();
assert_eq!(err.to_string(), "user not found")
}

View File

@ -15,3 +15,4 @@ homedisk-database = { path = "../database" }
homedisk-types = { path = "../types", features = ["axum"] }
axum-auth = "0.2.0"
jsonwebtoken = "8.1.0"
base64 = "0.13.0"

7
server/src/fs/mod.rs Normal file
View File

@ -0,0 +1,7 @@
pub mod upload;
pub fn app() -> axum::Router {
use axum::routing::post;
axum::Router::new().route("/upload", post(upload::handle))
}

62
server/src/fs/upload.rs Normal file
View File

@ -0,0 +1,62 @@
use std::{fs, path::Path};
use axum::{extract::rejection::JsonRejection, Extension, Json};
use axum_auth::AuthBearer;
use homedisk_database::{Database, Error};
use homedisk_types::{
config::types::Config,
errors::{AuthError, FsError, ServerError},
fs::upload::{Request, Response},
};
use crate::middleware::{validate_json, validate_jwt};
pub async fn handle(
db: Extension<Database>,
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)?;
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())))?;
// check if file already exists
if Path::new(&request.path).exists() {
return Err(ServerError::FsError(FsError::FileAlreadyExists));
}
let folder_path = format!(
"{path}/{username}",
path = config.storage.path,
username = res.username,
);
let path = format!("{folder_path}/{filepath}", filepath = request.path);
// create user directory
fs::create_dir_all(&folder_path).unwrap();
// 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,5 @@
pub mod auth;
pub mod fs;
pub mod middleware;
mod error;
@ -25,6 +26,7 @@ pub async fn serve(
let app = Router::new()
.route("/health-check", get(health_check))
.nest("/auth", auth::app())
.nest("/fs", fs::app())
.layer(CorsLayer::new().allow_origin(Origin::list(origins)))
.layer(Extension(db))
.layer(Extension(config));

View File

@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
pub struct Config {
pub http: ConfigHTTP,
pub jwt: ConfigJWT,
pub storage: ConfigStorage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -18,3 +19,8 @@ pub struct ConfigJWT {
pub secret: String,
pub expires: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigStorage {
pub path: String,
}

View File

@ -1,3 +1,3 @@
mod user;
pub use {user::*};
pub use user::*;

16
types/src/errors/fs.rs Normal file
View File

@ -0,0 +1,16 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
pub enum Error {
#[error("file already exists")]
FileAlreadyExists,
#[error("file already exists")]
WriteFile(String),
#[error("base64 - {0}")]
Base64(String),
#[error("unknow error")]
UnknowError(String),
}

View File

@ -1,6 +1,8 @@
mod auth;
mod fs;
pub use auth::Error as AuthError;
pub use fs::Error as FsError;
use serde::{Deserialize, Serialize};
@ -10,6 +12,9 @@ pub enum ServerError {
#[error("auth error: {0}")]
AuthError(#[from] AuthError),
#[error("fs error: {0}")]
FsError(#[from] FsError),
#[error("too may requests, please slow down")]
TooManyRequests,
@ -35,15 +40,20 @@ impl axum::response::IntoResponse for ServerError {
use axum::http::StatusCode;
let status = match self {
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::AuthError(ref err) => match err {
AuthError::UserNotFound => StatusCode::BAD_REQUEST,
AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE,
AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR,
AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
AuthError::InvalidToken => StatusCode::BAD_REQUEST,
AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
},
Self::FsError(ref err) => match err {
FsError::FileAlreadyExists => StatusCode::BAD_REQUEST,
FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
FsError::Base64(_) => StatusCode::BAD_REQUEST,
FsError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
},
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::MissingJsonContentType => StatusCode::BAD_REQUEST,
Self::JsonDataError => StatusCode::BAD_REQUEST,
Self::JsonSyntaxError => StatusCode::BAD_REQUEST,

1
types/src/fs/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod upload;

12
types/src/fs/upload.rs Normal file
View File

@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Request {
pub content: String,
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Response {
pub uploaded: bool,
}

View File

@ -5,3 +5,4 @@ pub mod macros;
#[cfg(feature = "database")]
pub mod database;
pub mod fs;