Create instance actor
This commit is contained in:
parent
52b51501d5
commit
d935b843a8
6 changed files with 91 additions and 19 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
Loading…
Reference in a new issue