feat: super basic file uploads for logged in users
this is super crude and unauthorized!!! also vulnerable to AP injections so much more work has to be done
This commit is contained in:
parent
005524201d
commit
6793f0fdc9
5 changed files with 78 additions and 4 deletions
|
@ -15,6 +15,9 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub compat: CompatibilityConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub files: FileStorageConfig,
|
||||
|
||||
// TODO should i move app keys here?
|
||||
}
|
||||
|
||||
|
@ -115,6 +118,13 @@ pub struct CompatibilityConfig {
|
|||
pub skip_single_attachment_if_image_is_set: bool,
|
||||
}
|
||||
|
||||
#[serde_inline_default::serde_inline_default]
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
|
||||
pub struct FileStorageConfig {
|
||||
#[serde_inline_default("files/".to_string())]
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: Option<&std::path::PathBuf>) -> Self {
|
||||
let Some(cfg_path) = path else { return Config::default() };
|
||||
|
|
|
@ -22,7 +22,7 @@ jrd = "0.1"
|
|||
tracing = "0.1"
|
||||
tokio = { version = "1.40", features = ["full"] } # TODO slim this down
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
axum = "0.7"
|
||||
axum = { version = "0.7", features = ["multipart"] }
|
||||
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
||||
httpsign = { path = "../../utils/httpsign/", features = ["axum"] }
|
||||
apb = { path = "../../apb", features = ["unstructured", "orm", "activitypub-fe", "activitypub-counters", "litepub", "ostatus", "toot", "jsonld"] }
|
||||
|
|
60
upub/routes/src/activitypub/file.rs
Normal file
60
upub/routes/src/activitypub/file.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use axum::extract::{Multipart, Path, State};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use upub::Context;
|
||||
|
||||
use crate::AuthIdentity;
|
||||
|
||||
pub async fn upload(
|
||||
State(ctx): State<Context>,
|
||||
AuthIdentity(auth): AuthIdentity,
|
||||
mut multipart: Multipart,
|
||||
) -> crate::ApiResult<()> {
|
||||
if !auth.is_local() {
|
||||
return Err(crate::ApiError::forbidden());
|
||||
}
|
||||
|
||||
let mut uploaded_something = false;
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let _ = if let Some(filename) = field.file_name() {
|
||||
filename.to_string()
|
||||
} else {
|
||||
tracing::warn!("skipping multipart field {field:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = match field.bytes().await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::error!("error reading multipart part: {e:?}");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let name = sha256::digest(data.as_ref());
|
||||
let path = format!("{}{name}", ctx.cfg().files.path);
|
||||
|
||||
tokio::fs::File::open(path).await?.write_all(&data).await?;
|
||||
uploaded_something = true;
|
||||
}
|
||||
|
||||
if uploaded_something {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ApiError::bad_request())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download(
|
||||
State(ctx): State<Context>,
|
||||
AuthIdentity(_auth): AuthIdentity,
|
||||
Path(id): Path<String>,
|
||||
) -> crate::ApiResult<Vec<u8>> {
|
||||
let path = format!("{}{id}", ctx.cfg().files.path);
|
||||
let mut buffer = Vec::new();
|
||||
tokio::fs::File::open(path)
|
||||
.await?
|
||||
.read_to_end(&mut buffer)
|
||||
.await?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
|
@ -6,6 +6,7 @@ pub mod activity;
|
|||
pub mod application;
|
||||
pub mod auth;
|
||||
pub mod tags;
|
||||
pub mod file;
|
||||
pub mod well_known;
|
||||
|
||||
use axum::{http::StatusCode, response::IntoResponse, routing::{get, patch, post, put}, Router};
|
||||
|
@ -69,13 +70,13 @@ impl ActivityPubRouter for Router<upub::Context> {
|
|||
.route("/objects/:id/replies/page", get(ap::object::replies::page))
|
||||
.route("/objects/:id/context", get(ap::object::context::get))
|
||||
.route("/objects/:id/context/page", get(ap::object::context::page))
|
||||
// file routes
|
||||
.route("/file", post(ap::file::upload))
|
||||
.route("/file/:id", get(ap::file::download))
|
||||
//.route("/objects/:id/likes", get(ap::object::likes::get))
|
||||
//.route("/objects/:id/likes/page", get(ap::object::likes::page))
|
||||
//.route("/objects/:id/shares", get(ap::object::announces::get))
|
||||
//.route("/objects/:id/shares/page", get(ap::object::announces::page))
|
||||
// hashtags routes
|
||||
//.route("/hashtags/:name", get(ap::hashtags::get))
|
||||
//.route("/hashtags/:name/page", get(ap::hashtags::page))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ pub enum ApiError {
|
|||
#[error("fetch error: {0:?}")]
|
||||
FetchError(#[from] upub::traits::fetch::RequestError),
|
||||
|
||||
#[error("error interacting with file system: {0:?}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
// wrapper error to return arbitraty status codes
|
||||
#[error("{0}")]
|
||||
Status(StatusCode),
|
||||
|
|
Loading…
Reference in a new issue