Feature flag nip96/blossom

closes #5
This commit is contained in:
Kieran 2024-08-30 19:49:29 +01:00
parent 79338256df
commit 1a1d85b898
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
8 changed files with 659 additions and 530 deletions

1067
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,38 @@
[package]
name = "void_cat"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
labels = ["dep:candle-core", "dep:candle-nn", "dep:candle-transformers"]
default = ["nip96", "blossom"]
labels = ["nip96", "dep:candle-core", "dep:candle-nn", "dep:candle-transformers"]
nip96 = ["dep:ffmpeg-sys-the-third", "dep:blurhash", "dep:libc"]
blossom = []
[dependencies]
log = "0.4.21"
nostr = "0.30.0"
nostr = "0.34.1"
pretty_env_logger = "0.5.0"
rocket = { version = "0.5.0", features = ["json"] }
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
base64 = "0.21.7"
base64 = "0.22.1"
hex = "0.4.3"
serde = { version = "1.0.198", features = ["derive"] }
uuid = { version = "1.8.0", features = ["v4"] }
anyhow = "1.0.82"
sha2 = "0.10.8"
sqlx = { version = "0.7.4", features = ["mysql", "runtime-tokio", "chrono"] }
sqlx = { version = "0.8.1", features = ["mysql", "runtime-tokio", "chrono"] }
config = { version = "0.14.0", features = ["toml"] }
chrono = { version = "0.4.38", features = ["serde"] }
ffmpeg-sys-the-third = { version = "1.1.1",features = ["default"] }
libc = "0.2.153"
blurhash = "0.2.1"
ffmpeg-sys-the-third = { version = "1.1.1", features = ["default"], optional = true }
libc = { version = "0.2.153", optional = true }
blurhash = { version = "0.2.1", optional = true }
ureq = { version = "2.9.7", features = ["json"] }
url = "2.5.0"
serde_with = { version = "3.8.1", features = ["hex"] }
candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.5.1", optional = true }
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.5.1", optional = true }
candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "0.5.1", optional = true }
candle-core = { git = "https://github.com/huggingface/candle.git", version = "^0.6.1", optional = true }
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "^0.6.1", optional = true }
candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "^0.6.1", optional = true }

View File

