Add API methods for creating user-signed Move() activities
This commit is contained in:
parent
73a7668d18
commit
4a42bcd369
5 changed files with 232 additions and 7 deletions
|
@ -175,6 +175,42 @@ paths:
|
|||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Update"}'
|
||||
/api/v1/accounts/move_followers:
|
||||
post:
|
||||
summary: Build Move(Person) activity for signing (experimental).
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
from_actor_id:
|
||||
description: The actor ID to move from.
|
||||
type: string
|
||||
example: 'https://xyz.com/users/test'
|
||||
followers_csv:
|
||||
description: The list of followers in CSV format.
|
||||
type: string
|
||||
example: |
|
||||
user1@example.org
|
||||
user2@test.com
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
params:
|
||||
description: Activity parameters
|
||||
$ref: '#/components/schemas/ActivityParameters'
|
||||
message:
|
||||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Move"}'
|
||||
400:
|
||||
description: Invalid data.
|
||||
/api/v1/accounts/send_activity:
|
||||
post:
|
||||
summary: Send signed activity (experimental).
|
||||
|
@ -1184,6 +1220,7 @@ components:
|
|||
description: Activity type
|
||||
type: string
|
||||
enum:
|
||||
- move
|
||||
- update
|
||||
Attachment:
|
||||
type: object
|
||||
|
|
|
@ -6,6 +6,7 @@ pub mod delete_note;
|
|||
pub mod delete_person;
|
||||
pub mod follow;
|
||||
pub mod like_note;
|
||||
pub mod move_person;
|
||||
pub mod remove_person;
|
||||
pub mod undo_announce_note;
|
||||
pub mod undo_follow;
|
||||
|
|
77
src/activitypub/builders/move_person.rs
Normal file
77
src/activitypub/builders/move_person.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::activitypub::{
|
||||
actors::types::Actor,
|
||||
constants::AP_CONTEXT,
|
||||
deliverer::OutgoingActivity,
|
||||
identifiers::{local_actor_id, local_object_id},
|
||||
vocabulary::MOVE,
|
||||
};
|
||||
use crate::config::Instance;
|
||||
use crate::errors::ConversionError;
|
||||
use crate::models::users::types::User;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MovePerson {
|
||||
#[serde(rename = "@context")]
|
||||
context: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
activity_type: String,
|
||||
|
||||
id: String,
|
||||
actor: String,
|
||||
object: String,
|
||||
target: String,
|
||||
|
||||
to: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn build_move_person(
|
||||
instance_url: &str,
|
||||
sender: &User,
|
||||
from_actor_id: &str,
|
||||
followers: &[String],
|
||||
internal_activity_id: &Uuid,
|
||||
) -> MovePerson {
|
||||
let activity_id = local_object_id(instance_url, internal_activity_id);
|
||||
let actor_id = local_actor_id(instance_url, &sender.profile.username);
|
||||
MovePerson {
|
||||
context: AP_CONTEXT.to_string(),
|
||||
activity_type: MOVE.to_string(),
|
||||
id: activity_id,
|
||||
actor: actor_id.clone(),
|
||||
object: from_actor_id.to_string(),
|
||||
target: actor_id,
|
||||
to: followers.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_signed_move_person(
|
||||
instance: &Instance,
|
||||
sender: &User,
|
||||
from_actor_id: &str,
|
||||
followers: Vec<Actor>,
|
||||
internal_activity_id: &Uuid,
|
||||
) -> Result<OutgoingActivity<Value>, ConversionError> {
|
||||
let followers_ids: Vec<String> = followers.iter()
|
||||
.map(|actor| actor.id.clone())
|
||||
.collect();
|
||||
let activity = build_move_person(
|
||||
&instance.url(),
|
||||
sender,
|
||||
from_actor_id,
|
||||
&followers_ids,
|
||||
internal_activity_id,
|
||||
);
|
||||
let activity_value = serde_json::to_value(activity)
|
||||
.map_err(|_| ConversionError)?;
|
||||
Ok(OutgoingActivity {
|
||||
instance: instance.clone(),
|
||||
sender: sender.clone(),
|
||||
activity: activity_value,
|
||||
recipients: followers,
|
||||
})
|
||||
}
|
|
@ -262,9 +262,20 @@ impl AccountUpdateData {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MoveFollowersRequest {
|
||||
pub from_actor_id: String,
|
||||
pub followers_csv: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||
pub enum ActivityParams {
|
||||
Move {
|
||||
internal_activity_id: Uuid,
|
||||
from_actor_id: String,
|
||||
followers: Vec<String>,
|
||||
},
|
||||
Update { internal_activity_id: Uuid },
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use actix_web::{
|
||||
get, patch, post, web,
|
||||
HttpRequest, HttpResponse, Scope,
|
||||
|
@ -5,8 +7,13 @@ use actix_web::{
|
|||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::activitypub::actors::types::ActorAddress;
|
||||
use crate::activitypub::builders::{
|
||||
follow::prepare_follow,
|
||||
move_person::{
|
||||
build_move_person,
|
||||
prepare_signed_move_person,
|
||||
},
|
||||
undo_follow::prepare_undo_follow,
|
||||
update_person::{
|
||||
build_update_person,
|
||||
|
@ -45,7 +52,9 @@ use crate::mastodon_api::statuses::helpers::build_status_list;
|
|||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::models::posts::queries::get_posts_by_author;
|
||||
use crate::models::profiles::queries::{
|
||||
get_profile_by_acct,
|
||||
get_profile_by_id,
|
||||
get_profile_by_remote_actor_id,
|
||||
search_profiles_by_did,
|
||||
update_profile,
|
||||
};
|
||||
|
@ -95,6 +104,7 @@ use super::types::{
|
|||
IdentityClaim,
|
||||
IdentityClaimQueryParams,
|
||||
IdentityProofData,
|
||||
MoveFollowersRequest,
|
||||
RelationshipQueryParams,
|
||||
SearchAcctQueryParams,
|
||||
SearchDidQueryParams,
|
||||
|
@ -254,6 +264,71 @@ async fn get_unsigned_update(
|
|||
Ok(HttpResponse::Ok().json(data))
|
||||
}
|
||||
|
||||
#[post("/move_followers")]
|
||||
async fn move_followers(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
request_data: web::Json<MoveFollowersRequest>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
// Old profile could be deleted
|
||||
let maybe_from_profile = match get_profile_by_remote_actor_id(
|
||||
db_client,
|
||||
&request_data.from_actor_id,
|
||||
).await {
|
||||
Ok(profile) => Some(profile),
|
||||
Err(DatabaseError::NotFound(_)) => None,
|
||||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
let mut followers = vec![];
|
||||
for follower_address in request_data.followers_csv.lines() {
|
||||
let follower_acct = ActorAddress::from_str(follower_address)?
|
||||
.acct(&config.instance().hostname());
|
||||
// TODO: fetch unknown profiles
|
||||
let follower = get_profile_by_acct(db_client, &follower_acct).await?;
|
||||
if let Some(remote_actor) = follower.actor_json {
|
||||
// Add remote actor to activity recipients list
|
||||
followers.push(remote_actor.id);
|
||||
} else {
|
||||
// Immediately move local followers
|
||||
if let Some(ref from_profile) = maybe_from_profile {
|
||||
match unfollow(db_client, &follower.id, &from_profile.id).await {
|
||||
Ok(_) => (),
|
||||
Err(DatabaseError::NotFound(_)) => (),
|
||||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
};
|
||||
match follow(db_client, &follower.id, ¤t_user.id).await {
|
||||
Ok(_) => (),
|
||||
// Ignore if already following
|
||||
Err(DatabaseError::AlreadyExists(_)) => (),
|
||||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
};
|
||||
};
|
||||
let internal_activity_id = new_uuid();
|
||||
let activity = build_move_person(
|
||||
&config.instance_url(),
|
||||
¤t_user,
|
||||
&request_data.from_actor_id,
|
||||
&followers,
|
||||
&internal_activity_id,
|
||||
);
|
||||
let canonical_json = canonicalize_object(&activity)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let data = UnsignedActivity {
|
||||
params: ActivityParams::Move {
|
||||
internal_activity_id,
|
||||
from_actor_id: request_data.from_actor_id.clone(),
|
||||
followers,
|
||||
},
|
||||
message: canonical_json,
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(data))
|
||||
}
|
||||
|
||||
#[post("/send_activity")]
|
||||
async fn send_signed_activity(
|
||||
auth: BearerAuth,
|
||||
|
@ -268,13 +343,36 @@ async fn send_signed_activity(
|
|||
if !current_user.profile.identity_proofs.any(&signer) {
|
||||
return Err(ValidationError("unknown signer").into());
|
||||
};
|
||||
let ActivityParams::Update { internal_activity_id } = data.params;
|
||||
let mut outgoing_activity = prepare_signed_update_person(
|
||||
let mut outgoing_activity = match &data.params {
|
||||
ActivityParams::Move {
|
||||
internal_activity_id,
|
||||
from_actor_id,
|
||||
followers: followers_ids,
|
||||
} => {
|
||||
let mut followers = vec![];
|
||||
for actor_id in followers_ids {
|
||||
let remote_actor = get_profile_by_remote_actor_id(db_client, actor_id)
|
||||
.await?
|
||||
.actor_json.ok_or(HttpError::InternalError)?;
|
||||
followers.push(remote_actor);
|
||||
};
|
||||
prepare_signed_move_person(
|
||||
&config.instance(),
|
||||
¤t_user,
|
||||
from_actor_id,
|
||||
followers,
|
||||
internal_activity_id,
|
||||
).map_err(|_| HttpError::InternalError)?
|
||||
},
|
||||
ActivityParams::Update { internal_activity_id } => {
|
||||
prepare_signed_update_person(
|
||||
db_client,
|
||||
&config.instance(),
|
||||
¤t_user,
|
||||
internal_activity_id,
|
||||
).await.map_err(|_| HttpError::InternalError)?;
|
||||
*internal_activity_id,
|
||||
).await.map_err(|_| HttpError::InternalError)?
|
||||
},
|
||||
};
|
||||
let canonical_json = canonicalize_object(&outgoing_activity.activity)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let proof = match signer {
|
||||
|
@ -707,6 +805,7 @@ pub fn account_api_scope() -> Scope {
|
|||
.service(verify_credentials)
|
||||
.service(update_credentials)
|
||||
.service(get_unsigned_update)
|
||||
.service(move_followers)
|
||||
.service(send_signed_activity)
|
||||
.service(get_identity_claim)
|
||||
.service(create_identity_proof)
|
||||
|
|
Loading…
Reference in a new issue