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::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use crate::config::Instance;
use crate::errors::ConversionError; use crate::errors::ConversionError;
use crate::models::profiles::types::{DbActorProfile, ExtraField}; use crate::models::profiles::types::{DbActorProfile, ExtraField};
use crate::models::users::types::User; use crate::models::users::types::User;
@ -14,7 +15,7 @@ use super::views::{
get_followers_url, get_followers_url,
get_following_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"; const W3ID_CONTEXT: &str = "https://w3id.org/security/v1";
@ -63,8 +64,12 @@ pub struct Actor {
pub preferred_username: String, pub preferred_username: String,
pub inbox: String, pub inbox: String,
pub outbox: 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, pub public_key: PublicKey,
@ -80,6 +85,7 @@ pub struct Actor {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>, pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment: Option<Vec<ActorProperty>>, pub attachment: Option<Vec<ActorProperty>>,
} }
@ -178,8 +184,8 @@ pub fn get_local_actor(
preferred_username: username.to_string(), preferred_username: username.to_string(),
inbox, inbox,
outbox, outbox,
followers, followers: Some(followers),
following, following: Some(following),
public_key, public_key,
capabilities: Some(capabilities), capabilities: Some(capabilities),
icon: avatar, icon: avatar,
@ -189,3 +195,38 @@ pub fn get_local_actor(
}; };
Ok(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::posts::queries::get_thread;
use crate::models::users::queries::get_user_by_name; use crate::models::users::queries::get_user_by_name;
use super::activity::{create_note, OrderedCollection}; 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::constants::ACTIVITY_CONTENT_TYPE;
use super::receiver::receive_activity; 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) 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 { pub fn get_object_url(instance_url: &str, object_uuid: &Uuid) -> String {
format!("{}/objects/{}", instance_url, object_uuid) format!("{}/objects/{}", instance_url, object_uuid)
} }
@ -55,7 +59,7 @@ fn is_activitypub_request(request: &HttpRequest) -> bool {
} }
#[get("")] #[get("")]
async fn get_actor( async fn actor_view(
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<Pool>, db_pool: web::Data<Pool>,
request: HttpRequest, request: HttpRequest,
@ -135,16 +139,28 @@ async fn following_collection(
Ok(response) Ok(response)
} }
pub fn activitypub_scope() -> Scope { pub fn actor_scope() -> Scope {
web::scope("/users/{username}") web::scope("/users/{username}")
.service(get_actor) .service(actor_view)
.service(inbox) .service(inbox)
.service(followers_collection) .service(followers_collection)
.service(following_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}")] #[get("/objects/{object_id}")]
pub async fn get_object( pub async fn object_view(
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<Pool>, db_pool: web::Data<Pool>,
request: HttpRequest, request: HttpRequest,

View file

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

View file

@ -5,6 +5,7 @@ use rsa::RsaPrivateKey;
use serde::{de, Deserialize, Deserializer}; use serde::{de, Deserialize, Deserializer};
use url::Url; use url::Url;
use crate::activitypub::views::get_instance_actor_url;
use crate::errors::ConversionError; use crate::errors::ConversionError;
use crate::utils::crypto::deserialize_private_key; use crate::utils::crypto::deserialize_private_key;
@ -152,6 +153,14 @@ impl Instance {
pub fn host(&self) -> String { pub fn host(&self) -> String {
self._url.host_str().unwrap().to_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 { pub fn parse_config() -> Config {

View file

@ -5,7 +5,7 @@ use actix_web::{
middleware::Logger as ActixLogger, 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::config::{Environment, parse_config};
use mitra::database::create_pool; use mitra::database::create_pool;
use mitra::database::migrate::apply_migrations; use mitra::database::migrate::apply_migrations;
@ -87,8 +87,9 @@ async fn main() -> std::io::Result<()> {
.service(search_api_scope()) .service(search_api_scope())
.service(timeline_api_scope()) .service(timeline_api_scope())
.service(webfinger::get_descriptor) .service(webfinger::get_descriptor)
.service(activitypub_scope()) .service(activitypub::actor_scope())
.service(get_object) .service(activitypub::instance_actor_view)
.service(activitypub::object_view)
.service(nodeinfo::get_nodeinfo) .service(nodeinfo::get_nodeinfo)
.service(nodeinfo::get_nodeinfo_2_0); .service(nodeinfo::get_nodeinfo_2_0);
if let Some(contract_dir) = &config.ethereum_contract_dir { 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 regex::Regex;
use tokio_postgres::GenericClient; 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::activitypub::constants::ACTIVITY_CONTENT_TYPE;
use crate::config::{Config, Instance}; use crate::config::{Config, Instance};
use crate::database::{Pool, get_database_client}; use crate::database::{Pool, get_database_client};
@ -22,7 +22,7 @@ async fn get_user_info(
) -> Result<JsonResourceDescriptor, HttpError> { ) -> Result<JsonResourceDescriptor, HttpError> {
// Parse 'acct' URI // Parse 'acct' URI
// https://datatracker.ietf.org/doc/html/rfc7565#section-7 // 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) let uri_caps = uri_regexp.captures(&query_params.resource)
.ok_or(ValidationError("invalid query target"))?; .ok_or(ValidationError("invalid query target"))?;
let username = uri_caps.name("user") let username = uri_caps.name("user")
@ -36,10 +36,14 @@ async fn get_user_info(
// Wrong instance // Wrong instance
return Err(HttpError::NotFoundError("user")); return Err(HttpError::NotFoundError("user"));
} }
if !is_registered_user(db_client, username).await? { let actor_url = if username == instance.host() {
return Err(HttpError::NotFoundError("user")); get_instance_actor_url(&instance.url())
} } else {
let actor_url = get_actor_url(&instance.url(), username); if !is_registered_user(db_client, username).await? {
return Err(HttpError::NotFoundError("user"));
};
get_actor_url(&instance.url(), username)
};
let link = Link { let link = Link {
rel: "self".to_string(), rel: "self".to_string(),
link_type: Some(ACTIVITY_CONTENT_TYPE.to_string()), link_type: Some(ACTIVITY_CONTENT_TYPE.to_string()),