This repository has been archived on 2023-07-11. You can view files and clone it, but cannot push or open issues or pull requests.
buzzrelay/src/main.rs

314 lines
10 KiB
Rust
Raw Normal View History

2022-12-11 01:07:39 +01:00
use axum::{
2022-12-20 00:15:00 +01:00
extract::{FromRef, Path, Query},
2022-12-23 18:06:42 +01:00
http::StatusCode,
2022-12-11 01:07:39 +01:00
response::{IntoResponse, Response},
2022-12-23 18:06:42 +01:00
routing::get, Json, Router,
2022-12-11 01:07:39 +01:00
};
2022-12-23 18:06:42 +01:00
use axum_extra::routing::SpaRouter;
2022-12-20 03:13:44 +01:00
use metrics::increment_counter;
use metrics_util::MetricKindMask;
2022-12-23 18:06:42 +01:00
use metrics_exporter_prometheus::PrometheusBuilder;
2022-12-12 21:31:42 +01:00
use serde_json::json;
2022-12-20 00:15:00 +01:00
use sigh::{PrivateKey, PublicKey};
2022-12-19 21:20:13 +01:00
use std::{net::SocketAddr, sync::Arc, time::Duration, collections::HashMap};
use std::{panic, process};
2022-12-11 01:07:39 +01:00
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
2022-12-19 21:20:13 +01:00
mod config;
mod actor;
mod db;
2022-12-11 01:07:39 +01:00
mod fetch;
pub use fetch::fetch;
mod send;
2022-12-18 21:31:50 +01:00
mod stream;
mod relay;
2022-12-11 01:07:39 +01:00
mod activitypub;
mod endpoint;
2022-12-12 21:31:42 +01:00
2022-12-19 21:20:13 +01:00
#[derive(Clone)]
2022-12-11 01:07:39 +01:00
struct State {
2022-12-19 21:20:13 +01:00
database: db::Database,
2022-12-11 01:07:39 +01:00
client: Arc<reqwest::Client>,
2022-12-19 21:20:13 +01:00
hostname: Arc<String>,
priv_key: PrivateKey,
pub_key: PublicKey,
2022-12-11 01:07:39 +01:00
}
impl FromRef<State> for Arc<reqwest::Client> {
fn from_ref(state: &State) -> Arc<reqwest::Client> {
state.client.clone()
}
}
2022-12-20 03:13:44 +01:00
fn track_request(method: &'static str, controller: &'static str, result: &'static str) {
2022-12-20 04:10:45 +01:00
increment_counter!("api_http_requests_total", "controller" => controller, "method" => method, "result" => result);
2022-12-20 03:13:44 +01:00
}
2022-12-19 21:20:13 +01:00
async fn webfinger(
axum::extract::State(state): axum::extract::State<State>,
Query(params): Query<HashMap<String, String>>,
) -> Response {
let resource = match params.get("resource") {
Some(resource) => resource,
2022-12-20 03:13:44 +01:00
None => {
track_request("GET", "webfinger", "invalid");
return StatusCode::NOT_FOUND.into_response();
},
2022-12-19 21:20:13 +01:00
};
let (target_kind, target_host) =
if resource.starts_with("acct:tag-") {
let off = "acct:tag-".len();
let at = resource.find('@');
(actor::ActorKind::TagRelay(resource[off..at.unwrap_or(resource.len())].to_string()),
at.map_or_else(|| state.hostname.clone(), |at| Arc::new(resource[at + 1..].to_string())))
} else if resource.starts_with("acct:instance-") {
let off = "acct:instance-".len();
let at = resource.find('@');
(actor::ActorKind::InstanceRelay(resource[off..at.unwrap_or(resource.len())].to_string()),
at.map_or_else(|| state.hostname.clone(), |at| Arc::new(resource[at + 1..].to_string())))
} else {
2022-12-20 03:13:44 +01:00
track_request("GET", "webfinger", "not_found");
2022-12-19 21:20:13 +01:00
return StatusCode::NOT_FOUND.into_response();
};
2022-12-20 03:13:44 +01:00
track_request("GET", "webfinger", "found");
2022-12-19 21:20:13 +01:00
let target = actor::Actor {
host: target_host,
kind: target_kind,
};
Json(json!({
"subject": &resource,
"aliases": &[
target.uri(),
],
"links": &[json!({
"rel": "self",
"type": "application/activity+json",
"href": target.uri(),
})],
})).into_response()
}
async fn get_tag_actor(
axum::extract::State(state): axum::extract::State<State>,
Path(tag): Path<String>
) -> Response {
2022-12-20 03:13:44 +01:00
track_request("GET", "actor", "tag");
2022-12-19 21:20:13 +01:00
let target = actor::Actor {
host: state.hostname.clone(),
kind: actor::ActorKind::from_tag(&tag),
2022-12-19 21:20:13 +01:00
};
target.as_activitypub(&state.pub_key)
.into_response()
}
async fn get_instance_actor(
axum::extract::State(state): axum::extract::State<State>,
Path(instance): Path<String>
) -> Response {
2022-12-20 03:13:44 +01:00
track_request("GET", "actor", "instance");
2022-12-19 21:20:13 +01:00
let target = actor::Actor {
host: state.hostname.clone(),
2022-12-19 21:22:15 +01:00
kind: actor::ActorKind::InstanceRelay(instance.to_lowercase()),
2022-12-19 21:20:13 +01:00
};
target.as_activitypub(&state.pub_key)
.into_response()
}
async fn post_tag_relay(
axum::extract::State(state): axum::extract::State<State>,
Path(tag): Path<String>,
endpoint: endpoint::Endpoint
) -> Response {
let target = actor::Actor {
host: state.hostname.clone(),
kind: actor::ActorKind::from_tag(&tag),
2022-12-19 21:20:13 +01:00
};
post_relay(state, endpoint, target).await
2022-12-11 01:07:39 +01:00
}
2022-12-19 21:20:13 +01:00
async fn post_instance_relay(
2022-12-11 01:07:39 +01:00
axum::extract::State(state): axum::extract::State<State>,
2022-12-19 21:20:13 +01:00
Path(instance): Path<String>,
endpoint: endpoint::Endpoint
) -> Response {
let target = actor::Actor {
host: state.hostname.clone(),
2022-12-19 21:22:15 +01:00
kind: actor::ActorKind::InstanceRelay(instance.to_lowercase()),
2022-12-19 21:20:13 +01:00
};
post_relay(state, endpoint, target).await
}
async fn post_relay(
state: State,
2022-12-11 01:07:39 +01:00
endpoint: endpoint::Endpoint,
2022-12-19 21:20:13 +01:00
target: actor::Actor
2022-12-11 01:07:39 +01:00
) -> Response {
let action = match serde_json::from_value::<activitypub::Action<serde_json::Value>>(endpoint.payload.clone()) {
Ok(action) => action,
2022-12-20 03:13:44 +01:00
Err(e) => {
track_request("POST", "relay", "bad_action");
return (
StatusCode::BAD_REQUEST,
format!("Bad action: {:?}", e)
).into_response();
}
2022-12-11 01:07:39 +01:00
};
2022-12-19 21:54:56 +01:00
let object_type = action.object
.and_then(|object| object.get("type").cloned())
2022-12-20 00:15:00 +01:00
.and_then(|object_type| object_type.as_str().map(std::string::ToString::to_string));
2022-12-18 21:31:50 +01:00
2022-12-11 01:07:39 +01:00
if action.action_type == "Follow" {
2022-12-19 21:20:13 +01:00
let priv_key = state.priv_key.clone();
2022-12-11 01:07:39 +01:00
let client = state.client.clone();
tokio::spawn(async move {
let accept = activitypub::Action {
2022-12-13 04:12:35 +01:00
jsonld_context: serde_json::Value::String("https://www.w3.org/ns/activitystreams".to_string()),
2022-12-11 01:07:39 +01:00
action_type: "Accept".to_string(),
2022-12-19 21:20:13 +01:00
actor: target.uri(),
to: Some(json!(endpoint.actor.id.clone())),
2022-12-13 04:12:35 +01:00
id: action.id,
2022-12-11 01:07:39 +01:00
object: Some(endpoint.payload),
};
2022-12-19 21:20:13 +01:00
let result = send::send(
2022-12-11 01:07:39 +01:00
client.as_ref(), &endpoint.actor.inbox,
2022-12-19 21:20:13 +01:00
&target.key_id(),
&priv_key,
&accept,
).await;
match result {
Ok(()) => {
2022-12-19 21:54:56 +01:00
match state.database.add_follow(
2022-12-19 21:20:13 +01:00
&endpoint.actor.id,
&endpoint.actor.inbox,
&target.uri(),
2022-12-19 21:54:56 +01:00
).await {
2022-12-20 03:13:44 +01:00
Ok(()) => {
track_request("POST", "relay", "follow");
}
Err(e) => {
2022-12-19 21:54:56 +01:00
// duplicate key constraint
2022-12-20 03:13:44 +01:00
tracing::error!("add_follow: {}", e);
track_request("POST", "relay", "follow_error");
}
2022-12-19 21:54:56 +01:00
}
2022-12-19 21:20:13 +01:00
}
Err(e) => {
tracing::error!("post accept: {}", e);
2022-12-20 03:13:44 +01:00
track_request("POST", "relay", "follow_accept_error");
2022-12-19 21:20:13 +01:00
}
}
2022-12-11 01:07:39 +01:00
});
2022-12-18 21:31:50 +01:00
2022-12-13 04:12:35 +01:00
(StatusCode::ACCEPTED,
2022-12-12 21:31:42 +01:00
[("content-type", "application/activity+json")],
"{}"
).into_response()
2022-12-19 21:54:56 +01:00
} else if action.action_type == "Undo" && object_type == Some("Follow".to_string()) {
match state.database.del_follow(
&endpoint.actor.id,
&target.uri(),
).await {
2022-12-20 03:13:44 +01:00
Ok(()) => {
track_request("POST", "relay", "unfollow");
2022-12-19 21:54:56 +01:00
(StatusCode::ACCEPTED,
[("content-type", "application/activity+json")],
"{}"
2022-12-20 03:13:44 +01:00
).into_response()
}
2022-12-19 21:54:56 +01:00
Err(e) => {
tracing::error!("del_follow: {}", e);
2022-12-20 03:13:44 +01:00
track_request("POST", "relay", "unfollow_error");
2022-12-19 21:54:56 +01:00
(StatusCode::INTERNAL_SERVER_ERROR,
format!("{}", e)
).into_response()
}
}
2022-12-11 01:07:39 +01:00
} else {
2022-12-20 03:13:44 +01:00
track_request("POST", "relay", "unrecognized");
2022-12-11 01:07:39 +01:00
(StatusCode::BAD_REQUEST, "Not a recognized request").into_response()
}
}
#[tokio::main]
async fn main() {
2022-12-19 21:20:13 +01:00
exit_on_panic();
2022-12-11 01:07:39 +01:00
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
"buzzrelay=trace,tower_http=trace,axum=trace".into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
2022-12-19 21:20:13 +01:00
let config = config::Config::load(
2022-12-20 00:15:00 +01:00
&std::env::args().nth(1)
2022-12-19 21:20:13 +01:00
.expect("Call with config.yaml")
);
2023-01-11 18:45:45 +01:00
let priv_key = config.priv_key();
let pub_key = config.pub_key();
2022-12-20 03:13:44 +01:00
let recorder = PrometheusBuilder::new()
.add_global_label("application", env!("CARGO_PKG_NAME"))
.idle_timeout(MetricKindMask::ALL, Some(Duration::from_secs(600)))
.install_recorder()
.unwrap();
2022-12-19 21:20:13 +01:00
let database = db::Database::connect(&config.db).await;
2023-01-11 18:45:45 +01:00
let stream_rx = stream::spawn(config.streams.into_iter());
2022-12-18 21:31:50 +01:00
let client = Arc::new(
reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.user_agent(concat!(
env!("CARGO_PKG_NAME"),
"/",
env!("CARGO_PKG_VERSION"),
))
.pool_max_idle_per_host(1)
.pool_idle_timeout(Some(Duration::from_secs(5)))
.build()
.unwrap()
);
2022-12-19 21:20:13 +01:00
let hostname = Arc::new(config.hostname.clone());
2023-01-11 18:45:45 +01:00
relay::spawn(client.clone(), hostname.clone(), database.clone(), priv_key.clone(), stream_rx);
2022-12-11 01:07:39 +01:00
let app = Router::new()
2022-12-19 21:20:13 +01:00
.route("/tag/:tag", get(get_tag_actor).post(post_tag_relay))
.route("/instance/:instance", get(get_instance_actor).post(post_instance_relay))
.route("/.well-known/webfinger", get(webfinger))
2022-12-20 03:13:44 +01:00
.route("/metrics", get(|| async move {
recorder.render().into_response()
}))
2022-12-11 01:07:39 +01:00
.with_state(State {
2022-12-19 21:20:13 +01:00
database,
2022-12-18 21:31:50 +01:00
client,
2022-12-19 21:20:13 +01:00
hostname,
2023-01-11 18:45:45 +01:00
priv_key,
pub_key,
2022-12-23 18:06:42 +01:00
})
.merge(SpaRouter::new("/", "static"));
2022-12-11 01:07:39 +01:00
2022-12-19 21:20:13 +01:00
let addr = SocketAddr::from(([127, 0, 0, 1], config.listen_port));
2022-12-20 00:19:21 +01:00
let server = axum::Server::bind(&addr)
.serve(app.into_make_service());
2022-12-19 21:20:13 +01:00
tracing::info!("serving on {}", addr);
2022-12-20 00:19:21 +01:00
systemd::daemon::notify(false, [(systemd::daemon::STATE_READY, "1")].iter())
.unwrap();
server.await
2022-12-11 01:07:39 +01:00
.unwrap();
}
2022-12-19 21:20:13 +01:00
fn exit_on_panic() {
let orig_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
// invoke the default handler and exit the process
orig_hook(panic_info);
process::exit(1);
}));
}