Create instance actor

This commit is contained in:
silverpill 2021-11-18 00:51:56 +00:00
parent 52b51501d5
commit d935b843a8
6 changed files with 91 additions and 19 deletions

View file

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use crate::config::Instance;
use crate::errors::ConversionError;
use crate::models::profiles::types::{DbActorProfile, ExtraField};
use crate::models::users::types::User;
@ -14,7 +15,7 @@ use super::views::{
get_followers_url,
get_following_url,
};
use super::vocabulary::{PERSON, IMAGE, PROPERTY_VALUE};
use super::vocabulary::{IMAGE, PERSON, PROPERTY_VALUE, SERVICE};
const W3ID_CONTEXT: &str = "https://w3id.org/security/v1";
@ -63,8 +64,12 @@ pub struct Actor {
pub preferred_username: String,
pub inbox: String,
pub outbox: String,
pub followers: String,
pub following: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub followers: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub following: Option<String>,
pub public_key: PublicKey,
@ -80,6 +85,7 @@ pub struct Actor {
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment: Option<Vec<ActorProperty>>,
}
@ -178,8 +184,8 @@ pub fn get_local_actor(
preferred_username: username.to_string(),
inbox,
outbox,
followers,
following,
followers: Some(followers),
following: Some(following),
public_key,
capabilities: Some(capabilities),
icon: avatar,
@ -189,3 +195,38 @@ pub fn get_local_actor(
};
Ok(actor)
}
pub fn get_instance_actor(
instance: &Instance,
) -> Result<Actor, ActorKeyError> {
let actor_id = instance.actor_id();
let actor_inbox = format!("{}/inbox", actor_id);
let actor_outbox = format!("{}/outbox", actor_id);
let public_key_pem = get_public_key_pem(&instance.actor_key)?;
let public_key = PublicKey {
id: instance.actor_key_id(),
owner: actor_id.clone(),
public_key_pem: public_key_pem,
};
let actor = Actor {
context: Some(json!([
AP_CONTEXT.to_string(),
W3ID_CONTEXT.to_string(),
])),
id: actor_id,
object_type: SERVICE.to_string(),
name: instance.host(),
preferred_username: instance.host(),
inbox: actor_inbox,
outbox: actor_outbox,
followers: None,
following: None,
public_key,
capabilities: None,
icon: None,
image: None,
summary: None,
attachment: None,
};
Ok(actor)
}

View file

@ -13,7 +13,7 @@ use crate::http_signatures::verify::verify_http_signature;
use crate::models::posts::queries::get_thread;
use crate::models::users::queries::get_user_by_name;
use super::activity::{create_note, OrderedCollection};
use super::actor::get_local_actor;
use super::actor::{get_local_actor, get_instance_actor};
use super::constants::ACTIVITY_CONTENT_TYPE;
use super::receiver::receive_activity;
@ -37,6 +37,10 @@ pub fn get_following_url(instance_url: &str, username: &str) -> String {
format!("{}/users/{}/following", instance_url, username)
}
pub fn get_instance_actor_url(instance_url: &str) -> String {
format!("{}/actor", instance_url)
}
pub fn get_object_url(instance_url: &str, object_uuid: &Uuid) -> String {
format!("{}/objects/{}", instance_url, object_uuid)
}
@ -55,7 +59,7 @@ fn is_activitypub_request(request: &HttpRequest) -> bool {
}
#[get("")]
async fn get_actor(
async fn actor_view(
config: web::Data<Config>,
db_pool: web::Data<Pool>,
request: HttpRequest,
@ -135,16 +139,28 @@ async fn following_collection(
Ok(response)
}
pub fn activitypub_scope() -> Scope {
pub fn actor_scope() -> Scope {
web::scope("/users/{username}")
.service(get_actor)
.service(actor_view)
.service(inbox)
.service(followers_collection)
.service(following_collection)
}
#[get("/actor")]
pub async fn instance_actor_view(
config: web::Data<Config>,
) -> Result<HttpResponse, HttpError> {
let actor = get_instance_actor(&config.instance())
.map_err(|_| HttpError::InternalError)?;
let response = HttpResponse::Ok()
.content_type(ACTIVITY_CONTENT_TYPE)
.json(actor);
Ok(response)
}
#[get("/objects/{object_id}")]
pub async fn get_object(
pub async fn object_view(
config: web::Data<Config>,
db_pool: web::Data<Pool>,
request: HttpRequest,

View file

@ -12,6 +12,7 @@ pub const UPDATE: &str = "Update";
// Actor types
pub const PERSON: &str = "Person";
pub const SERVICE: &str = "Service";
// Object types
pub const DOCUMENT: &str = "Document";

View file

@ -5,6 +5,7 @@ use rsa::RsaPrivateKey;
use serde::{de, Deserialize, Deserializer};
use url::Url;
use crate::activitypub::views::get_instance_actor_url;
use crate::errors::ConversionError;
use crate::utils::crypto::deserialize_private_key;
@ -152,6 +153,14 @@ impl Instance {
pub fn host(&self) -> String {
self._url.host_str().unwrap().to_string()
}
pub fn actor_id(&self) -> String {
get_instance_actor_url(&self.url())
}
pub fn actor_key_id(&self) -> String {
format!("{}#main-key", self.actor_id())
}
}
pub fn parse_config() -> Config {

View file

@ -5,7 +5,7 @@ use actix_web::{
middleware::Logger as ActixLogger,
};
use mitra::activitypub::views::{activitypub_scope, get_object};
use mitra::activitypub::views as activitypub;
use mitra::config::{Environment, parse_config};
use mitra::database::create_pool;
use mitra::database::migrate::apply_migrations;
@ -87,8 +87,9 @@ async fn main() -> std::io::Result<()> {
.service(search_api_scope())
.service(timeline_api_scope())
.service(webfinger::get_descriptor)
.service(activitypub_scope())
.service(get_object)
.service(activitypub::actor_scope())
.service(activitypub::instance_actor_view)
.service(activitypub::object_view)
.service(nodeinfo::get_nodeinfo)
.service(nodeinfo::get_nodeinfo_2_0);
if let Some(contract_dir) = &config.ethereum_contract_dir {

View file

@ -2,7 +2,7 @@ use actix_web::{get, web, HttpResponse};
use regex::Regex;
use tokio_postgres::GenericClient;
use crate::activitypub::views::get_actor_url;
use crate::activitypub::views::{get_actor_url, get_instance_actor_url};
use crate::activitypub::constants::ACTIVITY_CONTENT_TYPE;
use crate::config::{Config, Instance};
use crate::database::{Pool, get_database_client};
@ -22,7 +22,7 @@ async fn get_user_info(
) -> Result<JsonResourceDescriptor, HttpError> {
// Parse 'acct' URI
// https://datatracker.ietf.org/doc/html/rfc7565#section-7
let uri_regexp = Regex::new(r"acct:(?P<user>\w+)@(?P<instance>.+)").unwrap();
let uri_regexp = Regex::new(r"acct:(?P<user>[\w\.]+)@(?P<instance>.+)").unwrap();
let uri_caps = uri_regexp.captures(&query_params.resource)
.ok_or(ValidationError("invalid query target"))?;
let username = uri_caps.name("user")
@ -36,10 +36,14 @@ async fn get_user_info(
// Wrong instance
return Err(HttpError::NotFoundError("user"));
}
if !is_registered_user(db_client, username).await? {
return Err(HttpError::NotFoundError("user"));
}
let actor_url = get_actor_url(&instance.url(), username);
let actor_url = if username == instance.host() {
get_instance_actor_url(&instance.url())
} else {
if !is_registered_user(db_client, username).await? {
return Err(HttpError::NotFoundError("user"));
};
get_actor_url(&instance.url(), username)
};
let link = Link {
rel: "self".to_string(),
link_type: Some(ACTIVITY_CONTENT_TYPE.to_string()),