feat: Add a panic handler and clean up error page

This commit is contained in:
Ginger
2026-03-18 13:43:34 -04:00
parent 50c94d85a1
commit 728c5828ba
5 changed files with 48 additions and 7 deletions
+19 -3
View File
@@ -1,3 +1,5 @@
use std::any::Any;
use askama::Template; use askama::Template;
use axum::{ use axum::{
Router, Router,
@@ -6,7 +8,7 @@ use axum::{
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
}; };
use conduwuit_service::state; 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 tower_sec_fetch::SecFetchLayer;
use crate::pages::TemplateContext; use crate::pages::TemplateContext;
@@ -26,7 +28,7 @@ enum WebError {
QueryRejection(#[from] QueryRejection), QueryRejection(#[from] QueryRejection),
#[error("{0}")] #[error("{0}")]
FormRejection(#[from] FormRejection), FormRejection(#[from] FormRejection),
#[error("Bad request: {0}")] #[error("{0}")]
BadRequest(String), BadRequest(String),
#[error("This page does not exist.")] #[error("This page does not exist.")]
@@ -34,8 +36,10 @@ enum WebError {
#[error("Failed to render template: {0}")] #[error("Failed to render template: {0}")]
Render(#[from] askama::Error), Render(#[from] askama::Error),
#[error("Internal server error: {0}")] #[error("{0}")]
InternalError(#[from] conduwuit_core::Error), InternalError(#[from] conduwuit_core::Error),
#[error("Request handler panicked! {0}")]
Panic(String),
} }
impl IntoResponse for WebError { impl IntoResponse for WebError {
@@ -85,8 +89,20 @@ pub fn build() -> Router<state::State> {
Router::new() Router::new()
.merge(resources::build()) .merge(resources::build())
.merge(password_reset::build()) .merge(password_reset::build())
.merge(debug::build())
.fallback(async || WebError::NotFound), .fallback(async || WebError::NotFound),
) )
.layer(CatchPanicLayer::custom(|panic: Box<dyn Any + Send + 'static>| {
let details = if let Some(s) = panic.downcast_ref::<String>() {
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( .layer(SetResponseHeaderLayer::if_not_present(
header::CONTENT_SECURITY_POLICY, header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self'; img-src 'self' data:;"), HeaderValue::from_static("default-src 'self'; img-src 'self' data:;"),
+17
View File
@@ -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<crate::State> {
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()
}),
)
}
+1
View File
@@ -1,4 +1,5 @@
mod components; mod components;
pub(super) mod debug;
pub(super) mod index; pub(super) mod index;
pub(super) mod password_reset; pub(super) mod password_reset;
pub(super) mod resources; pub(super) mod resources;
+4 -2
View File
@@ -18,6 +18,8 @@ use crate::{
template, template,
}; };
const INVALID_TOKEN_ERROR: &str = "Invalid reset token. Your reset link may have expired.";
#[derive(Deserialize)] #[derive(Deserialize)]
struct PasswordResetQuery { struct PasswordResetQuery {
token: String, token: String,
@@ -67,7 +69,7 @@ async fn password_reset_form(
reset_form: Form<'static>, reset_form: Form<'static>,
) -> Result<impl IntoResponse, WebError> { ) -> Result<impl IntoResponse, WebError> {
let Some(token) = services.password_reset.check_token(&query.token).await else { 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; let user_card = UserCard::for_local_user(&services, &token.info.user).await;
@@ -96,7 +98,7 @@ async fn post_password_reset(
match form.validate() { match form.validate() {
| Ok(()) => { | Ok(()) => {
let Some(token) = services.password_reset.check_token(&query.token).await else { 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(); let user_id = token.info.user.clone();
+7 -2
View File
@@ -24,12 +24,17 @@
<h1> <h1>
{% if status == StatusCode::NOT_FOUND %} {% if status == StatusCode::NOT_FOUND %}
Not found Not found
{% else if status == StatusCode::INTERNAL_SERVER_ERROR %}
Internal server error
{% else %} {% else %}
Request error Bad request
{% endif %} {% endif %}
<small aria-hidden>(︶^︶)</small>
</h1> </h1>
{% if status == StatusCode::INTERNAL_SERVER_ERROR %}
<p>Please <a href="https://forgejo.ellis.link/continuwuation/continuwuity/issues/new">submit a bug report</a> 🥺</p>
{% endif %}
<pre><code>{{ error }}</code></pre> <pre><code>{{ error }}</code></pre>
</div> </div>