mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2025-01-05 01:01:24 +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]]
|
[[package]]
|
||||||
name = "pict-rs"
|
name = "pict-rs"
|
||||||
version = "0.2.0-alpha.2"
|
version = "0.2.0-alpha.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-form-data",
|
"actix-form-data",
|
||||||
"actix-fs",
|
"actix-fs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pict-rs"
|
name = "pict-rs"
|
||||||
description = "A simple image hosting service"
|
description = "A simple image hosting service"
|
||||||
version = "0.2.0-alpha.2"
|
version = "0.2.0-alpha.3"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
20
README.md
20
README.md
|
@ -4,7 +4,7 @@ _a simple image hosting service_
|
||||||
## Usage
|
## Usage
|
||||||
### Running
|
### Running
|
||||||
```
|
```
|
||||||
pict-rs 0.2.0-alpha.2
|
pict-rs 0.2.0-alpha.3
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
pict-rs [FLAGS] [OPTIONS] --path <path>
|
pict-rs [FLAGS] [OPTIONS] --path <path>
|
||||||
|
@ -17,6 +17,8 @@ FLAGS:
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-a, --addr <addr> The address and port the server binds to. [env: PICTRS_ADDR=] [default:
|
-a, --addr <addr> The address and port the server binds to. [env: PICTRS_ADDR=] [default:
|
||||||
0.0.0.0:8080]
|
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',
|
-f, --format <format> An optional image format to convert all uploaded files into, supports 'jpg',
|
||||||
'png', and 'webp' [env: PICTRS_FORMAT=]
|
'png', and 'webp' [env: PICTRS_FORMAT=]
|
||||||
-m, --max-file-size <max-file-size> Specify the maximum allowed uploaded file size (in Megabytes) [env:
|
-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"
|
"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
|
- `GET /image/download?url=...` Download an image from a remote server, returning the same JSON
|
||||||
payload as the `POST` endpoint
|
payload as the `POST` endpoint
|
||||||
- `GET /image/original/{file}` for getting a full-resolution image. `file` here is the `file` key from the
|
- `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
|
GET /image/process.jpg?src=asdf.png&thumbnail=256&blur=3.0
|
||||||
```
|
```
|
||||||
which would create a 256x256px JPEG thumbnail and blur it
|
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,
|
- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to
|
||||||
where `delete_token` and `file` are from the `/image` endpoint's JSON
|
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
|
## 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.
|
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"
|
default_value = "40"
|
||||||
)]
|
)]
|
||||||
max_file_size: usize,
|
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 {
|
impl Config {
|
||||||
|
@ -78,6 +85,10 @@ impl Config {
|
||||||
pub(crate) fn max_file_size(&self) -> usize {
|
pub(crate) fn max_file_size(&self) -> usize {
|
||||||
self.max_file_size
|
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)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -24,7 +24,7 @@ mod validate;
|
||||||
use self::{
|
use self::{
|
||||||
config::Config,
|
config::Config,
|
||||||
error::UploadError,
|
error::UploadError,
|
||||||
middleware::Tracing,
|
middleware::{Internal, Tracing},
|
||||||
processor::process_image,
|
processor::process_image,
|
||||||
upload_manager::UploadManager,
|
upload_manager::UploadManager,
|
||||||
validate::{image_webp, video_mp4},
|
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("/process.{ext}").route(web::get().to(process))),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/import")
|
web::scope("/internal")
|
||||||
.wrap(import_form.clone())
|
.wrap(Internal(CONFIG.api_key().map(|s| s.to_owned())))
|
||||||
.route(web::post().to(upload)),
|
.service(
|
||||||
|
web::resource("/import")
|
||||||
|
.wrap(import_form.clone())
|
||||||
|
.route(web::post().to(upload)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.bind(CONFIG.bind_address())?
|
.bind(CONFIG.bind_address())?
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use actix_web::dev::{Service, Transform};
|
use actix_web::{
|
||||||
use futures::future::{ok, Ready};
|
dev::{Service, ServiceRequest, Transform},
|
||||||
|
http::StatusCode,
|
||||||
|
HttpResponse, ResponseError,
|
||||||
|
};
|
||||||
|
use futures::future::{ok, LocalBoxFuture, Ready};
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use tracing_futures::{Instrument, Instrumented};
|
use tracing_futures::{Instrument, Instrumented};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -10,6 +14,22 @@ pub(crate) struct TracingMiddleware<S> {
|
||||||
inner: 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
|
impl<S> Transform<S> for Tracing
|
||||||
where
|
where
|
||||||
S: Service,
|
S: Service,
|
||||||
|
@ -49,3 +69,47 @@ where
|
||||||
.instrument(tracing::info_span!("request", ?uuid))
|
.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