* Add default response body size limit * Limit all the methods, add reqwest shim that wraps around bytes_stream * Change image to rust:1.65-bullseye * Use `DeserializeOwned` instead of the HRTB over `Deserialize` * Remove the configurability, limit to 100KB * Add documentation to body size restricted functions * rustfmt
193 lines
6.1 KiB
Rust
193 lines
6.1 KiB
Rust
use crate::{
|
|
core::activity_queue::create_activity_queue,
|
|
traits::ActivityHandler,
|
|
utils::verify_domains_match,
|
|
};
|
|
use async_trait::async_trait;
|
|
use background_jobs::Manager;
|
|
use derive_builder::Builder;
|
|
use dyn_clone::{clone_trait_object, DynClone};
|
|
use reqwest_middleware::ClientWithMiddleware;
|
|
use serde::de::DeserializeOwned;
|
|
use std::time::Duration;
|
|
use url::Url;
|
|
|
|
pub mod core;
|
|
pub mod data;
|
|
pub mod deser;
|
|
pub mod traits;
|
|
pub mod utils;
|
|
|
|
/// Mime type for Activitypub, used for `Accept` and `Content-Type` HTTP headers
|
|
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
|
|
|
/// Represents a single, federated instance (for example lemmy.ml). There should only be one of
|
|
/// this in your application (except for testing).
|
|
pub struct LocalInstance {
|
|
hostname: String,
|
|
client: ClientWithMiddleware,
|
|
activity_queue: Manager,
|
|
settings: InstanceSettings,
|
|
}
|
|
|
|
impl LocalInstance {
|
|
async fn verify_url_and_domain<Activity, Datatype>(
|
|
&self,
|
|
activity: &Activity,
|
|
) -> Result<(), Error>
|
|
where
|
|
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
|
{
|
|
verify_domains_match(activity.id(), activity.actor())?;
|
|
self.verify_url_valid(activity.id()).await?;
|
|
if self.is_local_url(activity.id()) {
|
|
return Err(Error::UrlVerificationError(
|
|
"Activity was sent from local instance",
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Perform some security checks on URLs as mentioned in activitypub spec, and call user-supplied
|
|
/// [`InstanceSettings.verify_url_function`].
|
|
///
|
|
/// https://www.w3.org/TR/activitypub/#security-considerations
|
|
async fn verify_url_valid(&self, url: &Url) -> Result<(), Error> {
|
|
match url.scheme() {
|
|
"https" => {}
|
|
"http" => {
|
|
if !self.settings.debug {
|
|
return Err(Error::UrlVerificationError(
|
|
"Http urls are only allowed in debug mode",
|
|
));
|
|
}
|
|
}
|
|
_ => return Err(Error::UrlVerificationError("Invalid url scheme")),
|
|
};
|
|
|
|
if url.domain().is_none() {
|
|
return Err(Error::UrlVerificationError("Url must have a domain"));
|
|
}
|
|
|
|
if url.domain() == Some("localhost") && !self.settings.debug {
|
|
return Err(Error::UrlVerificationError(
|
|
"Localhost is only allowed in debug mode",
|
|
));
|
|
}
|
|
|
|
self.settings
|
|
.url_verifier
|
|
.verify(url)
|
|
.await
|
|
.map_err(Error::UrlVerificationError)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait UrlVerifier: DynClone + Send {
|
|
async fn verify(&self, url: &Url) -> Result<(), &'static str>;
|
|
}
|
|
clone_trait_object!(UrlVerifier);
|
|
|
|
// Use InstanceSettingsBuilder to initialize this
|
|
#[derive(Builder)]
|
|
pub struct InstanceSettings {
|
|
/// Maximum number of outgoing HTTP requests per incoming activity
|
|
#[builder(default = "20")]
|
|
http_fetch_limit: i32,
|
|
/// Number of worker threads for sending outgoing activities
|
|
#[builder(default = "64")]
|
|
worker_count: u64,
|
|
/// Run library in debug mode. This allows usage of http and localhost urls. It also sends
|
|
/// outgoing activities synchronously, not in background thread. This helps to make tests
|
|
/// more consistent.
|
|
/// Do not use for production.
|
|
#[builder(default = "false")]
|
|
debug: bool,
|
|
/// Timeout for all HTTP requests. HTTP signatures are valid for 10s, so it makes sense to
|
|
/// use the same as timeout when sending
|
|
#[builder(default = "Duration::from_secs(10)")]
|
|
request_timeout: Duration,
|
|
/// Function used to verify that urls are valid, used when receiving activities or fetching remote
|
|
/// objects. Use this to implement functionality like federation blocklists. In case verification
|
|
/// fails, it should return an error message.
|
|
#[builder(default = "Box::new(DefaultUrlVerifier())")]
|
|
url_verifier: Box<dyn UrlVerifier + Sync>,
|
|
/// Enable to sign HTTP signatures according to draft 10, which does not include (created) and
|
|
/// (expires) fields. This is required for compatibility with some software like Pleroma.
|
|
/// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-10
|
|
/// https://git.pleroma.social/pleroma/pleroma/-/issues/2939
|
|
#[builder(default = "false")]
|
|
http_signature_compat: bool,
|
|
}
|
|
|
|
impl InstanceSettings {
|
|
/// Returns a new settings builder.
|
|
pub fn builder() -> InstanceSettingsBuilder {
|
|
<_>::default()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct DefaultUrlVerifier();
|
|
|
|
#[async_trait]
|
|
impl UrlVerifier for DefaultUrlVerifier {
|
|
async fn verify(&self, _url: &Url) -> Result<(), &'static str> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl LocalInstance {
|
|
pub fn new(domain: String, client: ClientWithMiddleware, settings: InstanceSettings) -> Self {
|
|
let activity_queue = create_activity_queue(client.clone(), &settings);
|
|
LocalInstance {
|
|
hostname: domain,
|
|
client,
|
|
activity_queue,
|
|
settings,
|
|
}
|
|
}
|
|
/// Returns true if the url refers to this instance. Handles hostnames like `localhost:8540` for
|
|
/// local debugging.
|
|
fn is_local_url(&self, url: &Url) -> bool {
|
|
let mut domain = url.domain().expect("id has domain").to_string();
|
|
if let Some(port) = url.port() {
|
|
domain = format!("{}:{}", domain, port);
|
|
}
|
|
domain == self.hostname
|
|
}
|
|
|
|
/// Returns the local hostname
|
|
pub fn hostname(&self) -> &str {
|
|
&self.hostname
|
|
}
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum Error {
|
|
#[error("Object was not found in database")]
|
|
NotFound,
|
|
#[error("Request limit was reached during fetch")]
|
|
RequestLimit,
|
|
#[error("Response body limit was reached during fetch")]
|
|
ResponseBodyLimit,
|
|
#[error("Object to be fetched was deleted")]
|
|
ObjectDeleted,
|
|
#[error("{0}")]
|
|
UrlVerificationError(&'static str),
|
|
#[error(transparent)]
|
|
Other(#[from] anyhow::Error),
|
|
}
|
|
|
|
impl Error {
|
|
pub fn conv<T>(error: T) -> Self
|
|
where
|
|
T: Into<anyhow::Error>,
|
|
{
|
|
Error::Other(error.into())
|
|
}
|
|
}
|