@ -1,6 +1,6 @@
use base64::prelude::*;
use log::info;
use nostr::{Event, JsonUtil, Kind, Tag, Timestamp};
use nostr::{Event, JsonUtil, Kind, Tag, TagKind, Timestamp};
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome};
use rocket::{async_trait, Request};
@ -15,7 +15,7 @@ impl<'r> FromRequest<'r> for BlossomAuth {
type Error = &'static str;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
return if let Some(auth) = request.headers().get_one("authorization") {
if let Some(auth) = request.headers().get_one("authorization") {
if auth.starts_with("Nostr ") {
let event = if let Ok(j) = BASE64_STANDARD.decode(auth[6..].to_string()) {
if let Ok(ev) = Event::from_json(j) {
@ -38,11 +38,13 @@ impl<'r> FromRequest<'r> for BlossomAuth {
}
// check expiration tag
if let Some(expiration) = event.tags.iter().find_map(|t| match t {
Tag::Expiration(v) => Some(v),
_ => None,
if let Some(expiration) = event.tags.iter().find_map(|t| if t.kind() == TagKind::Expiration {
t.content()
} else {
None
}) {
if *expiration <= Timestamp::now() {
let u_exp: Timestamp = expiration.parse().unwrap();
if u_exp <= Timestamp::now() {
return Outcome::Error((Status::new(401), "Expiration invalid"));
}
} else {
@ -69,6 +71,6 @@ impl<'r> FromRequest<'r> for BlossomAuth {
}
} else {
Outcome::Error((Status::new(403), "Auth header not found"))
};
}
}
}

View File

@ -15,6 +15,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt};
#[cfg(feature = "labels")]
use crate::db::FileLabel;
use crate::db::FileUpload;
#[cfg(feature = "nip96")]
use crate::processing::{compress_file, FileProcessorResult, probe_file, ProbeStream};
#[cfg(feature = "labels")]
use crate::processing::labeling::label_frame;
@ -86,6 +87,7 @@ impl FileStore {
info!("File saved to temp path: {}", tmp_path.to_str().unwrap());
#[cfg(feature = "nip96")]
if compress {
let start = SystemTime::now();
let proc_result = compress_file(tmp_path.clone(), mime_type)?;

View File

@ -20,9 +20,10 @@ mod blob;
mod cors;
mod db;
mod filesystem;
#[cfg(feature = "nip96")]
mod processing;
mod routes;
mod settings;
mod processing;
mod webhook;
#[rocket::main]
@ -44,7 +45,7 @@ async fn main() -> Result<(), Error> {
let mut config = rocket::Config::default();
let ip: SocketAddr = match &settings.listen {
Some(i) => i.parse().unwrap(),
None => SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 8000)
None => SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 8000),
};
config.address = ip.ip();
config.port = ip.port();
@ -56,20 +57,27 @@ async fn main() -> Result<(), Error> {
.limit("form", upload_limit);
config.ident = Ident::try_new("void-cat-rs").unwrap();
let rocket = rocket::Rocket::custom(config)
let mut rocket = rocket::Rocket::custom(config)
.manage(FileStore::new(settings.clone()))
.manage(settings.clone())
.manage(db.clone())
.manage(settings.webhook_url.as_ref().map(|w| Webhook::new(w.clone())))
.manage(
settings
.webhook_url
.as_ref()
.map(|w| Webhook::new(w.clone())),
)
.attach(CORS)
.attach(Shield::new()) // disable
.mount("/", routes::blossom_routes())
.mount("/", routes::nip96_routes())
.mount("/", routes![root, get_blob, head_blob])
.launch()
.await;
.mount("/", routes![root, get_blob, head_blob]);
if let Err(e) = rocket {
#[cfg(feature = "blossom")] {
rocket = rocket.mount("/", routes::blossom_routes());
}
#[cfg(feature = "nip96")] {
rocket = rocket.mount("/", routes::nip96_routes());
}
if let Err(e) = rocket.launch().await {
error!("Rocker error {}", e);
Err(Error::from(e))
} else {

View File

@ -4,8 +4,8 @@ use std::ptr;
use anyhow::Error;
use ffmpeg_sys_the_third::{av_frame_alloc, AVFrame, AVPixelFormat, sws_freeContext, sws_getContext, sws_scale_frame};
use crate::processing::probe::FFProbe;
use crate::processing::probe::FFProbe;
use crate::processing::webp::WebpProcessor;
mod webp;

View File

@ -2,17 +2,17 @@ use std::fs;
use log::error;
use nostr::prelude::hex;
use nostr::Tag;
use rocket::{Data, Route, routes, State};
use nostr::{Tag, TagKind};
use rocket::data::ByteUnit;
use rocket::http::Status;
use rocket::response::Responder;
use rocket::serde::json::Json;
use rocket::{routes, Data, Route, State};
use serde::{Deserialize, Serialize};
use crate::auth::blossom::BlossomAuth;
use crate::blob::BlobDescriptor;
use crate::db::{Database};
use crate::db::Database;
use crate::filesystem::FileStore;
use crate::routes::delete_file;
use crate::settings::Settings;
@ -54,9 +54,10 @@ impl BlossomResponse {
}
fn check_method(event: &nostr::Event, method: &str) -> bool {
if let Some(t) = event.tags.iter().find_map(|t| match t {
Tag::Hashtag(tag) => Some(tag),
_ => None,
if let Some(t) = event.tags.iter().find_map(|t| if t.kind() == TagKind::Method {
t.content()
} else {
None
}) {
return t == method;
}
@ -89,10 +90,11 @@ async fn upload(
return BlossomResponse::error("Invalid request method tag");
}
let name = auth.event.tags.iter().find_map(|t| match t {
Tag::Name(s) => Some(s.clone()),
_ => None,
});
let name = auth
.event
.tags
.iter()
.find_map(|t| if t.kind() == TagKind::Name { t.content() } else { None });
let size = match auth.event.tags.iter().find_map(|t| {
let values = t.as_vec();
if values.len() == 2 && values[0] == "size" {
@ -102,7 +104,7 @@ async fn upload(
}
}) {
Some(s) => s,
None => return BlossomResponse::error("Invalid request, no size tag")
None => return BlossomResponse::error("Invalid request, no size tag"),
};
if size > settings.max_upload_bytes {
return BlossomResponse::error("File too large");
@ -118,22 +120,31 @@ async fn upload(
}
}
match fs
.put(data.open(ByteUnit::from(settings.max_upload_bytes)), &mime_type, false)
.put(
data.open(ByteUnit::from(settings.max_upload_bytes)),
&mime_type,
false,
)
.await
{
Ok(mut blob) => {
blob.upload.name = name.unwrap_or("".to_string());
blob.upload.name = name.unwrap_or("").to_owned();
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
if let Some(wh) = webhook.as_ref() {
match wh.store_file(&pubkey_vec, blob.clone()) {
Ok(store) => if !store {
let _ = fs::remove_file(blob.path);
return BlossomResponse::error("Upload rejected");
Ok(store) => {
if !store {
let _ = fs::remove_file(blob.path);
return BlossomResponse::error("Upload rejected");
}
}
Err(e) => {
let _ = fs::remove_file(blob.path);
return BlossomResponse::error(format!("Internal error, failed to call webhook: {}", e));
return BlossomResponse::error(format!(
"Internal error, failed to call webhook: {}",
e
));
}
}
}

View File

@ -11,10 +11,14 @@ use rocket::response::Responder;
use crate::db::{Database, FileUpload};
use crate::filesystem::FileStore;
#[cfg(feature = "blossom")]
pub use crate::routes::blossom::blossom_routes;
#[cfg(feature = "nip96")]
pub use crate::routes::nip96::nip96_routes;
#[cfg(feature = "blossom")]
mod blossom;
#[cfg(feature = "nip96")]
mod nip96;
pub struct FilePayload {