mirror of
https://github.com/Noratrieb/upload.files.noratrieb.dev.git
synced 2026-01-14 17:55:02 +01:00
better
This commit is contained in:
parent
27c8420d61
commit
5cf3f69553
3 changed files with 79 additions and 78 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -71,6 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core",
|
"axum-core",
|
||||||
|
"axum-macros",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
|
|
@ -115,6 +116,17 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-macros"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.75"
|
version = "0.3.75"
|
||||||
|
|
@ -1670,6 +1682,7 @@ dependencies = [
|
||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1792,6 +1805,8 @@ dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"subtle",
|
"subtle",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -8,13 +8,7 @@ opt-level = "s"
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.4", default-features = false, features = [
|
axum = { version = "0.8.4", default-features = false, features = ["http2", "macros", "multipart", "tokio", "tower-log", "tracing"] }
|
||||||
"http2",
|
|
||||||
"multipart",
|
|
||||||
"tokio",
|
|
||||||
"tower-log",
|
|
||||||
"tracing",
|
|
||||||
] }
|
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bs58 = "0.5.1"
|
bs58 = "0.5.1"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
|
|
@ -22,5 +16,7 @@ object_store = { version = "0.12.3", default-features = false, features = ["aws"
|
||||||
rand_core = { version = "0.9.3", features = ["os_rng"] }
|
rand_core = { version = "0.9.3", features = ["os_rng"] }
|
||||||
subtle = { version = "2.6.1", default-features = false }
|
subtle = { version = "2.6.1", default-features = false }
|
||||||
tokio = { version = "1.47.1", features = ["macros", "rt", "net"] }
|
tokio = { version = "1.47.1", features = ["macros", "rt", "net"] }
|
||||||
|
tower = "0.5.2"
|
||||||
|
tower-http = { version = "0.6.6", features = ["trace"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
|
|
||||||
88
src/main.rs
88
src/main.rs
|
|
@ -1,7 +1,8 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Bytes,
|
body::Bytes,
|
||||||
extract::{DefaultBodyLimit, FromRequestParts, Multipart, State},
|
extract::{DefaultBodyLimit, Multipart, Request, State},
|
||||||
http::{header, StatusCode},
|
http::{header, StatusCode},
|
||||||
|
middleware::Next,
|
||||||
response::{Html, IntoResponse, Redirect, Response},
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
|
|
@ -11,6 +12,7 @@ use color_eyre::eyre::{self, bail, Context};
|
||||||
use color_eyre::{eyre::OptionExt, Result};
|
use color_eyre::{eyre::OptionExt, Result};
|
||||||
use object_store::ObjectStore;
|
use object_store::ObjectStore;
|
||||||
use rand_core::TryRngCore;
|
use rand_core::TryRngCore;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
use tracing::{error, info, level_filters::LevelFilter};
|
use tracing::{error, info, level_filters::LevelFilter};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
|
@ -48,16 +50,23 @@ async fn main() -> Result<()> {
|
||||||
.build()
|
.build()
|
||||||
.wrap_err("failed to build client")?;
|
.wrap_err("failed to build client")?;
|
||||||
|
|
||||||
let app = Router::new()
|
let state = Config {
|
||||||
.route("/", get(index))
|
|
||||||
.route("/", post(upload))
|
|
||||||
.with_state(Config {
|
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
s3_client,
|
s3_client,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.route("/", post(upload))
|
||||||
|
.with_state(state.clone())
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||||
// raise limit to 100MB
|
// raise limit to 100MB
|
||||||
.layer(DefaultBodyLimit::max(100_000_000));
|
.layer(DefaultBodyLimit::max(100_000_000))
|
||||||
|
.layer(axum::middleware::from_fn_with_state(state, auth_middleware)),
|
||||||
|
);
|
||||||
|
|
||||||
let addr = "0.0.0.0:3050";
|
let addr = "0.0.0.0:3050";
|
||||||
let listener = tokio::net::TcpListener::bind(addr)
|
let listener = tokio::net::TcpListener::bind(addr)
|
||||||
|
|
@ -68,23 +77,11 @@ async fn main() -> Result<()> {
|
||||||
axum::serve(listener, app).await.wrap_err("failed to serve")
|
axum::serve(listener, app).await.wrap_err("failed to serve")
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: use middleware
|
async fn index() -> impl IntoResponse {
|
||||||
async fn index(_: Auth) -> impl IntoResponse {
|
|
||||||
Html(include_str!("../index.html"))
|
Html(include_str!("../index.html"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upload(
|
async fn upload(State(config): State<Config>, multipart: Multipart) -> Result<Response, Response> {
|
||||||
auth: Auth,
|
|
||||||
State(config): State<Config>,
|
|
||||||
multipart: Multipart,
|
|
||||||
) -> Result<Response, Response> {
|
|
||||||
if auth.username != config.username {
|
|
||||||
return Err(reject_auth("invalid username"));
|
|
||||||
}
|
|
||||||
if subtle::ConstantTimeEq::ct_ne(auth.password.as_bytes(), config.password.as_bytes()).into() {
|
|
||||||
return Err(reject_auth("invalid password"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let req = parse_req(multipart).await.map_err(|err| {
|
let req = parse_req(multipart).await.map_err(|err| {
|
||||||
info!(?err, "Bad request for upload");
|
info!(?err, "Bad request for upload");
|
||||||
(StatusCode::BAD_REQUEST, err.to_string()).into_response()
|
(StatusCode::BAD_REQUEST, err.to_string()).into_response()
|
||||||
|
|
@ -203,31 +200,16 @@ async fn parse_req(mut multipart: Multipart) -> Result<UploadRequest> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Auth {
|
#[axum::debug_middleware]
|
||||||
username: String,
|
async fn auth_middleware(State(config): State<Config>, request: Request, next: Next) -> Response {
|
||||||
password: String,
|
match check_auth(config, request).await {
|
||||||
|
Ok(request) => next.run(request).await,
|
||||||
|
Err(err) => err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reject_auth(reason: &str) -> Response {
|
async fn check_auth(config: Config, request: Request) -> Result<Request, Response> {
|
||||||
info!("Rejecting request authentication due to {reason}");
|
let Some(header) = request.headers().get(header::AUTHORIZATION) else {
|
||||||
(
|
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
[(
|
|
||||||
header::WWW_AUTHENTICATE,
|
|
||||||
"Basic realm=\"upload.files.noratrieb.dev\"",
|
|
||||||
)],
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRequestParts<Config> for Auth {
|
|
||||||
type Rejection = Response;
|
|
||||||
|
|
||||||
async fn from_request_parts(
|
|
||||||
parts: &mut axum::http::request::Parts,
|
|
||||||
config: &Config,
|
|
||||||
) -> Result<Self, Self::Rejection> {
|
|
||||||
let Some(header) = parts.headers.get(header::AUTHORIZATION) else {
|
|
||||||
return Err(reject_auth("missing authorization header"));
|
return Err(reject_auth("missing authorization header"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -259,9 +241,17 @@ impl FromRequestParts<Config> for Auth {
|
||||||
return Err(reject_auth("invalid password"));
|
return Err(reject_auth("invalid password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Auth {
|
Ok(request)
|
||||||
username: username.to_owned(),
|
}
|
||||||
password: password.to_owned(),
|
|
||||||
})
|
fn reject_auth(reason: &str) -> Response {
|
||||||
}
|
info!("Rejecting request authentication due to {reason}");
|
||||||
|
(
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
[(
|
||||||
|
header::WWW_AUTHENTICATE,
|
||||||
|
"Basic realm=\"upload.files.noratrieb.dev\"",
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue