Skip to content

Commit 3bc3800

Browse files
committed
refactor secret
1 parent a9f987f commit 3bc3800

File tree

5 files changed

+59
-28
lines changed

5 files changed

+59
-28
lines changed

api/src/handler/file.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ pub async fn upload(
2828
multipart: Multipart,
2929
) -> ApiResult<Json<UploadResponse>> {
3030
query.validate()?;
31-
let auth = crate::util::http::parse_basic_auth(&headers)?;
32-
let (path, expire_time) = service::file::store(&state, &query, auth, multipart).await?;
31+
let secret = crate::util::http::parse_basic_auth(&headers)?;
32+
let (path, expire_time) = service::file::store(&state, &query, secret, multipart).await?;
3333
let url = path.url(&state.config.domain);
3434
let qrcode = crate::util::qrcode::encode(&url)?;
3535
Ok(Json(UploadResponse {
@@ -44,8 +44,8 @@ pub async fn download(
4444
Path((code, file_name)): Path<(String, String)>,
4545
req: Request<Body>,
4646
) -> ApiResult<Response<ServeFileSystemResponseBody>> {
47-
let auth = crate::util::http::parse_basic_auth(req.headers())?;
48-
let file = service::file::fetch(&state, &code, &file_name, auth).await?;
47+
let secret = crate::util::http::parse_basic_auth(req.headers())?;
48+
let file = service::file::fetch(&state, &code, &file_name, secret).await?;
4949
Ok(file.oneshot(req).await.unwrap())
5050
}
5151

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

@@ -64,8 +64,8 @@ pub async fn delete(
6464
Path((code, file_name)): Path<(String, String)>,
6565
headers: HeaderMap,
6666
) -> ApiResult<Json<MessageResponse>> {
67-
let auth = crate::util::http::parse_basic_auth(&headers)?;
68-
service::file::delete(&state, &code, &file_name, auth).await?;
67+
let secret = crate::util::http::parse_basic_auth(&headers)?;
68+
service::file::delete(&state, &code, &file_name, secret).await?;
6969
Ok(Json(MessageResponse {
7070
message: "OK".to_string(),
7171
}))

api/src/service/file.rs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use crate::error::{ApiError, ApiResult, ToApiResult};
2+
use crate::util::secret::Secret;
3+
use anyhow::anyhow;
24
use axum::extract::multipart::Field;
35
use axum::extract::Multipart;
46
use chrono::{DateTime, Utc};
@@ -17,10 +19,10 @@ use crate::server::ApiState;
1719
pub async fn store(
1820
state: &ApiState,
1921
query: &UploadParamQuery,
20-
auth: Option<String>,
22+
secret: Option<Secret>,
2123
mut multipart: Multipart,
2224
) -> ApiResult<(FilePath, DateTime<Utc>)> {
23-
let auth = hash(auth)?;
25+
let auth = secret.map(|s| s.hash()).transpose()?;
2426
let expire_secs = query
2527
.expire_time
2628
.unwrap_or(state.config.default_expire_secs) as i64;
@@ -89,7 +91,7 @@ pub async fn info(
8991
state: &ApiState,
9092
code: &str,
9193
file_name: &str,
92-
auth: Option<String>,
94+
auth: Option<Secret>,
9395
) -> ApiResult<MetaDataFile> {
9496
let path = FilePath {
9597
code: code.to_string(),
@@ -110,7 +112,7 @@ pub async fn fetch(
110112
state: &ApiState,
111113
code: &str,
112114
file_name: &str,
113-
auth: Option<String>,
115+
secret: Option<Secret>,
114116
) -> ApiResult<ServeFile> {
115117
let path = FilePath {
116118
code: code.to_string(),
@@ -123,23 +125,23 @@ pub async fn fetch(
123125
return Err(ApiError::NotFound(format!("{} not found", path.url_path())));
124126
}
125127
}
126-
authenticate(auth, &meta.auth)?;
128+
authenticate(secret, &meta.auth)?;
127129
read_file(&state.config.fs.base_dir.join(&path.url_path())).await
128130
}
129131

130132
pub async fn delete(
131133
state: &ApiState,
132134
code: &str,
133135
file_name: &str,
134-
auth: Option<String>,
136+
secret: Option<Secret>,
135137
) -> ApiResult<()> {
136138
let path = FilePath {
137139
code: code.to_string(),
138140
file_name: file_name.to_string(),
139141
};
140142
if let Some(meta) = state.db.fetch(&path)? {
141143
if meta.delete_manually {
142-
authenticate(auth, &meta.auth)?;
144+
authenticate(secret, &meta.auth)?;
143145
let file_path = path.fs_path(&state.config.fs.base_dir);
144146
tokio::fs::remove_file(file_path).await?;
145147
state.db.delete(path).await?;
@@ -158,18 +160,24 @@ pub async fn read_file(file_path: &PathBuf) -> ApiResult<ServeFile> {
158160
Ok(ServeFile::new(file_path))
159161
}
160162

161-
pub fn authenticate(auth: Option<String>, hash: &Option<String>) -> ApiResult<()> {
163+
pub fn authenticate(secret: Option<Secret>, hash: &Option<String>) -> ApiResult<()> {
162164
if let Some(hash) = hash {
163-
if !matches!(
164-
auth.map(|p| crate::util::hash::argon_verify(p, hash)),
165-
Some(Ok(()))
166-
) {
167-
return Err(ApiError::PermissionDenied(
168-
"user and password is invalid".to_string(),
169-
));
165+
match secret.map(|s| s.check(hash)) {
166+
Some(Ok(_)) => return Ok(()),
167+
Some(Err(e)) if e == argon2::password_hash::Error::Password => Err(
168+
ApiError::PermissionDenied("Secret token is invalid".to_string()),
169+
),
170+
Some(Err(e)) => Err(ApiError::Unknown(anyhow!(
171+
"Unexpected error happened: {}",
172+
e
173+
))),
174+
None => Err(ApiError::PermissionDenied(
175+
"Authorization header should be set".to_string(),
176+
)),
170177
}
178+
} else {
179+
Ok(())
171180
}
172-
Ok(())
173181
}
174182

175183
pub fn hash(auth: Option<String>) -> ApiResult<Option<String>> {

api/src/util/http.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
use hyper::HeaderMap;
22

3-
use crate::error::{invalid_input_error, ApiResult};
3+
use crate::error::{invalid_input_error, ApiError, ApiResult};
44

5-
pub fn parse_basic_auth(headers: &HeaderMap) -> ApiResult<Option<String>> {
5+
use super::secret::Secret;
6+
7+
pub fn parse_basic_auth(headers: &HeaderMap) -> ApiResult<Option<Secret>> {
68
if let Some(value) = headers.get("Authorization") {
79
let bytes = &value.as_bytes()["Basic ".len()..];
810
if let Some(non_space_pos) = bytes.iter().position(|b| *b != b' ') {
911
let bytes = &bytes[non_space_pos..];
1012
let value = String::from_utf8(bytes.to_vec())
1113
.map_err(|_e| invalid_input_error("Authorization", "invalid auth header"))?;
12-
Ok(Some(value))
14+
Ok(Some(Secret::new(value)))
1315
} else {
14-
Err(invalid_input_error("Authorization", "invalid auth header"))
16+
Err(invalid_input_error("Authorization", "Invalid auth header"))
1517
}
1618
} else {
1719
Ok(None)

api/src/util/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod file_name;
33
pub mod hash;
44
pub mod http;
55
pub mod qrcode;
6+
pub mod secret;
67
pub mod string;
78
pub mod test;
89
pub mod tracing;

api/src/util/secret.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use crate::error::{ApiError, ApiResult};
2+
3+
#[derive(Debug)]
4+
pub struct Secret {
5+
inner: String,
6+
}
7+
8+
impl Secret {
9+
pub fn new(secret: String) -> Self {
10+
Self { inner: secret }
11+
}
12+
13+
pub fn check(&self, hash: &str) -> Result<(), argon2::password_hash::Error> {
14+
crate::util::hash::argon_verify(&self.inner, hash)
15+
}
16+
17+
pub fn hash(&self) -> ApiResult<String> {
18+
crate::util::hash::argon_hash(&self.inner).map_err(|e| ApiError::HashError(e.to_string()))
19+
}
20+
}

0 commit comments

Comments
 (0)