Make /api/v1/accounts/{account_id}/follow work with form-data
This commit is contained in:
parent
e2ea58d33a
commit
7471c03ed1
2 changed files with 144 additions and 254 deletions
|
@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Ignore errors when importing activities from outbox.
|
- Ignore errors when importing activities from outbox.
|
||||||
- Make activity limit in outbox fetcher adjustable.
|
- Make activity limit in outbox fetcher adjustable.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Make `/api/v1/accounts/{account_id}/follow` work with form-data.
|
||||||
|
|
||||||
## [1.21.0] - 2023-04-12
|
## [1.21.0] - 2023-04-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
use actix_web::{
|
use actix_web::{dev::ConnectionInfo, get, patch, post, web, HttpRequest, HttpResponse, Scope};
|
||||||
dev::ConnectionInfo,
|
|
||||||
get,
|
|
||||||
patch,
|
|
||||||
post,
|
|
||||||
web,
|
|
||||||
HttpRequest,
|
|
||||||
HttpResponse,
|
|
||||||
Scope,
|
|
||||||
};
|
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -17,40 +8,21 @@ use mitra_models::{
|
||||||
posts::queries::get_posts_by_author,
|
posts::queries::get_posts_by_author,
|
||||||
profiles::helpers::find_verified_aliases,
|
profiles::helpers::find_verified_aliases,
|
||||||
profiles::queries::{
|
profiles::queries::{
|
||||||
get_profile_by_acct,
|
get_profile_by_acct, get_profile_by_id, search_profiles_by_did, update_profile,
|
||||||
get_profile_by_id,
|
|
||||||
search_profiles_by_did,
|
|
||||||
update_profile,
|
|
||||||
},
|
|
||||||
profiles::types::{
|
|
||||||
IdentityProof,
|
|
||||||
IdentityProofType,
|
|
||||||
ProfileUpdateData,
|
|
||||||
},
|
},
|
||||||
|
profiles::types::{IdentityProof, IdentityProofType, ProfileUpdateData},
|
||||||
relationships::queries::{
|
relationships::queries::{
|
||||||
get_followers_paginated,
|
get_followers_paginated, get_following_paginated, hide_replies, hide_reposts, show_replies,
|
||||||
get_following_paginated,
|
show_reposts, unfollow,
|
||||||
hide_replies,
|
|
||||||
hide_reposts,
|
|
||||||
show_replies,
|
|
||||||
show_reposts,
|
|
||||||
unfollow,
|
|
||||||
},
|
},
|
||||||
subscriptions::queries::get_incoming_subscriptions,
|
subscriptions::queries::get_incoming_subscriptions,
|
||||||
users::queries::{
|
users::queries::{create_user, get_user_by_did, is_valid_invite_code},
|
||||||
create_user,
|
|
||||||
get_user_by_did,
|
|
||||||
is_valid_invite_code,
|
|
||||||
},
|
|
||||||
users::types::{Role, UserCreateData},
|
users::types::{Role, UserCreateData},
|
||||||
};
|
};
|
||||||
use mitra_utils::{
|
use mitra_utils::{
|
||||||
caip2::ChainId,
|
caip2::ChainId,
|
||||||
canonicalization::canonicalize_object,
|
canonicalization::canonicalize_object,
|
||||||
crypto_rsa::{
|
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||||
generate_rsa_key,
|
|
||||||
serialize_private_key,
|
|
||||||
},
|
|
||||||
currencies::Currency,
|
currencies::Currency,
|
||||||
did::Did,
|
did::Did,
|
||||||
did_pkh::DidPkh,
|
did_pkh::DidPkh,
|
||||||
|
@ -58,69 +30,37 @@ use mitra_utils::{
|
||||||
passwords::hash_password,
|
passwords::hash_password,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::helpers::{get_aliases, get_relationship};
|
||||||
|
use super::types::{
|
||||||
|
Account, AccountCreateData, AccountUpdateData, ActivityParams, ApiSubscription, FollowData,
|
||||||
|
FollowListQueryParams, IdentityClaim, IdentityClaimQueryParams, IdentityProofData,
|
||||||
|
LookupAcctQueryParams, RelationshipQueryParams, SearchAcctQueryParams, SearchDidQueryParams,
|
||||||
|
SignedActivity, StatusListQueryParams, UnsignedActivity,
|
||||||
|
};
|
||||||
use crate::activitypub::{
|
use crate::activitypub::{
|
||||||
builders::{
|
builders::{
|
||||||
follow::follow_or_create_request,
|
follow::follow_or_create_request,
|
||||||
undo_follow::prepare_undo_follow,
|
undo_follow::prepare_undo_follow,
|
||||||
update_person::{
|
update_person::{build_update_person, prepare_update_person},
|
||||||
build_update_person,
|
|
||||||
prepare_update_person,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
identifiers::local_actor_id,
|
identifiers::local_actor_id,
|
||||||
};
|
};
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::http::get_request_base_url;
|
use crate::http::{get_request_base_url, FormOrJson};
|
||||||
use crate::identity::{
|
use crate::identity::{
|
||||||
claims::create_identity_claim,
|
claims::create_identity_claim,
|
||||||
minisign::{
|
minisign::{minisign_key_to_did, parse_minisign_signature, verify_minisign_signature},
|
||||||
minisign_key_to_did,
|
|
||||||
parse_minisign_signature,
|
|
||||||
verify_minisign_signature,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use crate::json_signatures::{
|
use crate::json_signatures::{
|
||||||
create::{add_integrity_proof, IntegrityProof},
|
create::{add_integrity_proof, IntegrityProof},
|
||||||
verify::{
|
verify::{verify_ed25519_json_signature, verify_eip191_json_signature},
|
||||||
verify_ed25519_json_signature,
|
|
||||||
verify_eip191_json_signature,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use crate::mastodon_api::{
|
use crate::mastodon_api::{
|
||||||
errors::MastodonError,
|
errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response,
|
||||||
oauth::auth::get_current_user,
|
search::helpers::search_profiles_only, statuses::helpers::build_status_list,
|
||||||
pagination::get_paginated_response,
|
|
||||||
search::helpers::search_profiles_only,
|
|
||||||
statuses::helpers::build_status_list,
|
|
||||||
statuses::types::Status,
|
statuses::types::Status,
|
||||||
};
|
};
|
||||||
use crate::validators::{
|
use crate::validators::{profiles::clean_profile_update_data, users::validate_local_username};
|
||||||
profiles::clean_profile_update_data,
|
|
||||||
users::validate_local_username,
|
|
||||||
};
|
|
||||||
use super::helpers::{
|
|
||||||
get_aliases,
|
|
||||||
get_relationship,
|
|
||||||
};
|
|
||||||
use super::types::{
|
|
||||||
Account,
|
|
||||||
AccountCreateData,
|
|
||||||
AccountUpdateData,
|
|
||||||
ActivityParams,
|
|
||||||
ApiSubscription,
|
|
||||||
FollowData,
|
|
||||||
FollowListQueryParams,
|
|
||||||
IdentityClaim,
|
|
||||||
IdentityClaimQueryParams,
|
|
||||||
IdentityProofData,
|
|
||||||
LookupAcctQueryParams,
|
|
||||||
RelationshipQueryParams,
|
|
||||||
SearchAcctQueryParams,
|
|
||||||
SearchDidQueryParams,
|
|
||||||
SignedActivity,
|
|
||||||
StatusListQueryParams,
|
|
||||||
UnsignedActivity,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[post("")]
|
#[post("")]
|
||||||
pub async fn create_account(
|
pub async fn create_account(
|
||||||
|
@ -132,7 +72,9 @@ pub async fn create_account(
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
// Validate
|
// Validate
|
||||||
if config.registration.registration_type == RegistrationType::Invite {
|
if config.registration.registration_type == RegistrationType::Invite {
|
||||||
let invite_code = account_data.invite_code.as_ref()
|
let invite_code = account_data
|
||||||
|
.invite_code
|
||||||
|
.as_ref()
|
||||||
.ok_or(ValidationError("invite code is required"))?;
|
.ok_or(ValidationError("invite code is required"))?;
|
||||||
if !is_valid_invite_code(db_client, invite_code).await? {
|
if !is_valid_invite_code(db_client, invite_code).await? {
|
||||||
return Err(ValidationError("invalid invite code").into());
|
return Err(ValidationError("invalid invite code").into());
|
||||||
|
@ -144,8 +86,7 @@ pub async fn create_account(
|
||||||
return Err(ValidationError("password or EIP-4361 message is required").into());
|
return Err(ValidationError("password or EIP-4361 message is required").into());
|
||||||
};
|
};
|
||||||
let maybe_password_hash = if let Some(password) = account_data.password.as_ref() {
|
let maybe_password_hash = if let Some(password) = account_data.password.as_ref() {
|
||||||
let password_hash = hash_password(password)
|
let password_hash = hash_password(password).map_err(|_| MastodonError::InternalError)?;
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
|
||||||
Some(password_hash)
|
Some(password_hash)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -156,11 +97,14 @@ pub async fn create_account(
|
||||||
Ok(Ok(private_key)) => private_key,
|
Ok(Ok(private_key)) => private_key,
|
||||||
_ => return Err(MastodonError::InternalError),
|
_ => return Err(MastodonError::InternalError),
|
||||||
};
|
};
|
||||||
let private_key_pem = serialize_private_key(&private_key)
|
let private_key_pem =
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
serialize_private_key(&private_key).map_err(|_| MastodonError::InternalError)?;
|
||||||
|
|
||||||
let AccountCreateData { username, invite_code, .. } =
|
let AccountCreateData {
|
||||||
account_data.into_inner();
|
username,
|
||||||
|
invite_code,
|
||||||
|
..
|
||||||
|
} = account_data.into_inner();
|
||||||
let role = match config.registration.default_role {
|
let role = match config.registration.default_role {
|
||||||
DefaultRole::NormalUser => Role::NormalUser,
|
DefaultRole::NormalUser => Role::NormalUser,
|
||||||
DefaultRole::ReadOnlyUser => Role::ReadOnlyUser,
|
DefaultRole::ReadOnlyUser => Role::ReadOnlyUser,
|
||||||
|
@ -175,8 +119,9 @@ pub async fn create_account(
|
||||||
};
|
};
|
||||||
let user = match create_user(db_client, user_data).await {
|
let user = match create_user(db_client, user_data).await {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(DatabaseError::AlreadyExists(_)) =>
|
Err(DatabaseError::AlreadyExists(_)) => {
|
||||||
return Err(ValidationError("user already exists").into()),
|
return Err(ValidationError("user already exists").into())
|
||||||
|
}
|
||||||
Err(other_error) => return Err(other_error.into()),
|
Err(other_error) => return Err(other_error.into()),
|
||||||
};
|
};
|
||||||
log::warn!("created user {}", user.id);
|
log::warn!("created user {}", user.id);
|
||||||
|
@ -215,25 +160,17 @@ async fn update_credentials(
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let mut profile_data = account_data.into_inner()
|
let mut profile_data = account_data
|
||||||
.into_profile_data(
|
.into_inner()
|
||||||
¤t_user.profile,
|
.into_profile_data(¤t_user.profile, &config.media_dir())?;
|
||||||
&config.media_dir(),
|
|
||||||
)?;
|
|
||||||
clean_profile_update_data(&mut profile_data)?;
|
clean_profile_update_data(&mut profile_data)?;
|
||||||
current_user.profile = update_profile(
|
current_user.profile = update_profile(db_client, ¤t_user.id, profile_data).await?;
|
||||||
db_client,
|
|
||||||
¤t_user.id,
|
|
||||||
profile_data,
|
|
||||||
).await?;
|
|
||||||
|
|
||||||
// Federate
|
// Federate
|
||||||
prepare_update_person(
|
prepare_update_person(db_client, &config.instance(), ¤t_user, None)
|
||||||
db_client,
|
.await?
|
||||||
&config.instance(),
|
.enqueue(db_client)
|
||||||
¤t_user,
|
.await?;
|
||||||
None,
|
|
||||||
).await?.enqueue(db_client).await?;
|
|
||||||
|
|
||||||
let account = Account::from_user(
|
let account = Account::from_user(
|
||||||
&get_request_base_url(connection_info),
|
&get_request_base_url(connection_info),
|
||||||
|
@ -256,11 +193,14 @@ async fn get_unsigned_update(
|
||||||
&config.instance_url(),
|
&config.instance_url(),
|
||||||
¤t_user,
|
¤t_user,
|
||||||
Some(internal_activity_id),
|
Some(internal_activity_id),
|
||||||
).map_err(|_| MastodonError::InternalError)?;
|
)
|
||||||
let canonical_json = canonicalize_object(&activity)
|
.map_err(|_| MastodonError::InternalError)?;
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
let canonical_json =
|
||||||
|
canonicalize_object(&activity).map_err(|_| MastodonError::InternalError)?;
|
||||||
let data = UnsignedActivity {
|
let data = UnsignedActivity {
|
||||||
params: ActivityParams::Update { internal_activity_id },
|
params: ActivityParams::Update {
|
||||||
|
internal_activity_id,
|
||||||
|
},
|
||||||
message: canonical_json,
|
message: canonical_json,
|
||||||
};
|
};
|
||||||
Ok(HttpResponse::Ok().json(data))
|
Ok(HttpResponse::Ok().json(data))
|
||||||
|
@ -276,20 +216,24 @@ async fn send_signed_activity(
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let signer = data.signer.parse::<Did>()
|
let signer = data
|
||||||
|
.signer
|
||||||
|
.parse::<Did>()
|
||||||
.map_err(|_| ValidationError("invalid DID"))?;
|
.map_err(|_| ValidationError("invalid DID"))?;
|
||||||
if !current_user.profile.identity_proofs.any(&signer) {
|
if !current_user.profile.identity_proofs.any(&signer) {
|
||||||
return Err(ValidationError("unknown signer").into());
|
return Err(ValidationError("unknown signer").into());
|
||||||
};
|
};
|
||||||
let mut outgoing_activity = match &data.params {
|
let mut outgoing_activity = match &data.params {
|
||||||
ActivityParams::Update { internal_activity_id } => {
|
ActivityParams::Update {
|
||||||
prepare_update_person(
|
internal_activity_id,
|
||||||
db_client,
|
} => prepare_update_person(
|
||||||
&config.instance(),
|
db_client,
|
||||||
¤t_user,
|
&config.instance(),
|
||||||
Some(*internal_activity_id),
|
¤t_user,
|
||||||
).await.map_err(|_| MastodonError::InternalError)?
|
Some(*internal_activity_id),
|
||||||
},
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| MastodonError::InternalError)?,
|
||||||
};
|
};
|
||||||
let canonical_json = canonicalize_object(&outgoing_activity.activity)
|
let canonical_json = canonicalize_object(&outgoing_activity.activity)
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
.map_err(|_| MastodonError::InternalError)?;
|
||||||
|
@ -300,14 +244,14 @@ async fn send_signed_activity(
|
||||||
verify_ed25519_json_signature(&signer, &canonical_json, &signature_bin)
|
verify_ed25519_json_signature(&signer, &canonical_json, &signature_bin)
|
||||||
.map_err(|_| ValidationError("invalid signature"))?;
|
.map_err(|_| ValidationError("invalid signature"))?;
|
||||||
IntegrityProof::jcs_ed25519(&signer, &signature_bin)
|
IntegrityProof::jcs_ed25519(&signer, &signature_bin)
|
||||||
},
|
}
|
||||||
Did::Pkh(signer) => {
|
Did::Pkh(signer) => {
|
||||||
let signature_bin = hex::decode(&data.signature)
|
let signature_bin =
|
||||||
.map_err(|_| ValidationError("invalid encoding"))?;
|
hex::decode(&data.signature).map_err(|_| ValidationError("invalid encoding"))?;
|
||||||
verify_eip191_json_signature(&signer, &canonical_json, &signature_bin)
|
verify_eip191_json_signature(&signer, &canonical_json, &signature_bin)
|
||||||
.map_err(|_| ValidationError("invalid signature"))?;
|
.map_err(|_| ValidationError("invalid signature"))?;
|
||||||
IntegrityProof::jcs_eip191(&signer, &signature_bin)
|
IntegrityProof::jcs_eip191(&signer, &signature_bin)
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
add_integrity_proof(&mut outgoing_activity.activity, proof)
|
add_integrity_proof(&mut outgoing_activity.activity, proof)
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
.map_err(|_| MastodonError::InternalError)?;
|
||||||
|
@ -333,25 +277,18 @@ async fn get_identity_claim(
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let did = match query_params.proof_type.as_str() {
|
let did = match query_params.proof_type.as_str() {
|
||||||
"ethereum" => {
|
"ethereum" => {
|
||||||
let did_pkh = DidPkh::from_address(
|
let did_pkh = DidPkh::from_address(&Currency::Ethereum, &query_params.signer);
|
||||||
&Currency::Ethereum,
|
|
||||||
&query_params.signer,
|
|
||||||
);
|
|
||||||
Did::Pkh(did_pkh)
|
Did::Pkh(did_pkh)
|
||||||
},
|
}
|
||||||
"minisign" => {
|
"minisign" => {
|
||||||
let did_key = minisign_key_to_did(&query_params.signer)
|
let did_key = minisign_key_to_did(&query_params.signer)
|
||||||
.map_err(|_| ValidationError("invalid key"))?;
|
.map_err(|_| ValidationError("invalid key"))?;
|
||||||
Did::Key(did_key)
|
Did::Key(did_key)
|
||||||
},
|
}
|
||||||
_ => return Err(ValidationError("unknown proof type").into()),
|
_ => return Err(ValidationError("unknown proof type").into()),
|
||||||
};
|
};
|
||||||
let actor_id = local_actor_id(
|
let actor_id = local_actor_id(&config.instance_url(), ¤t_user.profile.username);
|
||||||
&config.instance_url(),
|
let claim = create_identity_claim(&actor_id, &did).map_err(|_| MastodonError::InternalError)?;
|
||||||
¤t_user.profile.username,
|
|
||||||
);
|
|
||||||
let claim = create_identity_claim(&actor_id, &did)
|
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
|
||||||
let response = IdentityClaim { did, claim };
|
let response = IdentityClaim { did, claim };
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
|
@ -366,7 +303,9 @@ async fn create_identity_proof(
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let did = proof_data.did.parse::<Did>()
|
let did = proof_data
|
||||||
|
.did
|
||||||
|
.parse::<Did>()
|
||||||
.map_err(|_| ValidationError("invalid DID"))?;
|
.map_err(|_| ValidationError("invalid DID"))?;
|
||||||
// Reject proof if there's another local user with the same DID.
|
// Reject proof if there's another local user with the same DID.
|
||||||
// This is needed for matching ethereum subscriptions
|
// This is needed for matching ethereum subscriptions
|
||||||
|
@ -375,37 +314,30 @@ async fn create_identity_proof(
|
||||||
if user.id != current_user.id {
|
if user.id != current_user.id {
|
||||||
return Err(ValidationError("DID already associated with another user").into());
|
return Err(ValidationError("DID already associated with another user").into());
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Err(DatabaseError::NotFound(_)) => (),
|
Err(DatabaseError::NotFound(_)) => (),
|
||||||
Err(other_error) => return Err(other_error.into()),
|
Err(other_error) => return Err(other_error.into()),
|
||||||
};
|
};
|
||||||
let actor_id = local_actor_id(
|
let actor_id = local_actor_id(&config.instance_url(), ¤t_user.profile.username);
|
||||||
&config.instance_url(),
|
let message =
|
||||||
¤t_user.profile.username,
|
create_identity_claim(&actor_id, &did).map_err(|_| ValidationError("invalid claim"))?;
|
||||||
);
|
|
||||||
let message = create_identity_claim(&actor_id, &did)
|
|
||||||
.map_err(|_| ValidationError("invalid claim"))?;
|
|
||||||
|
|
||||||
// Verify proof
|
// Verify proof
|
||||||
let proof_type = match did {
|
let proof_type = match did {
|
||||||
Did::Key(ref did_key) => {
|
Did::Key(ref did_key) => {
|
||||||
let signature_bin = parse_minisign_signature(&proof_data.signature)
|
let signature_bin = parse_minisign_signature(&proof_data.signature)
|
||||||
.map_err(|_| ValidationError("invalid signature encoding"))?;
|
.map_err(|_| ValidationError("invalid signature encoding"))?;
|
||||||
verify_minisign_signature(
|
verify_minisign_signature(did_key, &message, &signature_bin)
|
||||||
did_key,
|
.map_err(|_| ValidationError("invalid signature"))?;
|
||||||
&message,
|
|
||||||
&signature_bin,
|
|
||||||
).map_err(|_| ValidationError("invalid signature"))?;
|
|
||||||
IdentityProofType::LegacyMinisignIdentityProof
|
IdentityProofType::LegacyMinisignIdentityProof
|
||||||
},
|
}
|
||||||
Did::Pkh(ref did_pkh) => {
|
Did::Pkh(ref did_pkh) => {
|
||||||
if did_pkh.chain_id != ChainId::ethereum_mainnet() {
|
if did_pkh.chain_id != ChainId::ethereum_mainnet() {
|
||||||
// DID must point to Ethereum Mainnet because it is a valid
|
// DID must point to Ethereum Mainnet because it is a valid
|
||||||
// identifier on any Ethereum chain
|
// identifier on any Ethereum chain
|
||||||
return Err(ValidationError("unsupported chain ID").into());
|
return Err(ValidationError("unsupported chain ID").into());
|
||||||
};
|
};
|
||||||
let maybe_public_address =
|
let maybe_public_address = current_user.public_wallet_address(&Currency::Ethereum);
|
||||||
current_user.public_wallet_address(&Currency::Ethereum);
|
|
||||||
if let Some(address) = maybe_public_address {
|
if let Some(address) = maybe_public_address {
|
||||||
// Do not allow to add more than one address proof
|
// Do not allow to add more than one address proof
|
||||||
if did_pkh.address != address {
|
if did_pkh.address != address {
|
||||||
|
@ -413,7 +345,7 @@ async fn create_identity_proof(
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return Err(ValidationError("invalid signature").into());
|
return Err(ValidationError("invalid signature").into());
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let proof = IdentityProof {
|
let proof = IdentityProof {
|
||||||
|
@ -423,19 +355,13 @@ async fn create_identity_proof(
|
||||||
};
|
};
|
||||||
let mut profile_data = ProfileUpdateData::from(¤t_user.profile);
|
let mut profile_data = ProfileUpdateData::from(¤t_user.profile);
|
||||||
profile_data.add_identity_proof(proof);
|
profile_data.add_identity_proof(proof);
|
||||||
current_user.profile = update_profile(
|
current_user.profile = update_profile(db_client, ¤t_user.id, profile_data).await?;
|
||||||
db_client,
|
|
||||||
¤t_user.id,
|
|
||||||
profile_data,
|
|
||||||
).await?;
|
|
||||||
|
|
||||||
// Federate
|
// Federate
|
||||||
prepare_update_person(
|
prepare_update_person(db_client, &config.instance(), ¤t_user, None)
|
||||||
db_client,
|
.await?
|
||||||
&config.instance(),
|
.enqueue(db_client)
|
||||||
¤t_user,
|
.await?;
|
||||||
None,
|
|
||||||
).await?.enqueue(db_client).await?;
|
|
||||||
|
|
||||||
let account = Account::from_user(
|
let account = Account::from_user(
|
||||||
&get_request_base_url(connection_info),
|
&get_request_base_url(connection_info),
|
||||||
|
@ -453,11 +379,7 @@ async fn get_relationships_view(
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
let db_client = &**get_database_client(&db_pool).await?;
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let relationship = get_relationship(
|
let relationship = get_relationship(db_client, ¤t_user.id, &query_params.id).await?;
|
||||||
db_client,
|
|
||||||
¤t_user.id,
|
|
||||||
&query_params.id,
|
|
||||||
).await?;
|
|
||||||
Ok(HttpResponse::Ok().json(vec![relationship]))
|
Ok(HttpResponse::Ok().json(vec![relationship]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,13 +412,13 @@ async fn search_by_acct(
|
||||||
match auth {
|
match auth {
|
||||||
Some(auth) => {
|
Some(auth) => {
|
||||||
get_current_user(db_client, auth.token()).await?;
|
get_current_user(db_client, auth.token()).await?;
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
// Only authorized users can make webfinger queries
|
// Only authorized users can make webfinger queries
|
||||||
if query_params.resolve {
|
if query_params.resolve {
|
||||||
return Err(MastodonError::PermissionError);
|
return Err(MastodonError::PermissionError);
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
let profiles = search_profiles_only(
|
let profiles = search_profiles_only(
|
||||||
&config,
|
&config,
|
||||||
|
@ -504,15 +426,13 @@ async fn search_by_acct(
|
||||||
&query_params.q,
|
&query_params.q,
|
||||||
query_params.resolve,
|
query_params.resolve,
|
||||||
query_params.limit.inner(),
|
query_params.limit.inner(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let base_url = get_request_base_url(connection_info);
|
let base_url = get_request_base_url(connection_info);
|
||||||
let instance_url = config.instance().url();
|
let instance_url = config.instance().url();
|
||||||
let accounts: Vec<Account> = profiles.into_iter()
|
let accounts: Vec<Account> = profiles
|
||||||
.map(|profile| Account::from_profile(
|
.into_iter()
|
||||||
&base_url,
|
.map(|profile| Account::from_profile(&base_url, &instance_url, profile))
|
||||||
&instance_url,
|
|
||||||
profile,
|
|
||||||
))
|
|
||||||
.collect();
|
.collect();
|
||||||
Ok(HttpResponse::Ok().json(accounts))
|
Ok(HttpResponse::Ok().json(accounts))
|
||||||
}
|
}
|
||||||
|
@ -525,17 +445,16 @@ async fn search_by_did(
|
||||||
query_params: web::Query<SearchDidQueryParams>,
|
query_params: web::Query<SearchDidQueryParams>,
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
let db_client = &**get_database_client(&db_pool).await?;
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
let did: Did = query_params.did.parse()
|
let did: Did = query_params
|
||||||
|
.did
|
||||||
|
.parse()
|
||||||
.map_err(|_| ValidationError("invalid DID"))?;
|
.map_err(|_| ValidationError("invalid DID"))?;
|
||||||
let profiles = search_profiles_by_did(db_client, &did, false).await?;
|
let profiles = search_profiles_by_did(db_client, &did, false).await?;
|
||||||
let base_url = get_request_base_url(connection_info);
|
let base_url = get_request_base_url(connection_info);
|
||||||
let instance_url = config.instance().url();
|
let instance_url = config.instance().url();
|
||||||
let accounts: Vec<Account> = profiles.into_iter()
|
let accounts: Vec<Account> = profiles
|
||||||
.map(|profile| Account::from_profile(
|
.into_iter()
|
||||||
&base_url,
|
.map(|profile| Account::from_profile(&base_url, &instance_url, profile))
|
||||||
&instance_url,
|
|
||||||
profile,
|
|
||||||
))
|
|
||||||
.collect();
|
.collect();
|
||||||
Ok(HttpResponse::Ok().json(accounts))
|
Ok(HttpResponse::Ok().json(accounts))
|
||||||
}
|
}
|
||||||
|
@ -563,17 +482,13 @@ async fn follow_account(
|
||||||
config: web::Data<Config>,
|
config: web::Data<Config>,
|
||||||
db_pool: web::Data<DbPool>,
|
db_pool: web::Data<DbPool>,
|
||||||
account_id: web::Path<Uuid>,
|
account_id: web::Path<Uuid>,
|
||||||
follow_data: web::Json<FollowData>,
|
follow_data: FormOrJson<FollowData>,
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
|
let follow_data = follow_data.into_inner();
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let target = get_profile_by_id(db_client, &account_id).await?;
|
let target = get_profile_by_id(db_client, &account_id).await?;
|
||||||
follow_or_create_request(
|
follow_or_create_request(db_client, &config.instance(), ¤t_user, &target).await?;
|
||||||
db_client,
|
|
||||||
&config.instance(),
|
|
||||||
¤t_user,
|
|
||||||
&target,
|
|
||||||
).await?;
|
|
||||||
if follow_data.reblogs {
|
if follow_data.reblogs {
|
||||||
show_reposts(db_client, ¤t_user.id, &target.id).await?;
|
show_reposts(db_client, ¤t_user.id, &target.id).await?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -584,11 +499,7 @@ async fn follow_account(
|
||||||
} else {
|
} else {
|
||||||
hide_replies(db_client, ¤t_user.id, &target.id).await?;
|
hide_replies(db_client, ¤t_user.id, &target.id).await?;
|
||||||
};
|
};
|
||||||
let relationship = get_relationship(
|
let relationship = get_relationship(db_client, ¤t_user.id, &target.id).await?;
|
||||||
db_client,
|
|
||||||
¤t_user.id,
|
|
||||||
&target.id,
|
|
||||||
).await?;
|
|
||||||
Ok(HttpResponse::Ok().json(relationship))
|
Ok(HttpResponse::Ok().json(relationship))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,25 +516,22 @@ async fn unfollow_account(
|
||||||
match unfollow(db_client, ¤t_user.id, &target.id).await {
|
match unfollow(db_client, ¤t_user.id, &target.id).await {
|
||||||
Ok(Some(follow_request_id)) => {
|
Ok(Some(follow_request_id)) => {
|
||||||
// Remote follow
|
// Remote follow
|
||||||
let remote_actor = target.actor_json
|
let remote_actor = target.actor_json.ok_or(MastodonError::InternalError)?;
|
||||||
.ok_or(MastodonError::InternalError)?;
|
|
||||||
prepare_undo_follow(
|
prepare_undo_follow(
|
||||||
&config.instance(),
|
&config.instance(),
|
||||||
¤t_user,
|
¤t_user,
|
||||||
&remote_actor,
|
&remote_actor,
|
||||||
&follow_request_id,
|
&follow_request_id,
|
||||||
).enqueue(db_client).await?;
|
)
|
||||||
},
|
.enqueue(db_client)
|
||||||
Ok(None) => (), // local follow
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(None) => (), // local follow
|
||||||
Err(DatabaseError::NotFound(_)) => (), // not following
|
Err(DatabaseError::NotFound(_)) => (), // not following
|
||||||
Err(other_error) => return Err(other_error.into()),
|
Err(other_error) => return Err(other_error.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let relationship = get_relationship(
|
let relationship = get_relationship(db_client, ¤t_user.id, &target.id).await?;
|
||||||
db_client,
|
|
||||||
¤t_user.id,
|
|
||||||
&target.id,
|
|
||||||
).await?;
|
|
||||||
Ok(HttpResponse::Ok().json(relationship))
|
Ok(HttpResponse::Ok().json(relationship))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,14 +564,16 @@ async fn get_account_statuses(
|
||||||
true,
|
true,
|
||||||
query_params.max_id,
|
query_params.max_id,
|
||||||
query_params.limit.inner(),
|
query_params.limit.inner(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let statuses = build_status_list(
|
let statuses = build_status_list(
|
||||||
db_client,
|
db_client,
|
||||||
&get_request_base_url(connection_info),
|
&get_request_base_url(connection_info),
|
||||||
&config.instance_url(),
|
&config.instance_url(),
|
||||||
maybe_current_user.as_ref(),
|
maybe_current_user.as_ref(),
|
||||||
posts,
|
posts,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
Ok(HttpResponse::Ok().json(statuses))
|
Ok(HttpResponse::Ok().json(statuses))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,24 +600,18 @@ async fn get_account_followers(
|
||||||
&profile.id,
|
&profile.id,
|
||||||
query_params.max_id,
|
query_params.max_id,
|
||||||
query_params.limit.inner(),
|
query_params.limit.inner(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
|
let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
|
||||||
let maybe_last_id = followers.get(max_index).map(|item| item.relationship_id);
|
let maybe_last_id = followers.get(max_index).map(|item| item.relationship_id);
|
||||||
let base_url = get_request_base_url(connection_info);
|
let base_url = get_request_base_url(connection_info);
|
||||||
let instance_url = config.instance().url();
|
let instance_url = config.instance().url();
|
||||||
let accounts: Vec<Account> = followers.into_iter()
|
let accounts: Vec<Account> = followers
|
||||||
.map(|item| Account::from_profile(
|
.into_iter()
|
||||||
&base_url,
|
.map(|item| Account::from_profile(&base_url, &instance_url, item.profile))
|
||||||
&instance_url,
|
|
||||||
item.profile,
|
|
||||||
))
|
|
||||||
.collect();
|
.collect();
|
||||||
let response = get_paginated_response(
|
let response =
|
||||||
&instance_url,
|
get_paginated_response(&instance_url, request.uri().path(), accounts, maybe_last_id);
|
||||||
request.uri().path(),
|
|
||||||
accounts,
|
|
||||||
maybe_last_id,
|
|
||||||
);
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -734,24 +638,18 @@ async fn get_account_following(
|
||||||
&profile.id,
|
&profile.id,
|
||||||
query_params.max_id,
|
query_params.max_id,
|
||||||
query_params.limit.inner(),
|
query_params.limit.inner(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
|
let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
|
||||||
let maybe_last_id = following.get(max_index).map(|item| item.relationship_id);
|
let maybe_last_id = following.get(max_index).map(|item| item.relationship_id);
|
||||||
let base_url = get_request_base_url(connection_info);
|
let base_url = get_request_base_url(connection_info);
|
||||||
let instance_url = config.instance().url();
|
let instance_url = config.instance().url();
|
||||||
let accounts: Vec<Account> = following.into_iter()
|
let accounts: Vec<Account> = following
|
||||||
.map(|item| Account::from_profile(
|
.into_iter()
|
||||||
&base_url,
|
.map(|item| Account::from_profile(&base_url, &instance_url, item.profile))
|
||||||
&instance_url,
|
|
||||||
item.profile,
|
|
||||||
))
|
|
||||||
.collect();
|
.collect();
|
||||||
let response = get_paginated_response(
|
let response =
|
||||||
&instance_url,
|
get_paginated_response(&instance_url, request.uri().path(), accounts, maybe_last_id);
|
||||||
request.uri().path(),
|
|
||||||
accounts,
|
|
||||||
maybe_last_id,
|
|
||||||
);
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,14 +678,10 @@ async fn get_account_subscribers(
|
||||||
query_params.max_id,
|
query_params.max_id,
|
||||||
query_params.limit.inner(),
|
query_params.limit.inner(),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|subscription| ApiSubscription::from_subscription(
|
.map(|subscription| ApiSubscription::from_subscription(&base_url, &instance_url, subscription))
|
||||||
&base_url,
|
.collect();
|
||||||
&instance_url,
|
|
||||||
subscription,
|
|
||||||
))
|
|
||||||
.collect();
|
|
||||||
Ok(HttpResponse::Ok().json(subscriptions))
|
Ok(HttpResponse::Ok().json(subscriptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,12 +697,9 @@ async fn get_account_aliases(
|
||||||
let aliases = find_verified_aliases(db_client, &profile).await?;
|
let aliases = find_verified_aliases(db_client, &profile).await?;
|
||||||
let base_url = get_request_base_url(connection_info);
|
let base_url = get_request_base_url(connection_info);
|
||||||
let instance_url = config.instance_url();
|
let instance_url = config.instance_url();
|
||||||
let accounts: Vec<Account> = aliases.into_iter()
|
let accounts: Vec<Account> = aliases
|
||||||
.map(|profile| Account::from_profile(
|
.into_iter()
|
||||||
&base_url,
|
.map(|profile| Account::from_profile(&base_url, &instance_url, profile))
|
||||||
&instance_url,
|
|
||||||
profile,
|
|
||||||
))
|
|
||||||
.collect();
|
.collect();
|
||||||
Ok(HttpResponse::Ok().json(accounts))
|
Ok(HttpResponse::Ok().json(accounts))
|
||||||
}
|
}
|
||||||
|
@ -824,12 +715,7 @@ async fn get_all_account_aliases(
|
||||||
let profile = get_profile_by_id(db_client, &account_id).await?;
|
let profile = get_profile_by_id(db_client, &account_id).await?;
|
||||||
let base_url = get_request_base_url(connection_info);
|
let base_url = get_request_base_url(connection_info);
|
||||||
let instance_url = config.instance_url();
|
let instance_url = config.instance_url();
|
||||||
let aliases = get_aliases(
|
let aliases = get_aliases(db_client, &base_url, &instance_url, &profile).await?;
|
||||||
db_client,
|
|
||||||
&base_url,
|
|
||||||
&instance_url,
|
|
||||||
&profile,
|
|
||||||
).await?;
|
|
||||||
Ok(HttpResponse::Ok().json(aliases))
|
Ok(HttpResponse::Ok().json(aliases))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue