mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2025-01-02 16:01:25 +00:00
Make 'import' a protected method
This commit is contained in:
parent
de3e04a411
commit
fd809e4a0b
6 changed files with 101 additions and 14 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1463,7 +1463,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||
|
||||
[[package]]
|
||||
name = "pict-rs"
|
||||
version = "0.2.0-alpha.2"
|
||||
version = "0.2.0-alpha.3"
|
||||
dependencies = [
|
||||
"actix-form-data",
|
||||
"actix-fs",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "pict-rs"
|
||||
description = "A simple image hosting service"
|
||||
version = "0.2.0-alpha.2"
|
||||
version = "0.2.0-alpha.3"
|
||||
authors = ["asonix <asonix@asonix.dog>"]
|
||||
license = "AGPL-3.0"
|
||||
readme = "README.md"
|
||||
|
|
20
README.md
20
README.md
|
@ -4,7 +4,7 @@ _a simple image hosting service_
|
|||
## Usage
|
||||
### Running
|
||||
```
|
||||
pict-rs 0.2.0-alpha.2
|
||||
pict-rs 0.2.0-alpha.3
|
||||
|
||||
USAGE:
|
||||
pict-rs [FLAGS] [OPTIONS] --path <path>
|
||||
|
@ -17,6 +17,8 @@ FLAGS:
|
|||
OPTIONS:
|
||||
-a, --addr <addr> The address and port the server binds to. [env: PICTRS_ADDR=] [default:
|
||||
0.0.0.0:8080]
|
||||
--api-key <api-key> An optional string to be checked on requests to privileged endpoints [env:
|
||||
PICTRS_API_KEY=]
|
||||
-f, --format <format> An optional image format to convert all uploaded files into, supports 'jpg',
|
||||
'png', and 'webp' [env: PICTRS_FORMAT=]
|
||||
-m, --max-file-size <max-file-size> Specify the maximum allowed uploaded file size (in Megabytes) [env:
|
||||
|
@ -99,9 +101,6 @@ pict-rs offers four endpoints:
|
|||
"msg": "ok"
|
||||
}
|
||||
```
|
||||
- `POST /import` for uploading an image while preserving the filename. This should not be exposed to
|
||||
the public internet, as it can cause naming conflicts with saved files. The upload format and
|
||||
response format are the same as the `POST /image` endpoint.
|
||||
- `GET /image/download?url=...` Download an image from a remote server, returning the same JSON
|
||||
payload as the `POST` endpoint
|
||||
- `GET /image/original/{file}` for getting a full-resolution image. `file` here is the `file` key from the
|
||||
|
@ -119,8 +118,17 @@ pict-rs offers four endpoints:
|
|||
GET /image/process.jpg?src=asdf.png&thumbnail=256&blur=3.0
|
||||
```
|
||||
which would create a 256x256px JPEG thumbnail and blur it
|
||||
- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to delete a file,
|
||||
where `delete_token` and `file` are from the `/image` endpoint's JSON
|
||||
- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to
|
||||
delete a file, where `delete_token` and `file` are from the `/image` endpoint's JSON
|
||||
- `POST /internal/import` for uploading an image while preserving the filename. This should not be
|
||||
exposed to the public internet, as it can cause naming conflicts with saved files. The upload
|
||||
format and response format are the same as the `POST /image` endpoint.
|
||||
|
||||
This endpoint also requires authentication via the `X-Api-Token` header, and is disabled unless
|
||||
the `--api-key` option is passed to the binary or the PICTRS_API_KEY environment variable is
|
||||
set.
|
||||
|
||||
A secure API key can be generated by any password generator.
|
||||
|
||||
## Contributing
|
||||
Feel free to open issues for anything you find an issue with. Please note that any contributed code will be licensed under the AGPLv3.
|
||||
|
|
|
@ -50,6 +50,13 @@ pub(crate) struct Config {
|
|||
default_value = "40"
|
||||
)]
|
||||
max_file_size: usize,
|
||||
|
||||
#[structopt(
|
||||
long,
|
||||
env = "PICTRS_API_KEY",
|
||||
help = "An optional string to be checked on requests to privileged endpoints"
|
||||
)]
|
||||
api_key: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -78,6 +85,10 @@ impl Config {
|
|||
pub(crate) fn max_file_size(&self) -> usize {
|
||||
self.max_file_size
|
||||
}
|
||||
|
||||
pub(crate) fn api_key(&self) -> Option<&str> {
|
||||
self.api_key.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -24,7 +24,7 @@ mod validate;
|
|||
use self::{
|
||||
config::Config,
|
||||
error::UploadError,
|
||||
middleware::Tracing,
|
||||
middleware::{Internal, Tracing},
|
||||
processor::process_image,
|
||||
upload_manager::UploadManager,
|
||||
validate::{image_webp, video_mp4},
|
||||
|
@ -502,9 +502,13 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
.service(web::resource("/process.{ext}").route(web::get().to(process))),
|
||||
)
|
||||
.service(
|
||||
web::resource("/import")
|
||||
.wrap(import_form.clone())
|
||||
.route(web::post().to(upload)),
|
||||
web::scope("/internal")
|
||||
.wrap(Internal(CONFIG.api_key().map(|s| s.to_owned())))
|
||||
.service(
|
||||
web::resource("/import")
|
||||
.wrap(import_form.clone())
|
||||
.route(web::post().to(upload)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.bind(CONFIG.bind_address())?
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use actix_web::dev::{Service, Transform};
|
||||
use futures::future::{ok, Ready};
|
||||
use actix_web::{
|
||||
dev::{Service, ServiceRequest, Transform},
|
||||
http::StatusCode,
|
||||
HttpResponse, ResponseError,
|
||||
};
|
||||
use futures::future::{ok, LocalBoxFuture, Ready};
|
||||
use std::task::{Context, Poll};
|
||||
use tracing_futures::{Instrument, Instrumented};
|
||||
use uuid::Uuid;
|
||||
|
@ -10,6 +14,22 @@ pub(crate) struct TracingMiddleware<S> {
|
|||
inner: S,
|
||||
}
|
||||
|
||||
pub(crate) struct Internal(pub(crate) Option<String>);
|
||||
pub(crate) struct InternalMiddleware<S>(Option<String>, S);
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[error("Invalid API Key")]
|
||||
struct ApiError;
|
||||
|
||||
impl ResponseError for ApiError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code()).json(serde_json::json!({ "msg": self.to_string() }))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Transform<S> for Tracing
|
||||
where
|
||||
S: Service,
|
||||
|
@ -49,3 +69,47 @@ where
|
|||
.instrument(tracing::info_span!("request", ?uuid))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Transform<S> for Internal
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
type Transform = InternalMiddleware<S>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(InternalMiddleware(self.0.clone(), service))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Service for InternalMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<S::Response, S::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.1.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: S::Request) -> Self::Future {
|
||||
if let Some(value) = req.headers().get("x-api-token") {
|
||||
if value.to_str().is_ok() && value.to_str().ok() == self.0.as_ref().map(|s| s.as_str())
|
||||
{
|
||||
let fut = self.1.call(req);
|
||||
return Box::pin(async move { fut.await });
|
||||
}
|
||||
}
|
||||
|
||||
Box::pin(async move { Err(ApiError.into()) })
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue