From 18b644d24bcc9a34baa96808d5b406ba3eda2ff0 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 15 Feb 2024 15:11:04 +0100 Subject: [PATCH] feat: proxy response bodies --- foxchat/src/error/mod.rs | 2 + foxchat/src/fed/request.rs | 73 ++++++++++++++++++++++------------ foxchat/src/http/response.rs | 15 ++++--- identity/src/http/proxy/mod.rs | 67 ++++++++++++++++++++++++++++--- 4 files changed, 121 insertions(+), 36 deletions(-) diff --git a/foxchat/src/error/mod.rs b/foxchat/src/error/mod.rs index daaf9c6..3080a99 100644 --- a/foxchat/src/error/mod.rs +++ b/foxchat/src/error/mod.rs @@ -29,6 +29,8 @@ pub enum FoxError { NotInGuild, #[error("channel not found")] ChannelNotFound, + #[error("internal server error while proxying")] + ProxyInternalServerError, } impl From for FoxError { diff --git a/foxchat/src/fed/request.rs b/foxchat/src/fed/request.rs index 409f653..65a03bc 100644 --- a/foxchat/src/fed/request.rs +++ b/foxchat/src/fed/request.rs @@ -1,16 +1,19 @@ -use eyre::Result; +use eyre::{Context, Result}; use once_cell::sync::Lazy; -use reqwest::{Response, StatusCode, header::{CONTENT_TYPE, CONTENT_LENGTH, DATE}}; +use reqwest::{ + header::{CONTENT_LENGTH, CONTENT_TYPE, DATE}, + Response, StatusCode, +}; use rsa::RsaPrivateKey; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tracing::error; use crate::{ + http::ErrorCode, signature::{build_signature, format_date}, - FoxError, }; -use super::{SIGNATURE_HEADER, USER_HEADER, SERVER_HEADER}; +use super::{SERVER_HEADER, SIGNATURE_HEADER, USER_HEADER}; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); @@ -32,13 +35,7 @@ pub async fn get( path: &str, user_id: Option, ) -> Result { - let (signature, date) = build_signature( - private_key, - host, - path, - None, - user_id.clone(), - ); + let (signature, date) = build_signature(private_key, host, path, None, user_id.clone()); let mut req = CLIENT .get(format!("https://{host}{path}")) @@ -53,7 +50,7 @@ pub async fn get( }; let resp = req.send().await?; - handle_response(resp).await + handle_response(resp).await.wrap_err("handling response") } pub async fn post( @@ -66,13 +63,8 @@ pub async fn post( ) -> Result { let body = serde_json::to_string(body)?; - let (signature, date) = build_signature( - private_key, - host, - path, - Some(body.len()), - user_id.clone(), - ); + let (signature, date) = + build_signature(private_key, host, path, Some(body.len()), user_id.clone()); let mut req = CLIENT .post(format!("https://{host}{path}")) @@ -90,15 +82,46 @@ pub async fn post( }; let resp = req.send().await?; - handle_response(resp).await + handle_response(resp).await.wrap_err("handling response") } -async fn handle_response(resp: Response) -> Result { +async fn handle_response(resp: Response) -> Result { if resp.status() != StatusCode::OK { - error!("federation request failed with status code {}", resp.status()); - return Err(FoxError::ResponseNotOk.into()); + let status = resp.status().as_u16(); + + let error_json = resp + .json::() + .await + .map_err(|_| ResponseError::JsonError)?; + + return Err(ResponseError::NotOk { + status, + code: error_json.code, + message: error_json.message, + }); } - let parsed = resp.json::().await?; + let parsed = resp + .json::() + .await + .map_err(|_| ResponseError::JsonError)?; Ok(parsed) } + +#[derive(thiserror::Error, Debug, Clone)] +pub enum ResponseError { + #[error("non-200 status code")] + NotOk { + status: u16, + code: ErrorCode, + message: String, + }, + #[error("error deserializing JSON")] + JsonError, +} + +#[derive(Deserialize)] +struct ApiError { + pub code: ErrorCode, + pub message: String, +} diff --git a/foxchat/src/http/response.rs b/foxchat/src/http/response.rs index dd0ab2e..13bc042 100644 --- a/foxchat/src/http/response.rs +++ b/foxchat/src/http/response.rs @@ -6,16 +6,16 @@ use axum::{ Json, }; use eyre::Report; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use serde_json::json; use tracing::error; use crate::FoxError; pub struct ApiError { - status: StatusCode, - code: ErrorCode, - message: String, + pub status: StatusCode, + pub code: ErrorCode, + pub message: String, } impl IntoResponse for ApiError { @@ -32,7 +32,7 @@ impl IntoResponse for ApiError { } } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ErrorCode { InternalServerError, @@ -147,6 +147,11 @@ impl From for ApiError { status: StatusCode::NOT_FOUND, code: ErrorCode::GuildNotFound, message: "Channel or guild not found".into(), + }, + FoxError::ProxyInternalServerError => ApiError { + status: StatusCode::INTERNAL_SERVER_ERROR, + code: ErrorCode::InternalServerError, + message: "Internal server error".into(), } } } diff --git a/identity/src/http/proxy/mod.rs b/identity/src/http/proxy/mod.rs index 3e7108b..decf0b3 100644 --- a/identity/src/http/proxy/mod.rs +++ b/identity/src/http/proxy/mod.rs @@ -1,14 +1,15 @@ use std::sync::Arc; -use axum::{extract::OriginalUri, routing::post, Extension, Json, Router}; +use axum::{extract::OriginalUri, http::StatusCode, routing::post, Extension, Json, Router}; use eyre::ContextCompat; use foxchat::{ - fed, + fed::{self, request::ResponseError}, http::ApiError, model::{ http::{channel::CreateMessageParams, guild::CreateGuildParams}, Guild, Message, }, + FoxError, }; use serde::{de::DeserializeOwned, Serialize}; use tracing::debug; @@ -45,9 +46,36 @@ async fn proxy_get( &format!("/_fox/chat/{}", proxy_path), Some(user.id), ) - .await?; + .await; - Ok(Json(resp)) + match resp { + Ok(r) => return Ok(Json(r)), + Err(e) => { + if let Some(e) = e.downcast_ref::() { + match e { + ResponseError::JsonError => { + return Err(FoxError::ProxyInternalServerError.into()) + } + ResponseError::NotOk { + status, + code, + message, + } => { + return Err(ApiError { + status: StatusCode::from_u16(status.to_owned()).map_err( + |_| -> ApiError { FoxError::ProxyInternalServerError.into() }, + )?, + code: code.to_owned(), + message: message.to_owned(), + }) + } + } + } else { + tracing::error!("proxying GET request: {}", e); + return Err(FoxError::ProxyInternalServerError.into()); + } + } + } } async fn proxy_post( @@ -71,7 +99,34 @@ async fn proxy_post( Some(user.id), &body, ) - .await?; + .await; - Ok(Json(resp)) + match resp { + Ok(r) => return Ok(Json(r)), + Err(e) => { + if let Some(e) = e.downcast_ref::() { + match e { + ResponseError::JsonError => { + return Err(FoxError::ProxyInternalServerError.into()) + } + ResponseError::NotOk { + status, + code, + message, + } => { + return Err(ApiError { + status: StatusCode::from_u16(status.to_owned()).map_err( + |_| -> ApiError { FoxError::ProxyInternalServerError.into() }, + )?, + code: code.to_owned(), + message: message.to_owned(), + }) + } + } + } else { + tracing::error!("proxying POST request: {}", e); + return Err(FoxError::ProxyInternalServerError.into()); + } + } + } }