2022-10-18 00:47:58 +03:00
|
|
|
//! Utlities for logging errors for admins and displaying errors to users.
|
|
|
|
|
2022-11-08 22:58:11 +03:00
|
|
|
use super::authorization::Authorization;
|
|
|
|
use crate::jsonrpc::JsonRpcForwardedResponse;
|
2022-08-10 05:37:34 +03:00
|
|
|
use axum::{
|
2022-10-27 00:39:26 +03:00
|
|
|
headers,
|
2022-08-10 05:37:34 +03:00
|
|
|
http::StatusCode,
|
|
|
|
response::{IntoResponse, Response},
|
|
|
|
Json,
|
|
|
|
};
|
2022-08-17 00:43:39 +03:00
|
|
|
use derive_more::From;
|
2022-10-27 00:39:26 +03:00
|
|
|
use http::header::InvalidHeaderValue;
|
|
|
|
use ipnet::AddrParseError;
|
2022-11-16 10:19:56 +03:00
|
|
|
use log::{trace, warn};
|
2022-11-14 21:24:52 +03:00
|
|
|
use migration::sea_orm::DbErr;
|
2022-09-15 20:57:24 +03:00
|
|
|
use redis_rate_limiter::redis::RedisError;
|
2022-10-27 00:39:26 +03:00
|
|
|
use reqwest::header::ToStrError;
|
2022-12-14 05:13:23 +03:00
|
|
|
use tokio::{sync::AcquireError, task::JoinError, time::Instant};
|
2022-06-05 22:58:47 +03:00
|
|
|
|
2022-10-20 23:26:14 +03:00
|
|
|
// TODO: take "IntoResponse" instead of Response?
|
2022-08-17 00:43:39 +03:00
|
|
|
pub type FrontendResult = Result<Response, FrontendErrorResponse>;
|
|
|
|
|
2022-10-20 23:26:14 +03:00
|
|
|
// TODO:
|
2022-10-29 01:52:47 +03:00
|
|
|
#[derive(Debug, From)]
|
2022-08-17 00:43:39 +03:00
|
|
|
pub enum FrontendErrorResponse {
|
2022-12-06 00:13:36 +03:00
|
|
|
AccessDenied,
|
2022-08-17 00:43:39 +03:00
|
|
|
Anyhow(anyhow::Error),
|
2022-12-14 05:13:23 +03:00
|
|
|
SemaphoreAcquireError(AcquireError),
|
2022-08-27 08:42:25 +03:00
|
|
|
Database(DbErr),
|
2022-10-27 00:39:26 +03:00
|
|
|
HeadersError(headers::Error),
|
|
|
|
HeaderToString(ToStrError),
|
|
|
|
InvalidHeaderValue(InvalidHeaderValue),
|
|
|
|
IpAddrParse(AddrParseError),
|
2022-11-03 02:14:16 +03:00
|
|
|
JoinError(JoinError),
|
2022-10-27 00:39:26 +03:00
|
|
|
NotFound,
|
2022-11-08 22:58:11 +03:00
|
|
|
RateLimited(Authorization, Option<Instant>),
|
2022-10-27 00:39:26 +03:00
|
|
|
Redis(RedisError),
|
2022-10-20 23:26:14 +03:00
|
|
|
/// simple way to return an error message to the user and an anyhow to our logs
|
2022-11-08 22:58:11 +03:00
|
|
|
StatusCode(StatusCode, String, Option<anyhow::Error>),
|
2022-12-20 08:37:12 +03:00
|
|
|
/// TODO: what should be attached to the timout?
|
|
|
|
Timeout(tokio::time::error::Elapsed),
|
2022-10-31 23:05:58 +03:00
|
|
|
UlidDecodeError(ulid::DecodeError),
|
2022-09-12 17:33:55 +03:00
|
|
|
UnknownKey,
|
2022-08-16 22:29:00 +03:00
|
|
|
}
|
|
|
|
|
2023-01-19 03:17:43 +03:00
|
|
|
impl FrontendErrorResponse {
|
|
|
|
pub fn into_response_parts(self) -> (StatusCode, JsonRpcForwardedResponse) {
|
|
|
|
match self {
|
2022-12-06 00:13:36 +03:00
|
|
|
Self::AccessDenied => {
|
|
|
|
// TODO: attach something to this trace. probably don't include much in the message though. don't want to leak creds by accident
|
|
|
|
trace!("access denied");
|
|
|
|
(
|
|
|
|
StatusCode::FORBIDDEN,
|
|
|
|
JsonRpcForwardedResponse::from_string(
|
|
|
|
// TODO: is it safe to expose all of our anyhow strings?
|
|
|
|
"FORBIDDEN".to_string(),
|
|
|
|
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-09-10 03:12:14 +03:00
|
|
|
Self::Anyhow(err) => {
|
2022-11-12 11:24:32 +03:00
|
|
|
warn!("anyhow. err={:?}", err);
|
2022-09-10 03:12:14 +03:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
2022-09-10 03:58:33 +03:00
|
|
|
JsonRpcForwardedResponse::from_string(
|
2022-09-20 06:26:12 +03:00
|
|
|
// TODO: is it safe to expose all of our anyhow strings?
|
2022-09-10 03:58:33 +03:00
|
|
|
err.to_string(),
|
2022-09-10 03:12:14 +03:00
|
|
|
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-12-20 08:37:12 +03:00
|
|
|
// Self::(err) => {
|
|
|
|
// warn!("boxed err={:?}", err);
|
|
|
|
// (
|
|
|
|
// StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
// JsonRpcForwardedResponse::from_str(
|
|
|
|
// // TODO: make this better. maybe include the error type?
|
|
|
|
// "boxed error!",
|
|
|
|
// Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
|
|
|
// None,
|
|
|
|
// ),
|
|
|
|
// )
|
|
|
|
// }
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::Database(err) => {
|
2022-11-12 11:24:32 +03:00
|
|
|
warn!("database err={:?}", err);
|
2022-09-10 03:12:14 +03:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
2022-10-27 00:39:26 +03:00
|
|
|
"database error!",
|
2022-09-10 03:12:14 +03:00
|
|
|
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::HeadersError(err) => {
|
2022-11-12 11:24:32 +03:00
|
|
|
warn!("HeadersError {:?}", err);
|
2022-10-27 00:39:26 +03:00
|
|
|
(
|
|
|
|
StatusCode::BAD_REQUEST,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
&format!("{}", err),
|
|
|
|
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
2022-08-21 12:39:38 +03:00
|
|
|
}
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::IpAddrParse(err) => {
|
2022-11-12 11:24:32 +03:00
|
|
|
warn!("IpAddrParse err={:?}", err);
|
2022-10-20 23:26:14 +03:00
|
|
|
(
|
2022-10-27 00:39:26 +03:00
|
|
|
StatusCode::BAD_REQUEST,
|
2022-10-20 23:26:14 +03:00
|
|
|
JsonRpcForwardedResponse::from_str(
|
2022-10-27 00:39:26 +03:00
|
|
|
&format!("{}", err),
|
|
|
|
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
2022-10-20 23:26:14 +03:00
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::InvalidHeaderValue(err) => {
|
2022-11-12 11:24:32 +03:00
|
|
|
warn!("InvalidHeaderValue err={:?}", err);
|
2022-09-10 03:12:14 +03:00
|
|
|
(
|
2022-10-27 00:39:26 +03:00
|
|
|
StatusCode::BAD_REQUEST,
|
2022-09-10 03:12:14 +03:00
|
|
|
JsonRpcForwardedResponse::from_str(
|
2022-10-27 00:39:26 +03:00
|
|
|
&format!("{}", err),
|
|
|
|
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-11-03 02:14:16 +03:00
|
|
|
Self::JoinError(err) => {
|
2022-12-20 08:37:12 +03:00
|
|
|
let code = if err.is_cancelled() {
|
|
|
|
trace!("JoinError. likely shutting down. err={:?}", err);
|
|
|
|
StatusCode::BAD_GATEWAY
|
|
|
|
} else {
|
|
|
|
warn!("JoinError. err={:?}", err);
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR
|
|
|
|
};
|
|
|
|
|
2022-11-03 02:14:16 +03:00
|
|
|
(
|
2022-12-20 08:37:12 +03:00
|
|
|
code,
|
2022-11-03 02:14:16 +03:00
|
|
|
JsonRpcForwardedResponse::from_str(
|
2022-12-20 08:37:12 +03:00
|
|
|
// TODO: different messages, too?
|
2022-11-03 02:14:16 +03:00
|
|
|
"Unable to complete request",
|
2022-12-20 08:37:12 +03:00
|
|
|
Some(code.as_u16().into()),
|
2022-11-03 02:14:16 +03:00
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::NotFound => {
|
|
|
|
// TODO: emit a stat?
|
|
|
|
// TODO: instead of an error, show a normal html page for 404
|
|
|
|
(
|
|
|
|
StatusCode::NOT_FOUND,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
"not found!",
|
|
|
|
Some(StatusCode::NOT_FOUND.as_u16().into()),
|
2022-09-10 03:12:14 +03:00
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// TODO: this should actually by the id of the key. multiple users might control one key
|
2022-11-08 22:58:11 +03:00
|
|
|
Self::RateLimited(authorization, retry_at) => {
|
2022-09-10 03:12:14 +03:00
|
|
|
// TODO: emit a stat
|
2022-11-08 22:58:11 +03:00
|
|
|
|
|
|
|
let retry_msg = if let Some(retry_at) = retry_at {
|
|
|
|
let retry_in = retry_at.duration_since(Instant::now()).as_secs();
|
|
|
|
|
|
|
|
format!(" Retry in {} seconds", retry_in)
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
// create a string with either the IP or the rpc_key_id
|
2023-01-19 03:17:43 +03:00
|
|
|
let msg = if authorization.checks.rpc_secret_key_id.is_none() {
|
2022-11-08 22:58:11 +03:00
|
|
|
format!("too many requests from {}.{}", authorization.ip, retry_msg)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
"too many requests from rpc key #{}.{}",
|
2023-01-19 03:17:43 +03:00
|
|
|
authorization.checks.rpc_secret_key_id.unwrap(),
|
2022-11-10 02:58:07 +03:00
|
|
|
retry_msg
|
2022-11-08 22:58:11 +03:00
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2022-09-10 03:12:14 +03:00
|
|
|
(
|
|
|
|
StatusCode::TOO_MANY_REQUESTS,
|
|
|
|
JsonRpcForwardedResponse::from_string(
|
2022-11-08 22:58:11 +03:00
|
|
|
msg,
|
2022-09-10 03:12:14 +03:00
|
|
|
Some(StatusCode::TOO_MANY_REQUESTS.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::Redis(err) => {
|
2022-11-12 11:24:32 +03:00
|
|
|
warn!("redis err={:?}", err);
|
2022-10-27 00:39:26 +03:00
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
"redis error!",
|
|
|
|
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-12-14 05:13:23 +03:00
|
|
|
Self::SemaphoreAcquireError(err) => {
|
|
|
|
warn!("semaphore acquire err={:?}", err);
|
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
JsonRpcForwardedResponse::from_string(
|
|
|
|
// TODO: is it safe to expose all of our anyhow strings?
|
|
|
|
"semaphore acquire error".to_string(),
|
|
|
|
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::StatusCode(status_code, err_msg, err) => {
|
2022-11-16 10:19:56 +03:00
|
|
|
// different status codes should get different error levels. 500s should warn. 400s should stat
|
|
|
|
let code = status_code.as_u16();
|
|
|
|
if (500..600).contains(&code) {
|
2022-12-20 08:39:17 +03:00
|
|
|
warn!("server error {} {:?}: {:?}", code, err_msg, err);
|
2022-11-16 10:19:56 +03:00
|
|
|
} else {
|
|
|
|
trace!("user error {} {:?}: {:?}", code, err_msg, err);
|
|
|
|
}
|
|
|
|
|
2022-10-27 00:39:26 +03:00
|
|
|
(
|
|
|
|
status_code,
|
2022-11-16 10:19:56 +03:00
|
|
|
JsonRpcForwardedResponse::from_str(&err_msg, Some(code.into()), None),
|
2022-10-27 00:39:26 +03:00
|
|
|
)
|
|
|
|
}
|
2022-12-20 08:37:12 +03:00
|
|
|
Self::Timeout(x) => (
|
|
|
|
StatusCode::REQUEST_TIMEOUT,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
&format!("request timed out: {:?}", x),
|
|
|
|
Some(StatusCode::REQUEST_TIMEOUT.as_u16().into()),
|
|
|
|
// TODO: include the actual id!
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
),
|
2022-10-27 00:39:26 +03:00
|
|
|
Self::HeaderToString(err) => {
|
2022-12-20 08:37:12 +03:00
|
|
|
// trace!(?err, "HeaderToString");
|
2022-10-27 00:39:26 +03:00
|
|
|
(
|
|
|
|
StatusCode::BAD_REQUEST,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
&format!("{}", err),
|
|
|
|
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2022-10-31 23:05:58 +03:00
|
|
|
Self::UlidDecodeError(err) => {
|
2022-12-20 08:37:12 +03:00
|
|
|
// trace!(?err, "UlidDecodeError");
|
2022-10-31 23:05:58 +03:00
|
|
|
(
|
|
|
|
StatusCode::BAD_REQUEST,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
&format!("{}", err),
|
|
|
|
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// TODO: stat?
|
2022-09-12 17:33:55 +03:00
|
|
|
Self::UnknownKey => (
|
|
|
|
StatusCode::UNAUTHORIZED,
|
|
|
|
JsonRpcForwardedResponse::from_str(
|
|
|
|
"unknown api key!",
|
|
|
|
Some(StatusCode::UNAUTHORIZED.as_u16().into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
),
|
2023-01-19 03:17:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IntoResponse for FrontendErrorResponse {
|
|
|
|
fn into_response(self) -> Response {
|
|
|
|
// TODO: include the request id in these so that users can give us something that will point to logs
|
|
|
|
// TODO: status code is in the jsonrpc response and is also the first item in the tuple. DRY
|
|
|
|
let (status_code, response) = self.into_response_parts();
|
2022-08-17 01:52:12 +03:00
|
|
|
|
2022-09-10 03:12:14 +03:00
|
|
|
(status_code, Json(response)).into_response()
|
2022-08-16 22:29:00 +03:00
|
|
|
}
|
|
|
|
}
|
2022-06-16 05:53:37 +03:00
|
|
|
|
2022-08-10 05:37:34 +03:00
|
|
|
pub async fn handler_404() -> Response {
|
2022-09-10 03:12:14 +03:00
|
|
|
FrontendErrorResponse::NotFound.into_response()
|
2022-06-05 22:58:47 +03:00
|
|
|
}
|