diff --git a/CHANGELOG.md b/CHANGELOG.md index cd0b7f8..a03347e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Set deliverer timeout to 30 seconds. - Added `federation` parameter group to configuration. - Add empty `spoiler_text` property to Mastodon API Status object. +- Added `error` and `error_description` fields to Mastodon API error responses. ### Changed @@ -22,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Deprecated `proxy_url` configuration parameter (replaced by `federation.proxy_url`). - Deprecated Atom feeds at `/feeds/{username}`. +- Deprecated `message` field in Mastodon API error response. ### Fixed diff --git a/src/errors.rs b/src/errors.rs index c9e233b..9dab473 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -39,12 +39,6 @@ pub enum HttpError { #[error("{0} not found")] NotFoundError(&'static str), - #[error("operation not supported")] - NotSupported, - - #[error("{0}")] - OperationError(&'static str), - #[error("internal error")] InternalError, } @@ -80,8 +74,6 @@ impl ResponseError for HttpError { HttpError::AuthError(_) => StatusCode::UNAUTHORIZED, HttpError::PermissionError => StatusCode::FORBIDDEN, HttpError::NotFoundError(_) => StatusCode::NOT_FOUND, - HttpError::NotSupported => StatusCode::IM_A_TEAPOT, - HttpError::OperationError(_) => StatusCode::UNPROCESSABLE_ENTITY, _ => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index b1f3a6a..6af562a 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -6,9 +6,10 @@ use uuid::Uuid; use mitra_utils::markdown::markdown_basic_to_html; -use crate::errors::{HttpError, ValidationError}; +use crate::errors::ValidationError; use crate::identity::did::Did; use crate::mastodon_api::{ + errors::MastodonError, pagination::PageSize, uploads::{save_b64_file, UploadError}, }; @@ -315,7 +316,7 @@ impl AccountUpdateData { self, profile: &DbActorProfile, media_dir: &Path, - ) -> Result { + ) -> Result { let maybe_bio = if let Some(ref bio_source) = self.note { let bio = markdown_basic_to_html(bio_source) .map_err(|_| ValidationError("invalid markdown"))?; diff --git a/src/mastodon_api/accounts/views.rs b/src/mastodon_api/accounts/views.rs index dd744e9..a288067 100644 --- a/src/mastodon_api/accounts/views.rs +++ b/src/mastodon_api/accounts/views.rs @@ -32,7 +32,7 @@ use crate::activitypub::builders::{ }, }; use crate::database::{get_database_client, DatabaseError, DbPool}; -use crate::errors::{HttpError, ValidationError}; +use crate::errors::ValidationError; use crate::ethereum::{ contracts::ContractSet, eip4361::verify_eip4361_signature, @@ -58,6 +58,7 @@ use crate::json_signatures::{ }, }; use crate::mastodon_api::{ + errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response, search::helpers::search_profiles_only, @@ -122,7 +123,7 @@ pub async fn create_account( db_pool: web::Data, maybe_blockchain: web::Data>, account_data: web::Json, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; // Validate account_data.clean()?; @@ -136,7 +137,7 @@ pub async fn create_account( let maybe_password_hash = if let Some(password) = account_data.password.as_ref() { let password_hash = hash_password(password) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; Some(password_hash) } else { None @@ -165,7 +166,7 @@ pub async fn create_account( let wallet_address = maybe_wallet_address.as_ref() .ok_or(ValidationError("wallet address is required"))?; let is_allowed = is_allowed_user(gate, wallet_address).await - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; if !is_allowed { return Err(ValidationError("not allowed to sign up").into()); }; @@ -175,10 +176,10 @@ pub async fn create_account( // Generate RSA private key for actor let private_key = match web::block(generate_rsa_key).await { Ok(Ok(private_key)) => private_key, - _ => return Err(HttpError::InternalError), + _ => return Err(MastodonError::InternalError), }; let private_key_pem = serialize_private_key(&private_key) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; let AccountCreateData { username, invite_code, .. } = account_data.into_inner(); @@ -216,7 +217,7 @@ async fn verify_credentials( connection_info: ConnectionInfo, config: web::Data, db_pool: web::Data, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let user = get_current_user(db_client, auth.token()).await?; let account = Account::from_user( @@ -234,7 +235,7 @@ async fn update_credentials( config: web::Data, db_pool: web::Data, account_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let mut current_user = get_current_user(db_client, auth.token()).await?; let mut profile_data = account_data.into_inner() @@ -270,7 +271,7 @@ async fn get_unsigned_update( auth: BearerAuth, config: web::Data, db_pool: web::Data, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let internal_activity_id = generate_ulid(); @@ -278,9 +279,9 @@ async fn get_unsigned_update( &config.instance_url(), ¤t_user, Some(internal_activity_id), - ).map_err(|_| HttpError::InternalError)?; + ).map_err(|_| MastodonError::InternalError)?; let canonical_json = canonicalize_object(&activity) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; let data = UnsignedActivity { params: ActivityParams::Update { internal_activity_id }, message: canonical_json, @@ -295,7 +296,7 @@ async fn send_signed_activity( config: web::Data, db_pool: web::Data, data: web::Json, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let signer = data.signer.parse::() @@ -310,11 +311,11 @@ async fn send_signed_activity( &config.instance(), ¤t_user, Some(*internal_activity_id), - ).await.map_err(|_| HttpError::InternalError)? + ).await.map_err(|_| MastodonError::InternalError)? }, }; let canonical_json = canonicalize_object(&outgoing_activity.activity) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; let proof = match signer { Did::Key(signer) => { let signature_bin = parse_minisign_signature(&data.signature) @@ -332,7 +333,7 @@ async fn send_signed_activity( }, }; add_integrity_proof(&mut outgoing_activity.activity, proof) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; outgoing_activity.enqueue(db_client).await?; @@ -350,7 +351,7 @@ async fn get_identity_claim( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let did = match query_params.proof_type.as_str() { @@ -370,7 +371,7 @@ async fn get_identity_claim( }; let actor_id = current_user.profile.actor_id(&config.instance_url()); let claim = create_identity_claim(&actor_id, &did) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; let response = IdentityClaim { did, claim }; Ok(HttpResponse::Ok().json(response)) } @@ -382,7 +383,7 @@ async fn create_identity_proof( config: web::Data, db_pool: web::Data, proof_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let mut current_user = get_current_user(db_client, auth.token()).await?; let did = proof_data.did.parse::() @@ -469,7 +470,7 @@ async fn get_relationships_view( auth: BearerAuth, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let relationship = get_relationship( @@ -486,7 +487,7 @@ async fn lookup_acct( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let profile = get_profile_by_acct(db_client, &query_params.acct).await?; let account = Account::from_profile( @@ -503,7 +504,7 @@ async fn search_by_acct( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let profiles = search_profiles_only( db_client, @@ -528,7 +529,7 @@ async fn search_by_did( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let did: Did = query_params.did.parse() .map_err(|_| ValidationError("invalid DID"))?; @@ -551,7 +552,7 @@ async fn get_account( config: web::Data, db_pool: web::Data, account_id: web::Path, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let profile = get_profile_by_id(db_client, &account_id).await?; let account = Account::from_profile( @@ -569,7 +570,7 @@ async fn follow_account( db_pool: web::Data, account_id: web::Path, follow_data: web::Json, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let target = get_profile_by_id(db_client, &account_id).await?; @@ -603,7 +604,7 @@ async fn unfollow_account( config: web::Data, db_pool: web::Data, account_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let target = get_profile_by_id(db_client, &account_id).await?; @@ -611,7 +612,7 @@ async fn unfollow_account( Ok(Some(follow_request_id)) => { // Remote follow let remote_actor = target.actor_json - .ok_or(HttpError::InternalError)?; + .ok_or(MastodonError::InternalError)?; prepare_undo_follow( &config.instance(), ¤t_user, @@ -640,7 +641,7 @@ async fn get_account_statuses( db_pool: web::Data, account_id: web::Path, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let maybe_current_user = match auth { Some(auth) => Some(get_current_user(db_client, auth.token()).await?), @@ -681,7 +682,7 @@ async fn get_account_followers( account_id: web::Path, query_params: web::Query, request: HttpRequest, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let profile = get_profile_by_id(db_client, &account_id).await?; @@ -725,7 +726,7 @@ async fn get_account_following( account_id: web::Path, query_params: web::Query, request: HttpRequest, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let profile = get_profile_by_id(db_client, &account_id).await?; @@ -768,7 +769,7 @@ async fn get_account_subscribers( db_pool: web::Data, account_id: web::Path, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let profile = get_profile_by_id(db_client, &account_id).await?; diff --git a/src/mastodon_api/apps/views.rs b/src/mastodon_api/apps/views.rs index 1292144..b77a16a 100644 --- a/src/mastodon_api/apps/views.rs +++ b/src/mastodon_api/apps/views.rs @@ -8,8 +8,10 @@ use actix_web::{ use uuid::Uuid; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; -use crate::mastodon_api::oauth::utils::generate_access_token; +use crate::mastodon_api::{ + errors::MastodonError, + oauth::utils::generate_access_token, +}; use crate::models::{ oauth::queries::create_oauth_app, oauth::types::DbOauthAppData, @@ -24,7 +26,7 @@ async fn create_app_view( web::Json, web::Form, >, -) -> Result { +) -> Result { let request_data = match request_data { Either::Left(json) => json.into_inner(), Either::Right(form) => form.into_inner(), diff --git a/src/mastodon_api/custom_emojis/views.rs b/src/mastodon_api/custom_emojis/views.rs index 0eead3d..38b5162 100644 --- a/src/mastodon_api/custom_emojis/views.rs +++ b/src/mastodon_api/custom_emojis/views.rs @@ -7,8 +7,8 @@ use actix_web::{ }; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::http::get_request_base_url; +use crate::mastodon_api::errors::MastodonError; use crate::models::emojis::queries::get_local_emojis; use super::types::CustomEmoji; @@ -17,7 +17,7 @@ use super::types::CustomEmoji; async fn custom_emoji_list( connection_info: ConnectionInfo, db_pool: web::Data, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let base_url = get_request_base_url(connection_info); let emojis: Vec = get_local_emojis(db_client).await? diff --git a/src/mastodon_api/directory/views.rs b/src/mastodon_api/directory/views.rs index 8aec698..0678166 100644 --- a/src/mastodon_api/directory/views.rs +++ b/src/mastodon_api/directory/views.rs @@ -11,10 +11,10 @@ use actix_web_httpauth::extractors::bearer::BearerAuth; use mitra_config::Config; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::http::get_request_base_url; use crate::mastodon_api::{ accounts::types::Account, + errors::MastodonError, oauth::auth::get_current_user, }; use crate::models::profiles::queries::get_profiles; @@ -27,7 +27,7 @@ async fn profile_directory( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; get_current_user(db_client, auth.token()).await?; let profiles = get_profiles( diff --git a/src/mastodon_api/errors.rs b/src/mastodon_api/errors.rs new file mode 100644 index 0000000..4cec0a6 --- /dev/null +++ b/src/mastodon_api/errors.rs @@ -0,0 +1,89 @@ +use actix_web::{ + error::ResponseError, + http::StatusCode, + HttpResponse, + HttpResponseBuilder, +}; +use serde::Serialize; + +use crate::database::DatabaseError; +use crate::errors::ValidationError; + +#[derive(thiserror::Error, Debug)] +pub enum MastodonError { + #[error(transparent)] + ActixError(#[from] actix_web::Error), + + #[error("database error")] + DatabaseError(#[source] DatabaseError), + + #[error("{0}")] + ValidationError(String), + + #[error("{0}")] + ValidationErrorAuto(#[from] ValidationError), + + #[error("{0}")] + AuthError(&'static str), + + #[error("permission error")] + PermissionError, + + #[error("{0} not found")] + NotFoundError(&'static str), + + #[error("operation not supported")] + NotSupported, + + #[error("{0}")] + OperationError(&'static str), + + #[error("internal error")] + InternalError, +} + +impl From for MastodonError { + fn from(error: DatabaseError) -> Self { + match error { + DatabaseError::NotFound(name) => Self::NotFoundError(name), + DatabaseError::AlreadyExists(name) => Self::ValidationError( + format!("{} already exists", name), + ), + _ => Self::DatabaseError(error), + } + } +} + +/// https://docs.joinmastodon.org/entities/Error/ +#[derive(Serialize)] +struct MastodonErrorData { + message: String, // deprecated + error: String, + error_description: Option, +} + +impl ResponseError for MastodonError { + fn error_response(&self) -> HttpResponse { + let error_data = MastodonErrorData { + message: self.to_string(), + error: self.to_string(), + error_description: Some(self.to_string()), + }; + HttpResponseBuilder::new(self.status_code()).json(error_data) + } + + fn status_code(&self) -> StatusCode { + match self { + Self::ActixError(error) => + error.as_response_error().status_code(), + Self::ValidationError(_) => StatusCode::BAD_REQUEST, + Self::ValidationErrorAuto(_) => StatusCode::BAD_REQUEST, + Self::AuthError(_) => StatusCode::UNAUTHORIZED, + Self::PermissionError => StatusCode::FORBIDDEN, + Self::NotFoundError(_) => StatusCode::NOT_FOUND, + Self::NotSupported => StatusCode::IM_A_TEAPOT, + Self::OperationError(_) => StatusCode::UNPROCESSABLE_ENTITY, + _ => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} diff --git a/src/mastodon_api/instance/views.rs b/src/mastodon_api/instance/views.rs index 11cf4aa..66166cd 100644 --- a/src/mastodon_api/instance/views.rs +++ b/src/mastodon_api/instance/views.rs @@ -3,8 +3,8 @@ use actix_web::{get, web, HttpResponse, Scope}; use mitra_config::Config; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::ethereum::contracts::ContractSet; +use crate::mastodon_api::errors::MastodonError; use crate::models::{ instances::queries::get_peer_count, posts::queries::get_local_post_count, @@ -18,7 +18,7 @@ async fn instance_view( config: web::Data, db_pool: web::Data, maybe_blockchain: web::Data>, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let user_count = get_user_count(db_client).await?; let post_count = get_local_post_count(db_client).await?; diff --git a/src/mastodon_api/markers/views.rs b/src/mastodon_api/markers/views.rs index cb7558a..201082d 100644 --- a/src/mastodon_api/markers/views.rs +++ b/src/mastodon_api/markers/views.rs @@ -2,8 +2,10 @@ use actix_web::{get, post, web, HttpResponse, Scope}; use actix_web_httpauth::extractors::bearer::BearerAuth; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; -use crate::mastodon_api::oauth::auth::get_current_user; +use crate::mastodon_api::{ + errors::MastodonError, + oauth::auth::get_current_user, +}; use crate::models::{ markers::queries::{ create_or_update_marker, @@ -19,7 +21,7 @@ async fn get_marker_view( auth: BearerAuth, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let timeline = query_params.to_timeline()?; @@ -35,7 +37,7 @@ async fn update_marker_view( auth: BearerAuth, db_pool: web::Data, marker_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let db_marker = create_or_update_marker( diff --git a/src/mastodon_api/media/views.rs b/src/mastodon_api/media/views.rs index 2bda465..7b55799 100644 --- a/src/mastodon_api/media/views.rs +++ b/src/mastodon_api/media/views.rs @@ -5,8 +5,8 @@ use actix_web_httpauth::extractors::bearer::BearerAuth; use mitra_config::Config; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::mastodon_api::{ + errors::MastodonError, oauth::auth::get_current_user, uploads::save_b64_file, }; @@ -19,7 +19,7 @@ async fn create_attachment_view( config: web::Data, db_pool: web::Data, attachment_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let (file_name, file_size, media_type) = save_b64_file( diff --git a/src/mastodon_api/mod.rs b/src/mastodon_api/mod.rs index b1dcdfa..d41ec91 100644 --- a/src/mastodon_api/mod.rs +++ b/src/mastodon_api/mod.rs @@ -7,12 +7,14 @@ pub mod markers; pub mod media; pub mod notifications; pub mod oauth; -mod pagination; pub mod search; pub mod settings; pub mod statuses; pub mod subscriptions; pub mod timelines; + +mod errors; +mod pagination; mod uploads; const MASTODON_API_VERSION: &str = "4.0.0"; diff --git a/src/mastodon_api/notifications/views.rs b/src/mastodon_api/notifications/views.rs index 49be79a..f368c2d 100644 --- a/src/mastodon_api/notifications/views.rs +++ b/src/mastodon_api/notifications/views.rs @@ -11,9 +11,9 @@ use actix_web_httpauth::extractors::bearer::BearerAuth; use mitra_config::Config; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::http::get_request_base_url; use crate::mastodon_api::{ + errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response, }; @@ -28,7 +28,7 @@ async fn get_notifications_view( db_pool: web::Data, query_params: web::Query, request: HttpRequest, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let base_url = get_request_base_url(connection_info); diff --git a/src/mastodon_api/oauth/auth.rs b/src/mastodon_api/oauth/auth.rs index 4cbcc87..f3ff24b 100644 --- a/src/mastodon_api/oauth/auth.rs +++ b/src/mastodon_api/oauth/auth.rs @@ -1,5 +1,5 @@ use crate::database::{DatabaseClient, DatabaseError}; -use crate::errors::HttpError; +use crate::mastodon_api::errors::MastodonError; use crate::models::{ oauth::queries::get_user_by_oauth_token, users::types::User, @@ -8,13 +8,13 @@ use crate::models::{ pub async fn get_current_user( db_client: &impl DatabaseClient, token: &str, -) -> Result { +) -> Result { let user = get_user_by_oauth_token(db_client, token).await.map_err(|err| { match err { DatabaseError::NotFound(_) => { - HttpError::AuthError("access token is invalid") + MastodonError::AuthError("access token is invalid") }, - _ => HttpError::InternalError, + _ => MastodonError::InternalError, } })?; Ok(user) diff --git a/src/mastodon_api/oauth/views.rs b/src/mastodon_api/oauth/views.rs index 0a0f760..5363f58 100644 --- a/src/mastodon_api/oauth/views.rs +++ b/src/mastodon_api/oauth/views.rs @@ -13,12 +13,13 @@ use mitra_config::Config; use mitra_utils::passwords::verify_password; use crate::database::{get_database_client, DatabaseError, DbPool}; -use crate::errors::{HttpError, ValidationError}; +use crate::errors::ValidationError; use crate::ethereum::{ eip4361::verify_eip4361_signature, utils::validate_ethereum_address, }; use crate::http::FormOrJson; +use crate::mastodon_api::errors::MastodonError; use crate::models::{ oauth::queries::{ create_oauth_authorization, @@ -60,7 +61,7 @@ async fn authorize_view( db_pool: web::Data, form_data: web::Form, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let user = get_user_by_name(db_client, &form_data.username).await?; let password_hash = user.password_hash.as_ref() @@ -68,7 +69,7 @@ async fn authorize_view( let password_correct = verify_password( password_hash, &form_data.password, - ).map_err(|_| HttpError::InternalError)?; + ).map_err(|_| MastodonError::InternalError)?; if !password_correct { return Err(ValidationError("incorrect password").into()); }; @@ -116,7 +117,7 @@ async fn token_view( config: web::Data, db_pool: web::Data, request_data: FormOrJson, -) -> Result { +) -> Result { let request_data = request_data.into_inner(); let db_client = &**get_database_client(&db_pool).await?; let user = match request_data.grant_type.as_str() { @@ -165,7 +166,7 @@ async fn token_view( let password_correct = verify_password( password_hash, password, - ).map_err(|_| HttpError::InternalError)?; + ).map_err(|_| MastodonError::InternalError)?; if !password_correct { return Err(ValidationError("incorrect password").into()); }; @@ -193,7 +194,7 @@ async fn revoke_token_view( auth: BearerAuth, db_pool: web::Data, request_data: web::Json, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; match delete_oauth_token( @@ -202,7 +203,7 @@ async fn revoke_token_view( &request_data.token, ).await { Ok(_) => (), - Err(DatabaseError::NotFound(_)) => return Err(HttpError::PermissionError), + Err(DatabaseError::NotFound(_)) => return Err(MastodonError::PermissionError), Err(other_error) => return Err(other_error.into()), }; Ok(HttpResponse::Ok().finish()) diff --git a/src/mastodon_api/search/views.rs b/src/mastodon_api/search/views.rs index 8bf6f55..9d3e836 100644 --- a/src/mastodon_api/search/views.rs +++ b/src/mastodon_api/search/views.rs @@ -11,10 +11,10 @@ use actix_web_httpauth::extractors::bearer::BearerAuth; use mitra_config::Config; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::http::get_request_base_url; use crate::mastodon_api::{ accounts::types::Account, + errors::MastodonError, oauth::auth::get_current_user, statuses::helpers::build_status_list, statuses::types::Tag, @@ -29,7 +29,7 @@ async fn search_view( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let (profiles, posts, tags) = search( diff --git a/src/mastodon_api/settings/views.rs b/src/mastodon_api/settings/views.rs index 9c6ca74..083792e 100644 --- a/src/mastodon_api/settings/views.rs +++ b/src/mastodon_api/settings/views.rs @@ -18,10 +18,11 @@ use crate::activitypub::{ }, }; use crate::database::{get_database_client, DatabaseError, DbPool}; -use crate::errors::{HttpError, ValidationError}; +use crate::errors::ValidationError; use crate::http::get_request_base_url; use crate::mastodon_api::{ accounts::types::Account, + errors::MastodonError, oauth::auth::get_current_user, }; use crate::models::{ @@ -49,11 +50,11 @@ async fn change_password_view( config: web::Data, db_pool: web::Data, request_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let password_hash = hash_password(&request_data.new_password) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; set_user_password(db_client, ¤t_user.id, password_hash).await?; let account = Account::from_user( &get_request_base_url(connection_info), @@ -68,7 +69,7 @@ async fn export_followers_view( auth: BearerAuth, config: web::Data, db_pool: web::Data, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let csv = export_followers( @@ -87,7 +88,7 @@ async fn export_follows_view( auth: BearerAuth, config: web::Data, db_pool: web::Data, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let csv = export_follows( @@ -107,7 +108,7 @@ async fn import_follows_view( config: web::Data, db_pool: web::Data, request_data: web::Json, -) -> Result { +) -> Result { 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)?; @@ -131,7 +132,7 @@ async fn move_followers( config: web::Data, db_pool: web::Data, request_data: web::Json, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let instance = config.instance(); diff --git a/src/mastodon_api/statuses/views.rs b/src/mastodon_api/statuses/views.rs index c89fa77..eaa2015 100644 --- a/src/mastodon_api/statuses/views.rs +++ b/src/mastodon_api/statuses/views.rs @@ -27,7 +27,7 @@ use crate::activitypub::builders::{ undo_like::prepare_undo_like, }; use crate::database::{get_database_client, DatabaseError, DbPool}; -use crate::errors::{HttpError, ValidationError}; +use crate::errors::ValidationError; use crate::ethereum::nft::create_mint_signature; use crate::http::{get_request_base_url, FormOrJson}; use crate::ipfs::{ @@ -35,7 +35,10 @@ use crate::ipfs::{ posts::PostMetadata, utils::get_ipfs_url, }; -use crate::mastodon_api::oauth::auth::get_current_user; +use crate::mastodon_api::{ + errors::MastodonError, + oauth::auth::get_current_user, +}; use crate::models::{ posts::helpers::{can_create_post, can_view_post}, posts::queries::{ @@ -81,11 +84,11 @@ async fn create_status( config: web::Data, db_pool: web::Data, status_data: FormOrJson, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; if !can_create_post(¤t_user) { - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let instance = config.instance(); let status_data = status_data.into_inner(); @@ -213,7 +216,7 @@ async fn preview_status( config: web::Data, db_pool: web::Data, status_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; get_current_user(db_client, auth.token()).await?; let instance = config.instance(); @@ -249,7 +252,7 @@ async fn get_status( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let maybe_current_user = match auth { Some(auth) => Some(get_current_user(db_client, auth.token()).await?), @@ -257,7 +260,7 @@ async fn get_status( }; let post = get_post_by_id(db_client, &status_id).await?; if !can_view_post(db_client, maybe_current_user.as_ref(), &post).await? { - return Err(HttpError::NotFoundError("post")); + return Err(MastodonError::NotFoundError("post")); }; let status = build_status( db_client, @@ -275,12 +278,12 @@ async fn delete_status( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let post = get_post_by_id(db_client, &status_id).await?; if post.author.id != current_user.id { - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let delete_note = prepare_delete_note( db_client, @@ -304,7 +307,7 @@ async fn get_context( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let maybe_current_user = match auth { Some(auth) => Some(get_current_user(db_client, auth.token()).await?), @@ -347,7 +350,7 @@ async fn get_thread_view( connection_info: ConnectionInfo, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let maybe_current_user = match auth { Some(auth) => Some(get_current_user(db_client, auth.token()).await?), @@ -375,12 +378,12 @@ async fn favourite( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let mut post = get_post_by_id(db_client, &status_id).await?; if post.repost_of_id.is_some() { - return Err(HttpError::NotFoundError("post")); + return Err(MastodonError::NotFoundError("post")); }; let maybe_reaction_created = match create_reaction( db_client, ¤t_user.id, &status_id, None, @@ -421,7 +424,7 @@ async fn unfavourite( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let mut post = get_post_by_id(db_client, &status_id).await?; @@ -464,15 +467,15 @@ async fn reblog( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; if !can_create_post(¤t_user) { - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let mut post = get_post_by_id(db_client, &status_id).await?; if !post.is_public() || post.repost_of_id.is_some() { - return Err(HttpError::NotFoundError("post")); + return Err(MastodonError::NotFoundError("post")); }; let repost_data = PostCreateData::repost(status_id.into_inner(), None); let mut repost = create_post(db_client, ¤t_user.id, repost_data).await?; @@ -504,7 +507,7 @@ async fn unreblog( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let reposts = find_reposts_by_user( @@ -512,7 +515,7 @@ async fn unreblog( ¤t_user.id, &[*status_id], ).await?; - let repost_id = reposts.first().ok_or(HttpError::NotFoundError("post"))?; + let repost_id = reposts.first().ok_or(MastodonError::NotFoundError("post"))?; // Ignore returned data because reposts don't have attached files delete_post(db_client, repost_id).await?; let post = get_post_by_id(db_client, &status_id).await?; @@ -543,28 +546,28 @@ async fn make_permanent( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let mut post = get_post_by_id(db_client, &status_id).await?; if post.ipfs_cid.is_some() { - return Err(HttpError::OperationError("post already saved to IPFS")); + return Err(MastodonError::OperationError("post already saved to IPFS")); }; if post.author.id != current_user.id || !post.is_public() || post.repost_of_id.is_some() { // Users can only archive their own public posts - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let ipfs_api_url = config.ipfs_api_url.as_ref() - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; let mut attachments = vec![]; for attachment in post.attachments.iter_mut() { // Add attachment to IPFS let image_path = config.media_dir().join(&attachment.file_name); let image_data = std::fs::read(image_path) - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; let image_cid = ipfs_store::add(ipfs_api_url, image_data).await - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; attachment.ipfs_cid = Some(image_cid.clone()); attachments.push((attachment.id, image_cid)); }; @@ -579,10 +582,10 @@ async fn make_permanent( maybe_post_image_cid, ); let post_metadata_json = serde_json::to_string(&post_metadata) - .map_err(|_| HttpError::InternalError)? + .map_err(|_| MastodonError::InternalError)? .as_bytes().to_vec(); let post_metadata_cid = ipfs_store::add(ipfs_api_url, post_metadata_json).await - .map_err(|_| HttpError::InternalError)?; + .map_err(|_| MastodonError::InternalError)?; set_post_ipfs_cid(db_client, &post.id, &post_metadata_cid, attachments).await?; post.ipfs_cid = Some(post_metadata_cid); @@ -603,31 +606,31 @@ async fn get_signature( config: web::Data, db_pool: web::Data, status_id: web::Path, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let ethereum_config = config.blockchain() - .ok_or(HttpError::NotSupported)? + .ok_or(MastodonError::NotSupported)? .ethereum_config() - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; // User must have a public ethereum address let wallet_address = current_user .public_wallet_address(&Currency::Ethereum) - .ok_or(HttpError::PermissionError)?; + .ok_or(MastodonError::PermissionError)?; let post = get_post_by_id(db_client, &status_id).await?; if post.author.id != current_user.id || !post.is_public() || post.repost_of_id.is_some() { // Users can only tokenize their own public posts - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let ipfs_cid = post.ipfs_cid // Post metadata is not immutable - .ok_or(HttpError::PermissionError)?; + .ok_or(MastodonError::PermissionError)?; let token_uri = get_ipfs_url(&ipfs_cid); let signature = create_mint_signature( ethereum_config, &wallet_address, &token_uri, - ).map_err(|_| HttpError::InternalError)?; + ).map_err(|_| MastodonError::InternalError)?; Ok(HttpResponse::Ok().json(signature)) } @@ -639,15 +642,15 @@ async fn token_minted( db_pool: web::Data, status_id: web::Path, transaction_data: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let mut post = get_post_by_id(db_client, &status_id).await?; if post.token_tx_id.is_some() { - return Err(HttpError::OperationError("transaction is already registered")); + return Err(MastodonError::OperationError("transaction is already registered")); }; if post.author.id != current_user.id || !post.is_public() || post.repost_of_id.is_some() { - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let token_tx_id = transaction_data.into_inner().transaction_id; set_post_token_tx_id(db_client, &post.id, &token_tx_id).await?; diff --git a/src/mastodon_api/subscriptions/views.rs b/src/mastodon_api/subscriptions/views.rs index 5ffaa49..a7b8250 100644 --- a/src/mastodon_api/subscriptions/views.rs +++ b/src/mastodon_api/subscriptions/views.rs @@ -14,7 +14,7 @@ use mitra_utils::currencies::Currency; use crate::activitypub::builders::update_person::prepare_update_person; use crate::database::{get_database_client, DbPool}; -use crate::errors::{HttpError, ValidationError}; +use crate::errors::ValidationError; use crate::ethereum::{ contracts::ContractSet, subscriptions::{ @@ -25,6 +25,7 @@ use crate::ethereum::{ use crate::http::get_request_base_url; use crate::mastodon_api::{ accounts::types::Account, + errors::MastodonError, oauth::auth::get_current_user, }; use crate::models::{ @@ -62,24 +63,24 @@ pub async fn authorize_subscription( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let ethereum_config = config.blockchain() - .ok_or(HttpError::NotSupported)? + .ok_or(MastodonError::NotSupported)? .ethereum_config() - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; // The user must have a public ethereum address, // because subscribers should be able // to verify that payments are actually sent to the recipient. let wallet_address = current_user .public_wallet_address(&Currency::Ethereum) - .ok_or(HttpError::PermissionError)?; + .ok_or(MastodonError::PermissionError)?; let signature = create_subscription_signature( ethereum_config, &wallet_address, query_params.price, - ).map_err(|_| HttpError::InternalError)?; + ).map_err(|_| MastodonError::InternalError)?; Ok(HttpResponse::Ok().json(signature)) } @@ -87,7 +88,7 @@ pub async fn authorize_subscription( async fn get_subscription_options( auth: BearerAuth, db_pool: web::Data, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let options: Vec = current_user.profile @@ -105,23 +106,23 @@ pub async fn register_subscription_option( db_pool: web::Data, maybe_blockchain: web::Data>, subscription_option: web::Json, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let mut current_user = get_current_user(db_client, auth.token()).await?; if !current_user.role.has_permission(Permission::ManageSubscriptionOptions) { - return Err(HttpError::PermissionError); + return Err(MastodonError::PermissionError); }; let maybe_payment_option = match subscription_option.into_inner() { SubscriptionOption::Ethereum => { let ethereum_config = config.blockchain() .and_then(|conf| conf.ethereum_config()) - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; let contract_set = maybe_blockchain.as_ref().as_ref() - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; let wallet_address = current_user .public_wallet_address(&Currency::Ethereum) - .ok_or(HttpError::PermissionError)?; + .ok_or(MastodonError::PermissionError)?; if current_user.profile.payment_options .any(PaymentType::EthereumSubscription) { @@ -131,7 +132,7 @@ pub async fn register_subscription_option( let is_registered = is_registered_recipient( contract_set, &wallet_address, - ).await.map_err(|_| HttpError::InternalError)?; + ).await.map_err(|_| MastodonError::InternalError)?; if !is_registered { return Err(ValidationError("recipient is not registered").into()); }; @@ -143,7 +144,7 @@ pub async fn register_subscription_option( SubscriptionOption::Monero { price, payout_address } => { let monero_config = config.blockchain() .and_then(|conf| conf.monero_config()) - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; if price == 0 { return Err(ValidationError("price must be greater than 0").into()); }; @@ -186,7 +187,7 @@ pub async fn register_subscription_option( async fn find_subscription( db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let subscription = get_subscription_by_participants( db_client, @@ -205,11 +206,11 @@ async fn create_invoice_view( config: web::Data, db_pool: web::Data, invoice_data: web::Json, -) -> Result { +) -> Result { let monero_config = config.blockchain() - .ok_or(HttpError::NotSupported)? + .ok_or(MastodonError::NotSupported)? .monero_config() - .ok_or(HttpError::NotSupported)?; + .ok_or(MastodonError::NotSupported)?; if invoice_data.sender_id == invoice_data.recipient_id { return Err(ValidationError("sender must be different from recipient").into()); }; @@ -225,7 +226,7 @@ async fn create_invoice_view( }; let payment_address = create_monero_address(monero_config).await - .map_err(|_| HttpError::InternalError)? + .map_err(|_| MastodonError::InternalError)? .to_string(); let db_invoice = create_invoice( db_client, @@ -243,7 +244,7 @@ async fn create_invoice_view( async fn get_invoice( db_pool: web::Data, invoice_id: web::Path, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let db_invoice = get_invoice_by_id(db_client, &invoice_id).await?; let invoice = Invoice::from(db_invoice); diff --git a/src/mastodon_api/timelines/views.rs b/src/mastodon_api/timelines/views.rs index 27d1194..be2f695 100644 --- a/src/mastodon_api/timelines/views.rs +++ b/src/mastodon_api/timelines/views.rs @@ -11,9 +11,9 @@ use actix_web_httpauth::extractors::bearer::BearerAuth; use mitra_config::Config; use crate::database::{get_database_client, DbPool}; -use crate::errors::HttpError; use crate::http::get_request_base_url; use crate::mastodon_api::{ + errors::MastodonError, oauth::auth::get_current_user, statuses::helpers::build_status_list, }; @@ -31,7 +31,7 @@ async fn home_timeline( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let posts = get_home_timeline( @@ -58,7 +58,7 @@ async fn public_timeline( config: web::Data, db_pool: web::Data, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let posts = get_local_timeline( @@ -85,7 +85,7 @@ async fn hashtag_timeline( db_pool: web::Data, hashtag: web::Path, query_params: web::Query, -) -> Result { +) -> Result { let db_client = &**get_database_client(&db_pool).await?; let maybe_current_user = match auth { Some(auth) => Some(get_current_user(db_client, auth.token()).await?), diff --git a/src/mastodon_api/uploads.rs b/src/mastodon_api/uploads.rs index 880df99..7efc0a9 100644 --- a/src/mastodon_api/uploads.rs +++ b/src/mastodon_api/uploads.rs @@ -2,8 +2,8 @@ use std::path::Path; use mitra_utils::files::sniff_media_type; -use crate::errors::HttpError; use crate::media::{save_file, SUPPORTED_MEDIA_TYPES}; +use super::errors::MastodonError; pub const UPLOAD_MAX_SIZE: usize = 1024 * 1024 * 5; @@ -22,12 +22,12 @@ pub enum UploadError { InvalidMediaType, } -impl From for HttpError { +impl From for MastodonError { fn from(error: UploadError) -> Self { match error { - UploadError::WriteError(_) => HttpError::InternalError, + UploadError::WriteError(_) => MastodonError::InternalError, other_error => { - HttpError::ValidationError(other_error.to_string()) + MastodonError::ValidationError(other_error.to_string()) }, } }