2023-04-24 15:35:32 +00:00
|
|
|
use actix_web::{dev::ConnectionInfo, get, post, web, HttpResponse, Scope};
|
2022-11-24 01:30:23 +00:00
|
|
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
|
|
|
|
2023-04-25 13:49:35 +00:00
|
|
|
use fedimovies_config::Config;
|
|
|
|
use fedimovies_models::{
|
2023-03-30 20:27:17 +00:00
|
|
|
database::{get_database_client, DatabaseError, DbPool},
|
2023-04-03 20:16:16 +00:00
|
|
|
profiles::helpers::find_verified_aliases,
|
2023-04-24 15:35:32 +00:00
|
|
|
profiles::queries::{get_profile_by_acct, get_profile_by_remote_actor_id, update_profile},
|
2023-04-02 21:23:04 +00:00
|
|
|
profiles::types::ProfileUpdateData,
|
2023-04-24 15:35:32 +00:00
|
|
|
users::queries::{set_user_password, update_client_config},
|
2023-04-05 23:31:08 +00:00
|
|
|
users::types::ClientConfig,
|
2023-03-30 20:27:17 +00:00
|
|
|
};
|
2023-04-25 13:49:35 +00:00
|
|
|
use fedimovies_utils::passwords::hash_password;
|
2023-02-18 22:25:49 +00:00
|
|
|
|
2023-04-24 15:35:32 +00:00
|
|
|
use super::helpers::{
|
|
|
|
export_followers, export_follows, import_follows_task, move_followers_task, parse_address_list,
|
|
|
|
};
|
|
|
|
use super::types::{
|
|
|
|
AddAliasRequest, ImportFollowsRequest, MoveFollowersRequest, PasswordChangeRequest,
|
|
|
|
};
|
2023-04-05 17:54:32 +00:00
|
|
|
use crate::activitypub::{
|
2023-04-24 15:35:32 +00:00
|
|
|
builders::update_person::prepare_update_person, identifiers::profile_actor_id,
|
2023-04-05 17:54:32 +00:00
|
|
|
};
|
2023-02-25 22:27:07 +00:00
|
|
|
use crate::errors::ValidationError;
|
2023-02-21 21:39:42 +00:00
|
|
|
use crate::http::get_request_base_url;
|
2022-11-25 23:37:04 +00:00
|
|
|
use crate::mastodon_api::{
|
2023-04-24 15:35:32 +00:00
|
|
|
accounts::helpers::get_aliases, accounts::types::Account, errors::MastodonError,
|
2022-11-25 23:37:04 +00:00
|
|
|
oauth::auth::get_current_user,
|
|
|
|
};
|
|
|
|
|
2023-04-05 23:31:08 +00:00
|
|
|
// Similar to Pleroma settings store
|
|
|
|
// https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#pleroma-settings-store
|
|
|
|
#[post("/client_config")]
|
|
|
|
async fn client_config_view(
|
|
|
|
auth: BearerAuth,
|
|
|
|
connection_info: ConnectionInfo,
|
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<DbPool>,
|
|
|
|
request_data: web::Json<ClientConfig>,
|
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
|
|
|
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
|
|
|
if request_data.len() != 1 {
|
|
|
|
return Err(ValidationError("can't update more than one config").into());
|
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
let (client_name, client_config_value) = request_data
|
|
|
|
.iter()
|
|
|
|
.next()
|
|
|
|
.expect("hashmap entry should exist");
|
2023-04-05 23:31:08 +00:00
|
|
|
current_user.client_config = update_client_config(
|
|
|
|
db_client,
|
|
|
|
¤t_user.id,
|
|
|
|
client_name,
|
|
|
|
client_config_value,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.await?;
|
2023-04-05 23:31:08 +00:00
|
|
|
let account = Account::from_user(
|
|
|
|
&get_request_base_url(connection_info),
|
|
|
|
&config.instance_url(),
|
|
|
|
current_user,
|
|
|
|
);
|
|
|
|
Ok(HttpResponse::Ok().json(account))
|
|
|
|
}
|
|
|
|
|
2022-11-25 23:37:04 +00:00
|
|
|
#[post("/change_password")]
|
|
|
|
async fn change_password_view(
|
|
|
|
auth: BearerAuth,
|
2023-02-21 21:39:42 +00:00
|
|
|
connection_info: ConnectionInfo,
|
2022-11-25 23:37:04 +00:00
|
|
|
config: web::Data<Config>,
|
2022-12-03 21:23:52 +00:00
|
|
|
db_pool: web::Data<DbPool>,
|
2022-11-25 23:37:04 +00:00
|
|
|
request_data: web::Json<PasswordChangeRequest>,
|
2023-02-25 22:27:07 +00:00
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
2022-11-25 23:37:04 +00:00
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2023-04-24 15:35:32 +00:00
|
|
|
let password_hash =
|
|
|
|
hash_password(&request_data.new_password).map_err(|_| MastodonError::InternalError)?;
|
2022-11-25 23:37:04 +00:00
|
|
|
set_user_password(db_client, ¤t_user.id, password_hash).await?;
|
2023-02-21 21:23:12 +00:00
|
|
|
let account = Account::from_user(
|
2023-02-21 21:39:42 +00:00
|
|
|
&get_request_base_url(connection_info),
|
2023-02-21 21:23:12 +00:00
|
|
|
&config.instance_url(),
|
|
|
|
current_user,
|
|
|
|
);
|
2022-11-25 23:37:04 +00:00
|
|
|
Ok(HttpResponse::Ok().json(account))
|
|
|
|
}
|
2022-11-24 01:30:23 +00:00
|
|
|
|
2023-04-02 21:23:04 +00:00
|
|
|
#[post("/aliases")]
|
|
|
|
async fn add_alias_view(
|
|
|
|
auth: BearerAuth,
|
|
|
|
config: web::Data<Config>,
|
|
|
|
connection_info: ConnectionInfo,
|
|
|
|
db_pool: web::Data<DbPool>,
|
|
|
|
request_data: web::Json<AddAliasRequest>,
|
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
|
|
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
|
|
|
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
|
|
|
let alias = get_profile_by_acct(db_client, &request_data.acct).await?;
|
|
|
|
let instance = config.instance();
|
|
|
|
let alias_id = profile_actor_id(&instance.url(), &alias);
|
|
|
|
let mut profile_data = ProfileUpdateData::from(¤t_user.profile);
|
|
|
|
if !profile_data.aliases.contains(&alias_id) {
|
|
|
|
profile_data.aliases.push(alias_id);
|
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
current_user.profile = update_profile(db_client, ¤t_user.id, profile_data).await?;
|
|
|
|
prepare_update_person(db_client, &instance, ¤t_user, None)
|
|
|
|
.await?
|
|
|
|
.enqueue(db_client)
|
|
|
|
.await?;
|
2023-04-02 21:23:04 +00:00
|
|
|
let aliases = get_aliases(
|
|
|
|
db_client,
|
|
|
|
&get_request_base_url(connection_info),
|
|
|
|
&instance.url(),
|
|
|
|
¤t_user.profile,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.await?;
|
2023-04-02 21:23:04 +00:00
|
|
|
Ok(HttpResponse::Ok().json(aliases))
|
|
|
|
}
|
|
|
|
|
2022-11-24 01:30:23 +00:00
|
|
|
#[get("/export_followers")]
|
|
|
|
async fn export_followers_view(
|
|
|
|
auth: BearerAuth,
|
|
|
|
config: web::Data<Config>,
|
2022-12-03 21:23:52 +00:00
|
|
|
db_pool: web::Data<DbPool>,
|
2023-02-25 22:27:07 +00:00
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
2022-11-24 01:30:23 +00:00
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2023-04-24 15:35:32 +00:00
|
|
|
let csv = export_followers(db_client, &config.instance().hostname(), ¤t_user.id).await?;
|
|
|
|
let response = HttpResponse::Ok().content_type("text/csv").body(csv);
|
2022-11-24 01:30:23 +00:00
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/export_follows")]
|
|
|
|
async fn export_follows_view(
|
|
|
|
auth: BearerAuth,
|
|
|
|
config: web::Data<Config>,
|
2022-12-03 21:23:52 +00:00
|
|
|
db_pool: web::Data<DbPool>,
|
2023-02-25 22:27:07 +00:00
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
2022-11-24 01:30:23 +00:00
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2023-04-24 15:35:32 +00:00
|
|
|
let csv = export_follows(db_client, &config.instance().hostname(), ¤t_user.id).await?;
|
|
|
|
let response = HttpResponse::Ok().content_type("text/csv").body(csv);
|
2022-11-24 01:30:23 +00:00
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
2023-01-10 20:46:57 +00:00
|
|
|
#[post("/import_follows")]
|
|
|
|
async fn import_follows_view(
|
|
|
|
auth: BearerAuth,
|
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<DbPool>,
|
|
|
|
request_data: web::Json<ImportFollowsRequest>,
|
2023-02-25 22:27:07 +00:00
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
2023-01-10 20:46:57 +00:00
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
|
|
|
let address_list = parse_address_list(&request_data.follows_csv)?;
|
|
|
|
tokio::spawn(async move {
|
2023-04-24 15:35:32 +00:00
|
|
|
import_follows_task(&config, current_user, &db_pool, address_list)
|
|
|
|
.await
|
|
|
|
.unwrap_or_else(|error| {
|
|
|
|
log::error!("import follows: {}", error);
|
|
|
|
});
|
2023-01-10 20:46:57 +00:00
|
|
|
});
|
|
|
|
Ok(HttpResponse::NoContent().finish())
|
|
|
|
}
|
|
|
|
|
2023-01-08 21:40:41 +00:00
|
|
|
#[post("/move_followers")]
|
|
|
|
async fn move_followers(
|
|
|
|
auth: BearerAuth,
|
2023-02-21 21:39:42 +00:00
|
|
|
connection_info: ConnectionInfo,
|
2023-01-08 21:40:41 +00:00
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<DbPool>,
|
|
|
|
request_data: web::Json<MoveFollowersRequest>,
|
2023-02-25 22:27:07 +00:00
|
|
|
) -> Result<HttpResponse, MastodonError> {
|
2023-01-08 21:40:41 +00:00
|
|
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2023-03-21 18:05:57 +00:00
|
|
|
if current_user.profile.identity_proofs.inner().is_empty() {
|
|
|
|
return Err(ValidationError("identity proof is required").into());
|
|
|
|
};
|
2023-01-10 00:26:09 +00:00
|
|
|
let instance = config.instance();
|
|
|
|
if request_data.from_actor_id.starts_with(&instance.url()) {
|
|
|
|
return Err(ValidationError("can't move from local actor").into());
|
|
|
|
};
|
2023-01-08 21:40:41 +00:00
|
|
|
// Existence of actor is not verified because
|
|
|
|
// the old profile could have been deleted
|
2023-04-24 15:35:32 +00:00
|
|
|
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()),
|
|
|
|
};
|
2023-01-08 21:40:41 +00:00
|
|
|
if maybe_from_profile.is_some() {
|
|
|
|
// Find known aliases of the current user
|
2023-04-24 15:35:32 +00:00
|
|
|
let mut aliases = find_verified_aliases(db_client, ¤t_user.profile)
|
|
|
|
.await?
|
2023-01-08 21:40:41 +00:00
|
|
|
.into_iter()
|
2023-03-18 17:25:52 +00:00
|
|
|
.map(|profile| profile_actor_id(&instance.url(), &profile));
|
2023-01-08 21:40:41 +00:00
|
|
|
if !aliases.any(|actor_id| actor_id == request_data.from_actor_id) {
|
|
|
|
return Err(ValidationError("old profile is not an alias").into());
|
|
|
|
};
|
|
|
|
};
|
2023-01-10 01:36:06 +00:00
|
|
|
let address_list = parse_address_list(&request_data.followers_csv)?;
|
2023-02-26 19:03:48 +00:00
|
|
|
let current_user_clone = current_user.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
move_followers_task(
|
|
|
|
&config,
|
|
|
|
&db_pool,
|
|
|
|
current_user_clone,
|
|
|
|
&request_data.from_actor_id,
|
|
|
|
maybe_from_profile,
|
|
|
|
address_list,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap_or_else(|error| {
|
2023-02-26 19:03:48 +00:00
|
|
|
log::error!("move followers: {}", error);
|
|
|
|
});
|
|
|
|
});
|
2023-01-08 23:21:31 +00:00
|
|
|
|
2023-02-21 21:23:12 +00:00
|
|
|
let account = Account::from_user(
|
2023-02-21 21:39:42 +00:00
|
|
|
&get_request_base_url(connection_info),
|
2023-02-21 21:23:12 +00:00
|
|
|
&instance.url(),
|
|
|
|
current_user,
|
|
|
|
);
|
2023-01-08 23:21:31 +00:00
|
|
|
Ok(HttpResponse::Ok().json(account))
|
2023-01-08 21:40:41 +00:00
|
|
|
}
|
|
|
|
|
2022-11-24 01:30:23 +00:00
|
|
|
pub fn settings_api_scope() -> Scope {
|
|
|
|
web::scope("/api/v1/settings")
|
2023-04-05 23:31:08 +00:00
|
|
|
.service(client_config_view)
|
2022-11-25 23:37:04 +00:00
|
|
|
.service(change_password_view)
|
2023-04-02 21:23:04 +00:00
|
|
|
.service(add_alias_view)
|
2022-11-24 01:30:23 +00:00
|
|
|
.service(export_followers_view)
|
|
|
|
.service(export_follows_view)
|
2023-01-10 20:46:57 +00:00
|
|
|
.service(import_follows_view)
|
2023-01-08 21:40:41 +00:00
|
|
|
.service(move_followers)
|
2022-11-24 01:30:23 +00:00
|
|
|
}
|