diff --git a/src/web/mod.rs b/src/web/mod.rs index 2440df4a..c5932a13 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,3 +1,5 @@ +use std::any::Any; + use askama::Template; use axum::{ Router, @@ -6,7 +8,7 @@ use axum::{ response::{Html, IntoResponse, Response}, }; use conduwuit_service::state; -use tower_http::set_header::SetResponseHeaderLayer; +use tower_http::{catch_panic::CatchPanicLayer, set_header::SetResponseHeaderLayer}; use tower_sec_fetch::SecFetchLayer; use crate::pages::TemplateContext; @@ -26,7 +28,7 @@ enum WebError { QueryRejection(#[from] QueryRejection), #[error("{0}")] FormRejection(#[from] FormRejection), - #[error("Bad request: {0}")] + #[error("{0}")] BadRequest(String), #[error("This page does not exist.")] @@ -34,8 +36,10 @@ enum WebError { #[error("Failed to render template: {0}")] Render(#[from] askama::Error), - #[error("Internal server error: {0}")] + #[error("{0}")] InternalError(#[from] conduwuit_core::Error), + #[error("Request handler panicked! {0}")] + Panic(String), } impl IntoResponse for WebError { @@ -85,8 +89,20 @@ pub fn build() -> Router { Router::new() .merge(resources::build()) .merge(password_reset::build()) + .merge(debug::build()) .fallback(async || WebError::NotFound), ) + .layer(CatchPanicLayer::custom(|panic: Box| { + let details = if let Some(s) = panic.downcast_ref::() { + s.clone() + } else if let Some(s) = panic.downcast_ref::<&str>() { + (*s).to_owned() + } else { + "(opaque panic payload)".to_owned() + }; + + WebError::Panic(details).into_response() + })) .layer(SetResponseHeaderLayer::if_not_present( header::CONTENT_SECURITY_POLICY, HeaderValue::from_static("default-src 'self'; img-src 'self' data:;"), diff --git a/src/web/pages/debug.rs b/src/web/pages/debug.rs new file mode 100644 index 00000000..9522480a --- /dev/null +++ b/src/web/pages/debug.rs @@ -0,0 +1,17 @@ +use std::convert::Infallible; + +use axum::{Router, routing::get}; +use conduwuit_core::Error; + +use crate::WebError; + +pub(crate) fn build() -> Router { + Router::new() + .route("/_debug/panic", get(async || -> Infallible { panic!("Guru meditation error") })) + .route( + "/_debug/error", + get(async || -> WebError { + Error::Err(std::borrow::Cow::Borrowed("Guru meditation error")).into() + }), + ) +} diff --git a/src/web/pages/mod.rs b/src/web/pages/mod.rs index cbf78dfc..58977fd0 100644 --- a/src/web/pages/mod.rs +++ b/src/web/pages/mod.rs @@ -1,4 +1,5 @@ mod components; +pub(super) mod debug; pub(super) mod index; pub(super) mod password_reset; pub(super) mod resources; diff --git a/src/web/pages/password_reset.rs b/src/web/pages/password_reset.rs index f5d9b8d0..1c33f0f8 100644 --- a/src/web/pages/password_reset.rs +++ b/src/web/pages/password_reset.rs @@ -18,6 +18,8 @@ use crate::{ template, }; +const INVALID_TOKEN_ERROR: &str = "Invalid reset token. Your reset link may have expired."; + #[derive(Deserialize)] struct PasswordResetQuery { token: String, @@ -67,7 +69,7 @@ async fn password_reset_form( reset_form: Form<'static>, ) -> Result { let Some(token) = services.password_reset.check_token(&query.token).await else { - return Err(WebError::BadRequest("Invalid reset token".to_owned())); + return Err(WebError::BadRequest(INVALID_TOKEN_ERROR.to_owned())); }; let user_card = UserCard::for_local_user(&services, &token.info.user).await; @@ -96,7 +98,7 @@ async fn post_password_reset( match form.validate() { | Ok(()) => { let Some(token) = services.password_reset.check_token(&query.token).await else { - return Err(WebError::BadRequest("Invalid reset token".to_owned())); + return Err(WebError::BadRequest(INVALID_TOKEN_ERROR.to_owned())); }; let user_id = token.info.user.clone(); diff --git a/src/web/pages/templates/error.html.j2 b/src/web/pages/templates/error.html.j2 index 5bc34b5b..71afe195 100644 --- a/src/web/pages/templates/error.html.j2 +++ b/src/web/pages/templates/error.html.j2 @@ -24,12 +24,17 @@

{% if status == StatusCode::NOT_FOUND %} Not found + {% else if status == StatusCode::INTERNAL_SERVER_ERROR %} + Internal server error {% else %} - Request error + Bad request {% endif %} - (︶^︶)

+ {% if status == StatusCode::INTERNAL_SERVER_ERROR %} +

Please submit a bug report 🥺

+ {% endif %} +
{{ error }}