diff --git a/api/src/endpoint/v1/error.rs b/api/src/endpoint/v1/error.rs index 5a83559..1398d89 100644 --- a/api/src/endpoint/v1/error.rs +++ b/api/src/endpoint/v1/error.rs @@ -14,6 +14,9 @@ pub struct ErrorAttributes { #[derive(Debug, Clone, thiserror::Error, UploaderError)] pub enum Error { + #[error("Invalid auth key")] + #[uploader(status_code = 403)] + Unauthorized, #[error("The uploaded image is too large")] #[uploader(status_code = 403)] ImageTooLargeError, diff --git a/api/src/endpoint/v1/image/upload.rs b/api/src/endpoint/v1/image/upload.rs index 3c6ca65..3c69113 100644 --- a/api/src/endpoint/v1/image/upload.rs +++ b/api/src/endpoint/v1/image/upload.rs @@ -1,10 +1,14 @@ +use std::convert::Infallible; + use rand::distributions::Alphanumeric; use rand::Rng; use rocket::form::{Form, FromForm}; use rocket::fs::TempFile; use rocket::http::ContentType; +use rocket::outcome::Outcome; +use rocket::request::{self, FromRequest}; use rocket::serde::json::Json; -use rocket::{post, Responder, State}; +use rocket::{post, Request, Responder, State}; use serde::Serialize; use uuid::Uuid; @@ -21,6 +25,8 @@ pub struct ImageData<'r> { image: TempFile<'r>, } +pub struct AuthToken(String); + #[derive(Responder)] #[response(status = 200, content_type = "json")] pub struct UploadResponse { @@ -41,18 +47,38 @@ impl UploadResponse { } } +#[rocket::async_trait] +impl<'r> FromRequest<'r> for AuthToken { + type Error = Infallible; + + async fn from_request(request: &'r Request<'_>) -> request::Outcome { + let token = request.headers().get_one("Authorization"); + match token { + Some(token) => Outcome::Success(AuthToken(token.to_string())), + None => Outcome::Success(AuthToken("".into())), + } + } +} + #[post("/image/upload", data = "")] pub async fn upload( image_data: Form>, bucket: BucketGuard, database: PostgresDb, config: &State, + token: AuthToken, ) -> UploaderResult { let mut transaction = database.begin().await.map_err(|_| Error::DatabaseError)?; let bucket_id = Uuid::new_v4().to_string().replace("-", ""); let secret = Uuid::new_v4().to_string().replace("-", ""); let id = generate_image_id(config.image_id_length); + if let Some(auth_key) = &config.auth_key { + if auth_key != &token.0 { + return Err(Error::Unauthorized); + } + } + // As we use transactions, if the image upload fails the image will be dropped save_image( &mut transaction, diff --git a/api/src/main.rs b/api/src/main.rs index 6852bfe..ac8a918 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -13,6 +13,8 @@ pub struct GlobalConfig { public_url: String, // Length of the image id used for "shwoing" the image image_id_length: usize, + // If not empty requires an authentication header containing this key for uploads + auth_key: Option, } #[rocket::main]