relay can be followed

This commit is contained in:
Astro 2022-12-13 04:12:35 +01:00
parent 6260f4306e
commit 177051be52
5 changed files with 58 additions and 13 deletions

View file

@ -26,8 +26,11 @@ pub struct ActorPublicKey {
/// ActivityPub "activity" /// ActivityPub "activity"
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action<O> { pub struct Action<O> {
#[serde(rename = "@context")]
pub jsonld_context: serde_json::Value,
#[serde(rename = "type")] #[serde(rename = "type")]
pub action_type: String, pub action_type: String,
pub id: String,
pub actor: String, pub actor: String,
pub to: Option<String>, pub to: Option<String>,
pub object: Option<O>, pub object: Option<O>,

View file

@ -85,7 +85,6 @@ where
// mastodon uses base64::alphabet::STANDARD, not base64::alphabet::URL_SAFE // mastodon uses base64::alphabet::STANDARD, not base64::alphabet::URL_SAFE
digest_header = digest_header.replace("+", "-") digest_header = digest_header.replace("+", "-")
.replace("/", "_"); .replace("/", "_");
dbg!(&digest_header);
let digest: DigestHeader = digest_header.parse() let digest: DigestHeader = digest_header.parse()
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Cannot parse Digest: header: {}", e)))?; .map_err(|e| (StatusCode::BAD_REQUEST, format!("Cannot parse Digest: header: {}", e)))?;
// read body // read body

View file

@ -17,6 +17,7 @@ pub use fetch::fetch;
mod send; mod send;
pub use send::send; pub use send::send;
mod activitypub; mod activitypub;
mod webfinger;
mod endpoint; mod endpoint;
const ACTOR_ID: &str = "https://relay.fedi.buzz/actor"; const ACTOR_ID: &str = "https://relay.fedi.buzz/actor";
@ -61,6 +62,7 @@ async fn handler(
axum::extract::State(state): axum::extract::State<State>, axum::extract::State(state): axum::extract::State<State>,
endpoint: endpoint::Endpoint, endpoint: endpoint::Endpoint,
) -> Response { ) -> Response {
dbg!(&endpoint);
let action = match serde_json::from_value::<activitypub::Action<serde_json::Value>>(endpoint.payload.clone()) { let action = match serde_json::from_value::<activitypub::Action<serde_json::Value>>(endpoint.payload.clone()) {
Ok(action) => action, Ok(action) => action,
Err(e) => return ( Err(e) => return (
@ -68,29 +70,29 @@ async fn handler(
format!("Bad action: {:?}", e) format!("Bad action: {:?}", e)
).into_response(), ).into_response(),
}; };
dbg!(&action);
if action.action_type == "Follow" { if action.action_type == "Follow" {
let private_key = state.private_key.clone(); let private_key = state.private_key.clone();
let client = state.client.clone(); let client = state.client.clone();
tokio::spawn(async move { tokio::spawn(async move {
let accept = activitypub::Action { let accept = activitypub::Action {
jsonld_context: serde_json::Value::String("https://www.w3.org/ns/activitystreams".to_string()),
action_type: "Accept".to_string(), action_type: "Accept".to_string(),
actor: ACTOR_ID.to_string(), actor: ACTOR_ID.to_string(),
to: Some(endpoint.actor.id), to: Some(endpoint.actor.id.clone()),
id: action.id,
object: Some(endpoint.payload), object: Some(endpoint.payload),
}; };
dbg!(serde_json::to_string_pretty(&accept));
send::send( send::send(
client.as_ref(), &endpoint.actor.inbox, client.as_ref(), &endpoint.actor.inbox,
ACTOR_KEY, ACTOR_KEY,
&private_key, &private_key,
accept, accept,
).await ).await
.map_err(|e| tracing::error!("post: {}", e)); .map_err(|e| tracing::error!("post accept: {}", e));
}); });
(StatusCode::CREATED, (StatusCode::ACCEPTED,
[("content-type", "application/activity+json")], [("content-type", "application/activity+json")],
"{}" "{}"
).into_response() ).into_response()
@ -115,6 +117,7 @@ async fn main() {
let app = Router::new() let app = Router::new()
.route("/actor", get(actor)) .route("/actor", get(actor))
.route("/relay", post(handler)) .route("/relay", post(handler))
.route("/.well-known/webfinger", get(webfinger::webfinger))
.with_state(State { .with_state(State {
client: Arc::new(reqwest::Client::new()), client: Arc::new(reqwest::Client::new()),
private_key, public_key, private_key, public_key,

View file

@ -1,3 +1,4 @@
use http::StatusCode;
use http_digest_headers::{DigestHeader, DigestMethod}; use http_digest_headers::{DigestHeader, DigestMethod};
use serde::Serialize; use serde::Serialize;
use sigh::{PrivateKey, SigningConfig, alg::RsaSha256}; use sigh::{PrivateKey, SigningConfig, alg::RsaSha256};
@ -14,11 +15,15 @@ pub enum SendError {
HttpReq(#[from] http::Error), HttpReq(#[from] http::Error),
#[error("HTTP client error")] #[error("HTTP client error")]
Http(#[from] reqwest::Error), Http(#[from] reqwest::Error),
#[error("Invalid URI")]
InvalidUri,
#[error("Error response from remote")]
Response(String),
} }
pub async fn send<T: Serialize>( pub async fn send<T: Serialize>(
client: &reqwest::Client, client: &reqwest::Client,
url: &str, uri: &str,
key_id: &str, key_id: &str,
private_key: &PrivateKey, private_key: &PrivateKey,
body: T, body: T,
@ -38,9 +43,12 @@ pub async fn send<T: Serialize>(
&digest_header[7..].replace("-", "+").replace("_", "/") &digest_header[7..].replace("-", "+").replace("_", "/")
); );
let url = reqwest::Url::parse(uri)
.map_err(|_| SendError::InvalidUri)?;
let mut req = http::Request::builder() let mut req = http::Request::builder()
.method("POST") .method("POST")
.uri(url) .uri(uri)
.header("host", format!("{}", url.host().ok_or(SendError::InvalidUri)?))
.header("content-type", "application/activity+json") .header("content-type", "application/activity+json")
.header("date", chrono::Utc::now().to_rfc2822() .header("date", chrono::Utc::now().to_rfc2822()
.replace("+0000", "GMT")) .replace("+0000", "GMT"))
@ -49,11 +57,12 @@ pub async fn send<T: Serialize>(
.map_err(SendError::HttpReq)?; .map_err(SendError::HttpReq)?;
SigningConfig::new(RsaSha256, private_key, key_id) SigningConfig::new(RsaSha256, private_key, key_id)
.sign(&mut req)?; .sign(&mut req)?;
dbg!(&req);
let res = client.execute(req.try_into()?) let res = client.execute(req.try_into()?)
.await?; .await?;
dbg!(&res); if res.status() >= StatusCode::OK && res.status() < StatusCode::MULTIPLE_CHOICES {
dbg!(res.text().await); Ok(())
} else {
Ok(()) let response = res.text().await?;
Err(SendError::Response(response))
}
} }

31
src/webfinger.rs Normal file
View file

@ -0,0 +1,31 @@
use std::collections::HashMap;
use axum::{
async_trait,
body::{Bytes, HttpBody},
extract::{Query},
http::{header::CONTENT_TYPE, Request, StatusCode},
Json,
response::{IntoResponse, Response},
routing::post,
Form, RequestExt, Router, BoxError,
};
use serde_json::json;
pub async fn webfinger(Query(params): Query<HashMap<String, String>>) -> Response {
let resource = match params.get("resource") {
Some(resource) => resource,
None => return StatusCode::NOT_FOUND.into_response(),
};
Json(json!({
"subject": &resource,
"aliases": &[
"https://relay.fedi.buzz/actor",
],
"links": &[json!({
"rel": "self",
"type": "application/activity+json",
"href": "https://relay.fedi.buzz/actor",
})],
})).into_response()
}