Skip to content

Commit

Permalink
refactor secret
Browse files Browse the repository at this point in the history
  • Loading branch information
robatipoor committed Jan 18, 2024
1 parent a9f987f commit 3bc3800
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 28 deletions.
16 changes: 8 additions & 8 deletions api/src/handler/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub async fn upload(
multipart: Multipart,
) -> ApiResult<Json<UploadResponse>> {
query.validate()?;
let auth = crate::util::http::parse_basic_auth(&headers)?;
let (path, expire_time) = service::file::store(&state, &query, auth, multipart).await?;
let secret = crate::util::http::parse_basic_auth(&headers)?;
let (path, expire_time) = service::file::store(&state, &query, secret, multipart).await?;
let url = path.url(&state.config.domain);
let qrcode = crate::util::qrcode::encode(&url)?;
Ok(Json(UploadResponse {
Expand All @@ -44,8 +44,8 @@ pub async fn download(
Path((code, file_name)): Path<(String, String)>,
req: Request<Body>,
) -> ApiResult<Response<ServeFileSystemResponseBody>> {
let auth = crate::util::http::parse_basic_auth(req.headers())?;
let file = service::file::fetch(&state, &code, &file_name, auth).await?;
let secret = crate::util::http::parse_basic_auth(req.headers())?;
let file = service::file::fetch(&state, &code, &file_name, secret).await?;
Ok(file.oneshot(req).await.unwrap())
}

Expand All @@ -54,8 +54,8 @@ pub async fn info(
Path((code, file_name)): Path<(String, String)>,
headers: HeaderMap,
) -> ApiResult<Json<MetaDataFileResponse>> {
let auth = crate::util::http::parse_basic_auth(&headers)?;
let meta = service::file::info(&state, &code, &file_name, auth).await?;
let secret = crate::util::http::parse_basic_auth(&headers)?;
let meta = service::file::info(&state, &code, &file_name, secret).await?;
Ok(Json(MetaDataFileResponse::from(&meta)))
}

Expand All @@ -64,8 +64,8 @@ pub async fn delete(
Path((code, file_name)): Path<(String, String)>,
headers: HeaderMap,
) -> ApiResult<Json<MessageResponse>> {
let auth = crate::util::http::parse_basic_auth(&headers)?;
service::file::delete(&state, &code, &file_name, auth).await?;
let secret = crate::util::http::parse_basic_auth(&headers)?;
service::file::delete(&state, &code, &file_name, secret).await?;
Ok(Json(MessageResponse {
message: "OK".to_string(),
}))
Expand Down
40 changes: 24 additions & 16 deletions api/src/service/file.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::error::{ApiError, ApiResult, ToApiResult};
use crate::util::secret::Secret;
use anyhow::anyhow;
use axum::extract::multipart::Field;
use axum::extract::Multipart;
use chrono::{DateTime, Utc};
Expand All @@ -17,10 +19,10 @@ use crate::server::ApiState;
pub async fn store(
state: &ApiState,
query: &UploadParamQuery,
auth: Option<String>,
secret: Option<Secret>,
mut multipart: Multipart,
) -> ApiResult<(FilePath, DateTime<Utc>)> {
let auth = hash(auth)?;
let auth = secret.map(|s| s.hash()).transpose()?;
let expire_secs = query
.expire_time
.unwrap_or(state.config.default_expire_secs) as i64;
Expand Down Expand Up @@ -89,7 +91,7 @@ pub async fn info(
state: &ApiState,
code: &str,
file_name: &str,
auth: Option<String>,
auth: Option<Secret>,
) -> ApiResult<MetaDataFile> {
let path = FilePath {
code: code.to_string(),
Expand All @@ -110,7 +112,7 @@ pub async fn fetch(
state: &ApiState,
code: &str,
file_name: &str,
auth: Option<String>,
secret: Option<Secret>,
) -> ApiResult<ServeFile> {
let path = FilePath {
code: code.to_string(),
Expand All @@ -123,23 +125,23 @@ pub async fn fetch(
return Err(ApiError::NotFound(format!("{} not found", path.url_path())));
}
}
authenticate(auth, &meta.auth)?;
authenticate(secret, &meta.auth)?;
read_file(&state.config.fs.base_dir.join(&path.url_path())).await
}

pub async fn delete(
state: &ApiState,
code: &str,
file_name: &str,
auth: Option<String>,
secret: Option<Secret>,
) -> ApiResult<()> {
let path = FilePath {
code: code.to_string(),
file_name: file_name.to_string(),
};
if let Some(meta) = state.db.fetch(&path)? {
if meta.delete_manually {
authenticate(auth, &meta.auth)?;
authenticate(secret, &meta.auth)?;
let file_path = path.fs_path(&state.config.fs.base_dir);
tokio::fs::remove_file(file_path).await?;
state.db.delete(path).await?;
Expand All @@ -158,18 +160,24 @@ pub async fn read_file(file_path: &PathBuf) -> ApiResult<ServeFile> {
Ok(ServeFile::new(file_path))
}

pub fn authenticate(auth: Option<String>, hash: &Option<String>) -> ApiResult<()> {
pub fn authenticate(secret: Option<Secret>, hash: &Option<String>) -> ApiResult<()> {
if let Some(hash) = hash {
if !matches!(
auth.map(|p| crate::util::hash::argon_verify(p, hash)),
Some(Ok(()))
) {
return Err(ApiError::PermissionDenied(
"user and password is invalid".to_string(),
));
match secret.map(|s| s.check(hash)) {
Some(Ok(_)) => return Ok(()),
Some(Err(e)) if e == argon2::password_hash::Error::Password => Err(
ApiError::PermissionDenied("Secret token is invalid".to_string()),
),
Some(Err(e)) => Err(ApiError::Unknown(anyhow!(
"Unexpected error happened: {}",
e
))),
None => Err(ApiError::PermissionDenied(
"Authorization header should be set".to_string(),
)),
}
} else {
Ok(())
}
Ok(())
}

pub fn hash(auth: Option<String>) -> ApiResult<Option<String>> {
Expand Down
10 changes: 6 additions & 4 deletions api/src/util/http.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use hyper::HeaderMap;

use crate::error::{invalid_input_error, ApiResult};
use crate::error::{invalid_input_error, ApiError, ApiResult};

pub fn parse_basic_auth(headers: &HeaderMap) -> ApiResult<Option<String>> {
use super::secret::Secret;

pub fn parse_basic_auth(headers: &HeaderMap) -> ApiResult<Option<Secret>> {
if let Some(value) = headers.get("Authorization") {
let bytes = &value.as_bytes()["Basic ".len()..];
if let Some(non_space_pos) = bytes.iter().position(|b| *b != b' ') {
let bytes = &bytes[non_space_pos..];
let value = String::from_utf8(bytes.to_vec())
.map_err(|_e| invalid_input_error("Authorization", "invalid auth header"))?;
Ok(Some(value))
Ok(Some(Secret::new(value)))
} else {
Err(invalid_input_error("Authorization", "invalid auth header"))
Err(invalid_input_error("Authorization", "Invalid auth header"))
}
} else {
Ok(None)
Expand Down
1 change: 1 addition & 0 deletions api/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod file_name;
pub mod hash;
pub mod http;
pub mod qrcode;
pub mod secret;
pub mod string;
pub mod test;
pub mod tracing;
20 changes: 20 additions & 0 deletions api/src/util/secret.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::error::{ApiError, ApiResult};

#[derive(Debug)]
pub struct Secret {
inner: String,
}

impl Secret {
pub fn new(secret: String) -> Self {
Self { inner: secret }
}

pub fn check(&self, hash: &str) -> Result<(), argon2::password_hash::Error> {
crate::util::hash::argon_verify(&self.inner, hash)
}

pub fn hash(&self) -> ApiResult<String> {
crate::util::hash::argon_hash(&self.inner).map_err(|e| ApiError::HashError(e.to_string()))
}
}

0 comments on commit 3bc3800

Please sign in to comment.