From ca7b26cf1806805de841b0eeb1332fd5f034397c Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 10 May 2022 16:51:12 +0200 Subject: [PATCH] Simplify error handling in user-facing routes --- crates/axum-utils/src/fancy_error.rs | 94 +++------------ crates/axum-utils/src/lib.rs | 2 +- crates/axum-utils/src/user_authorization.rs | 9 ++ crates/handlers/src/health.rs | 7 +- crates/handlers/src/lib.rs | 113 +++++++++++------- crates/handlers/src/oauth2/userinfo.rs | 28 +---- crates/handlers/src/views/account/emails.rs | 70 ++++------- crates/handlers/src/views/account/mod.rs | 25 +--- crates/handlers/src/views/account/password.rs | 40 ++----- crates/handlers/src/views/index.rs | 17 +-- crates/handlers/src/views/login.rs | 36 ++---- crates/handlers/src/views/logout.rs | 21 +--- crates/handlers/src/views/reauth.rs | 40 ++----- crates/handlers/src/views/register.rs | 41 ++----- crates/handlers/src/views/verify.rs | 29 ++--- crates/static-files/src/lib.rs | 1 - crates/templates/src/context.rs | 2 +- 17 files changed, 192 insertions(+), 383 deletions(-) diff --git a/crates/axum-utils/src/fancy_error.rs b/crates/axum-utils/src/fancy_error.rs index 1baf2f2b..5d078b70 100644 --- a/crates/axum-utils/src/fancy_error.rs +++ b/crates/axum-utils/src/fancy_error.rs @@ -12,92 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{convert::Infallible, error::Error}; - -use async_trait::async_trait; use axum::{ - body::{HttpBody, StreamBody}, - extract::{Extension, FromRequest, RequestParts}, http::StatusCode, response::{IntoResponse, Response}, + Extension, }; -use futures_util::FutureExt; -use headers::{ContentType, HeaderMapExt}; -use mas_templates::{ErrorContext, Templates}; -use sqlx::PgPool; - -struct DatabaseConnection(sqlx::pool::PoolConnection); - -#[async_trait] -impl FromRequest for DatabaseConnection -where - B: Send, -{ - type Rejection = FancyError; - - async fn from_request(req: &mut RequestParts) -> Result { - let Extension(templates) = Extension::::from_request(req) - .await - .map_err(internal_error)?; - - let Extension(pool) = Extension::::from_request(req) - .await - .map_err(fancy_error(templates))?; - - let conn = pool.acquire().await.map_err(internal_error)?; - - Ok(Self(conn)) - } -} - -pub fn fancy_error( - templates: Templates, -) -> impl Fn(E) -> FancyError { - move |error: E| FancyError { - templates: Some(templates.clone()), - error: Box::new(error), - } -} - -pub fn internal_error(error: E) -> FancyError -where - E: Error, -{ - FancyError { - templates: None, - error: Box::new(error), - } -} +use mas_templates::ErrorContext; pub struct FancyError { - templates: Option, - error: Box, + context: ErrorContext, +} + +impl From for FancyError { + fn from(err: E) -> Self { + let context = ErrorContext::new().with_description(err.to_string()); + FancyError { context } + } } impl IntoResponse for FancyError { fn into_response(self) -> Response { - let error = format!("{}", self.error); - let context = ErrorContext::new().with_description(error.clone()); - let body = match self.templates { - Some(templates) => { - let stream = (async move { - Ok::<_, Infallible>(match templates.render_error(&context).await { - Ok(s) => s, - Err(_e) => "failed to render error template".to_string(), - }) - }) - .into_stream(); - - StreamBody::new(stream).boxed_unsync() - } - None => axum::body::Full::from(error) - .map_err(|_e| unreachable!()) - .boxed_unsync(), - }; - - let mut res = Response::new(body); - *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; - res.headers_mut().typed_insert(ContentType::html()); - res + let error = format!("{:?}", self.context); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Extension(self.context), + error, + ) + .into_response() } } diff --git a/crates/axum-utils/src/lib.rs b/crates/axum-utils/src/lib.rs index e11455f1..8281126d 100644 --- a/crates/axum-utils/src/lib.rs +++ b/crates/axum-utils/src/lib.rs @@ -21,6 +21,6 @@ pub mod user_authorization; pub use self::{ cookies::CookieExt, - fancy_error::{fancy_error, internal_error, FancyError}, + fancy_error::FancyError, session::{SessionInfo, SessionInfoExt}, }; diff --git a/crates/axum-utils/src/user_authorization.rs b/crates/axum-utils/src/user_authorization.rs index 87bd1e9e..ffdc2634 100644 --- a/crates/axum-utils/src/user_authorization.rs +++ b/crates/axum-utils/src/user_authorization.rs @@ -32,6 +32,7 @@ use mas_storage::{ }; use serde::{de::DeserializeOwned, Deserialize}; use sqlx::{Acquire, Postgres}; +use thiserror::Error; #[derive(Debug, Deserialize)] struct AuthorizedForm { @@ -111,10 +112,18 @@ pub enum UserAuthorizationError { InternalError(Box), } +#[derive(Debug, Error)] pub enum AuthorizationVerificationError { + #[error("missing token")] MissingToken, + + #[error("invalid token")] InvalidToken, + + #[error("missing form")] MissingForm, + + #[error(transparent)] InternalError(Box), } diff --git a/crates/handlers/src/health.rs b/crates/handlers/src/health.rs index 5f8729de..7658bb21 100644 --- a/crates/handlers/src/health.rs +++ b/crates/handlers/src/health.rs @@ -13,19 +13,18 @@ // limitations under the License. use axum::{extract::Extension, response::IntoResponse}; -use mas_axum_utils::{internal_error, FancyError}; +use mas_axum_utils::FancyError; use sqlx::PgPool; use tracing::{info_span, Instrument}; pub async fn get(Extension(pool): Extension) -> Result { - let mut conn = pool.acquire().await.map_err(internal_error)?; + let mut conn = pool.acquire().await?; sqlx::query("SELECT $1") .bind(1_i64) .execute(&mut conn) .instrument(info_span!("DB health")) - .await - .map_err(internal_error)?; + .await?; Ok("ok") } diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 01b783c2..9be667ab 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -19,11 +19,12 @@ clippy::unused_async // Some axum handlers need that )] -use std::{sync::Arc, time::Duration}; +use std::{convert::Infallible, sync::Arc, time::Duration}; use axum::{ body::HttpBody, extract::Extension, + response::{Html, IntoResponse}, routing::{get, on, post, MethodFilter}, Router, }; @@ -33,8 +34,9 @@ use mas_email::Mailer; use mas_http::CorsLayerExt; use mas_jose::StaticKeystore; use mas_router::{Route, UrlBuilder}; -use mas_templates::Templates; +use mas_templates::{ErrorContext, Templates}; use sqlx::PgPool; +use tower::util::ThenLayer; use tower_http::cors::{Any, CorsLayer}; mod health; @@ -42,6 +44,7 @@ mod oauth2; mod views; #[must_use] +#[allow(clippy::too_many_lines, clippy::missing_panics_doc)] pub fn router( pool: &PgPool, templates: &Templates, @@ -102,47 +105,71 @@ where .max_age(Duration::from_secs(60 * 60)), ); - Router::new() - .route(mas_router::Index::route(), get(self::views::index::get)) - .route(mas_router::Healthcheck::route(), get(self::health::get)) - .route( - mas_router::Login::route(), - get(self::views::login::get).post(self::views::login::post), - ) - .route(mas_router::Logout::route(), post(self::views::logout::post)) - .route( - mas_router::Reauth::route(), - get(self::views::reauth::get).post(self::views::reauth::post), - ) - .route( - mas_router::Register::route(), - get(self::views::register::get).post(self::views::register::post), - ) - .route( - mas_router::VerifyEmail::route(), - get(self::views::verify::get), - ) - .route(mas_router::Account::route(), get(self::views::account::get)) - .route( - mas_router::AccountPassword::route(), - get(self::views::account::password::get).post(self::views::account::password::post), - ) - .route( - mas_router::AccountEmails::route(), - get(self::views::account::emails::get).post(self::views::account::emails::post), - ) - .route( - mas_router::OAuth2AuthorizationEndpoint::route(), - get(self::oauth2::authorization::get), - ) - .route( - mas_router::ContinueAuthorizationGrant::route(), - get(self::oauth2::authorization::complete::get), - ) - .route( - mas_router::Consent::route(), - get(self::oauth2::consent::get).post(self::oauth2::consent::post), - ) + let human_router = { + let templates = templates.clone(); + Router::new() + .route(mas_router::Index::route(), get(self::views::index::get)) + .route(mas_router::Healthcheck::route(), get(self::health::get)) + .route( + mas_router::Login::route(), + get(self::views::login::get).post(self::views::login::post), + ) + .route(mas_router::Logout::route(), post(self::views::logout::post)) + .route( + mas_router::Reauth::route(), + get(self::views::reauth::get).post(self::views::reauth::post), + ) + .route( + mas_router::Register::route(), + get(self::views::register::get).post(self::views::register::post), + ) + .route( + mas_router::VerifyEmail::route(), + get(self::views::verify::get), + ) + .route(mas_router::Account::route(), get(self::views::account::get)) + .route( + mas_router::AccountPassword::route(), + get(self::views::account::password::get).post(self::views::account::password::post), + ) + .route( + mas_router::AccountEmails::route(), + get(self::views::account::emails::get).post(self::views::account::emails::post), + ) + .route( + mas_router::OAuth2AuthorizationEndpoint::route(), + get(self::oauth2::authorization::get), + ) + .route( + mas_router::ContinueAuthorizationGrant::route(), + get(self::oauth2::authorization::complete::get), + ) + .route( + mas_router::Consent::route(), + get(self::oauth2::consent::get).post(self::oauth2::consent::post), + ) + .layer(ThenLayer::new( + move |result: Result| async move { + let response = result.unwrap(); + + if response.status().is_server_error() { + // Error responses should have an ErrorContext attached to them + let ext = response.extensions().get::(); + if let Some(ctx) = ext { + if let Ok(res) = templates.render_error(ctx).await { + let (mut parts, _original_body) = response.into_parts(); + parts.headers.remove(CONTENT_TYPE); + return Ok((parts, Html(res)).into_response()); + } + } + } + + Ok(response) + }, + )) + }; + + human_router .merge(api_router) .layer(Extension(pool.clone())) .layer(Extension(templates.clone())) diff --git a/crates/handlers/src/oauth2/userinfo.rs b/crates/handlers/src/oauth2/userinfo.rs index b0867fe4..edf8b2a8 100644 --- a/crates/handlers/src/oauth2/userinfo.rs +++ b/crates/handlers/src/oauth2/userinfo.rs @@ -20,8 +20,7 @@ use axum::{ Json, TypedHeader, }; use headers::ContentType; -use hyper::StatusCode; -use mas_axum_utils::{internal_error, user_authorization::UserAuthorization}; +use mas_axum_utils::{user_authorization::UserAuthorization, FancyError}; use mas_jose::{DecodedJsonWebToken, SigningKeystore, StaticKeystore}; use mas_router::UrlBuilder; use mime::Mime; @@ -52,18 +51,11 @@ pub async fn get( Extension(pool): Extension, Extension(key_store): Extension>, user_authorization: UserAuthorization, -) -> Result { +) -> Result { // TODO: error handling - let mut conn = pool - .acquire() - .await - .map_err(internal_error) - .map_err(IntoResponse::into_response)?; + let mut conn = pool.acquire().await?; - let session = user_authorization - .protected(&mut conn) - .await - .map_err(IntoResponse::into_response)?; + let session = user_authorization.protected(&mut conn).await?; let user = session.browser_session.user; let mut user_info = UserInfo { @@ -81,11 +73,7 @@ pub async fn get( } if let Some(alg) = session.client.userinfo_signed_response_alg { - let header = key_store - .prepare_header(alg) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) - .map_err(IntoResponse::into_response)?; + let header = key_store.prepare_header(alg).await?; let user_info = SignedUserInfo { iss: url_builder.oidc_issuer().to_string(), @@ -94,11 +82,7 @@ pub async fn get( }; let user_info = DecodedJsonWebToken::new(header, user_info); - let user_info = user_info - .sign(key_store.as_ref()) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) - .map_err(IntoResponse::into_response)?; + let user_info = user_info.sign(key_store.as_ref()).await?; let token = user_info.serialize(); let application_jwt: Mime = "application/jwt".parse().unwrap(); diff --git a/crates/handlers/src/views/account/emails.rs b/crates/handlers/src/views/account/emails.rs index 6d56d472..f172efa5 100644 --- a/crates/handlers/src/views/account/emails.rs +++ b/crates/handlers/src/views/account/emails.rs @@ -20,7 +20,7 @@ use axum_extra::extract::PrivateCookieJar; use lettre::{message::Mailbox, Address}; use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, - fancy_error, FancyError, SessionInfoExt, + FancyError, SessionInfoExt, }; use mas_config::Encrypter; use mas_data_model::{BrowserSession, User, UserEmail}; @@ -53,17 +53,11 @@ pub(crate) async fn get( Extension(pool): Extension, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut conn).await?; if let Some(session) = maybe_session { render(templates, session, cookie_jar, &mut conn).await @@ -81,18 +75,13 @@ async fn render( ) -> Result { let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); - let emails = get_user_emails(executor, &session.user) - .await - .map_err(fancy_error(templates.clone()))?; + let emails = get_user_emails(executor, &session.user).await?; let ctx = AccountEmailsContext::new(emails) .with_session(session) .with_csrf(csrf_token.form_value()); - let content = templates - .render_account_emails(&ctx) - .await - .map_err(fancy_error(templates))?; + let content = templates.render_account_emails(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } @@ -136,14 +125,11 @@ pub(crate) async fn post( cookie_jar: PrivateCookieJar, Form(form): Form>, ) -> Result { - let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?; + let mut txn = pool.begin().await?; let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut txn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut txn).await?; let mut session = if let Some(session) = maybe_session { session @@ -152,55 +138,39 @@ pub(crate) async fn post( return Ok((cookie_jar, login.go()).into_response()); }; - let form = cookie_jar - .verify_form(form) - .map_err(fancy_error(templates.clone()))?; + let form = cookie_jar.verify_form(form)?; match form { ManagementForm::Add { email } => { - let user_email = add_user_email(&mut txn, &session.user, email) - .await - .map_err(fancy_error(templates.clone()))?; + let user_email = add_user_email(&mut txn, &session.user, email).await?; start_email_verification(&mailer, &url_builder, &mut txn, &session.user, &user_email) - .await - .map_err(fancy_error(templates.clone()))?; + .await?; } ManagementForm::Remove { data } => { - let id = data.parse().map_err(fancy_error(templates.clone()))?; + let id = data.parse()?; - let email = get_user_email(&mut txn, &session.user, id) - .await - .map_err(fancy_error(templates.clone()))?; - remove_user_email(&mut txn, email) - .await - .map_err(fancy_error(templates.clone()))?; + let email = get_user_email(&mut txn, &session.user, id).await?; + remove_user_email(&mut txn, email).await?; } ManagementForm::ResendConfirmation { data } => { - let id = data.parse().map_err(fancy_error(templates.clone()))?; + let id = data.parse()?; - let user_email = get_user_email(&mut txn, &session.user, id) - .await - .map_err(fancy_error(templates.clone()))?; + let user_email = get_user_email(&mut txn, &session.user, id).await?; start_email_verification(&mailer, &url_builder, &mut txn, &session.user, &user_email) - .await - .map_err(fancy_error(templates.clone()))?; + .await?; } ManagementForm::SetPrimary { data } => { - let id = data.parse().map_err(fancy_error(templates.clone()))?; - let email = get_user_email(&mut txn, &session.user, id) - .await - .map_err(fancy_error(templates.clone()))?; - set_user_email_as_primary(&mut txn, &email) - .await - .map_err(fancy_error(templates.clone()))?; + let id = data.parse()?; + let email = get_user_email(&mut txn, &session.user, id).await?; + set_user_email_as_primary(&mut txn, &email).await?; session.user.primary_email = Some(email); } }; let reply = render(templates.clone(), session, cookie_jar, &mut txn).await?; - txn.commit().await.map_err(fancy_error(templates.clone()))?; + txn.commit().await?; Ok(reply) } diff --git a/crates/handlers/src/views/account/mod.rs b/crates/handlers/src/views/account/mod.rs index f881a2d5..51bc9900 100644 --- a/crates/handlers/src/views/account/mod.rs +++ b/crates/handlers/src/views/account/mod.rs @@ -20,7 +20,7 @@ use axum::{ response::{Html, IntoResponse, Response}, }; use axum_extra::extract::PrivateCookieJar; -use mas_axum_utils::{csrf::CsrfExt, fancy_error, FancyError, SessionInfoExt}; +use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt}; use mas_config::Encrypter; use mas_router::Route; use mas_storage::user::{count_active_sessions, get_user_emails}; @@ -32,18 +32,12 @@ pub(crate) async fn get( Extension(pool): Extension, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut conn).await?; let session = if let Some(session) = maybe_session { session @@ -52,22 +46,15 @@ pub(crate) async fn get( return Ok((cookie_jar, login.go()).into_response()); }; - let active_sessions = count_active_sessions(&mut conn, &session.user) - .await - .map_err(fancy_error(templates.clone()))?; + let active_sessions = count_active_sessions(&mut conn, &session.user).await?; - let emails = get_user_emails(&mut conn, &session.user) - .await - .map_err(fancy_error(templates.clone()))?; + let emails = get_user_emails(&mut conn, &session.user).await?; let ctx = AccountContext::new(active_sessions, emails) .with_session(session) .with_csrf(csrf_token.form_value()); - let content = templates - .render_account_index(&ctx) - .await - .map_err(fancy_error(templates.clone()))?; + let content = templates.render_account_index(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } diff --git a/crates/handlers/src/views/account/password.rs b/crates/handlers/src/views/account/password.rs index 056517fd..51cdd80a 100644 --- a/crates/handlers/src/views/account/password.rs +++ b/crates/handlers/src/views/account/password.rs @@ -20,7 +20,7 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, - fancy_error, FancyError, SessionInfoExt, + FancyError, SessionInfoExt, }; use mas_config::Encrypter; use mas_data_model::BrowserSession; @@ -45,17 +45,11 @@ pub(crate) async fn get( Extension(pool): Extension, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut conn).await?; if let Some(session) = maybe_session { render(templates, session, cookie_jar).await @@ -76,10 +70,7 @@ async fn render( .with_session(session) .with_csrf(csrf_token.form_value()); - let content = templates - .render_account_password(&ctx) - .await - .map_err(fancy_error(templates))?; + let content = templates.render_account_password(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } @@ -90,18 +81,13 @@ pub(crate) async fn post( cookie_jar: PrivateCookieJar, Form(form): Form>, ) -> Result { - let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?; + let mut txn = pool.begin().await?; - let form = cookie_jar - .verify_form(form) - .map_err(fancy_error(templates.clone()))?; + let form = cookie_jar.verify_form(form)?; let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut txn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut txn).await?; let mut session = if let Some(session) = maybe_session { session @@ -110,23 +96,19 @@ pub(crate) async fn post( return Ok((cookie_jar, login.go()).into_response()); }; - authenticate_session(&mut txn, &mut session, form.current_password) - .await - .map_err(fancy_error(templates.clone()))?; + authenticate_session(&mut txn, &mut session, form.current_password).await?; // TODO: display nice form errors if form.new_password != form.new_password_confirm { - return Err(anyhow::anyhow!("password mismatch")).map_err(fancy_error(templates.clone())); + return Err(anyhow::anyhow!("password mismatch").into()); } let phf = Argon2::default(); - set_password(&mut txn, phf, &session.user, &form.new_password) - .await - .map_err(fancy_error(templates.clone()))?; + set_password(&mut txn, phf, &session.user, &form.new_password).await?; let reply = render(templates.clone(), session, cookie_jar).await?; - txn.commit().await.map_err(fancy_error(templates.clone()))?; + txn.commit().await?; Ok(reply) } diff --git a/crates/handlers/src/views/index.rs b/crates/handlers/src/views/index.rs index 398bfb0c..e046961c 100644 --- a/crates/handlers/src/views/index.rs +++ b/crates/handlers/src/views/index.rs @@ -17,7 +17,7 @@ use axum::{ response::{Html, IntoResponse}, }; use axum_extra::extract::PrivateCookieJar; -use mas_axum_utils::{csrf::CsrfExt, fancy_error, FancyError, SessionInfoExt}; +use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt}; use mas_config::Encrypter; use mas_router::UrlBuilder; use mas_templates::{IndexContext, TemplateContext, Templates}; @@ -29,26 +29,17 @@ pub async fn get( Extension(pool): Extension, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); let (session_info, cookie_jar) = cookie_jar.session_info(); - let session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let session = session_info.load_session(&mut conn).await?; let ctx = IndexContext::new(url_builder.oidc_discovery()) .maybe_with_session(session) .with_csrf(csrf_token.form_value()); - let content = templates - .render_index(&ctx) - .await - .map_err(fancy_error(templates))?; + let content = templates.render_index(&ctx).await?; Ok((cookie_jar, Html(content))) } diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index 89273777..c7130e68 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -19,7 +19,7 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, - fancy_error, FancyError, SessionInfoExt, + FancyError, SessionInfoExt, }; use mas_config::Encrypter; use mas_data_model::errors::WrapFormError; @@ -44,28 +44,19 @@ pub(crate) async fn get( Query(query): Query, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut conn).await?; if maybe_session.is_some() { let reply = query.go_next(); Ok((cookie_jar, reply).into_response()) } else { let ctx = LoginContext::default(); - let next = query - .load_context(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let next = query.load_context(&mut conn).await?; let ctx = if let Some(next) = next { ctx.with_post_action(next) } else { @@ -76,10 +67,7 @@ pub(crate) async fn get( .with_register_link(register_link.to_string()) .with_csrf(csrf_token.form_value()); - let content = templates - .render_login(&ctx) - .await - .map_err(fancy_error(templates.clone()))?; + let content = templates.render_login(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } @@ -93,14 +81,9 @@ pub(crate) async fn post( Form(form): Form>, ) -> Result { use mas_storage::user::LoginError; - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; - let form = cookie_jar - .verify_form(form) - .map_err(fancy_error(templates.clone()))?; + let form = cookie_jar.verify_form(form)?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); @@ -121,10 +104,7 @@ pub(crate) async fn post( .with_form_error(errored_form) .with_csrf(csrf_token.form_value()); - let content = templates - .render_login(&ctx) - .await - .map_err(fancy_error(templates.clone()))?; + let content = templates.render_login(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } diff --git a/crates/handlers/src/views/logout.rs b/crates/handlers/src/views/logout.rs index 6306c744..902f7323 100644 --- a/crates/handlers/src/views/logout.rs +++ b/crates/handlers/src/views/logout.rs @@ -19,40 +19,31 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, - fancy_error, FancyError, SessionInfoExt, + FancyError, SessionInfoExt, }; use mas_config::Encrypter; use mas_storage::user::end_session; -use mas_templates::Templates; use sqlx::PgPool; pub(crate) async fn post( - Extension(templates): Extension, Extension(pool): Extension, cookie_jar: PrivateCookieJar, Form(form): Form>, ) -> Result { - let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?; + let mut txn = pool.begin().await?; - cookie_jar - .verify_form(form) - .map_err(fancy_error(templates.clone()))?; + cookie_jar.verify_form(form)?; let (session_info, mut cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut txn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut txn).await?; if let Some(session) = maybe_session { - end_session(&mut txn, &session) - .await - .map_err(fancy_error(templates.clone()))?; + end_session(&mut txn, &session).await?; cookie_jar = cookie_jar.update_session_info(&session_info.mark_session_ended()); } - txn.commit().await.map_err(fancy_error(templates))?; + txn.commit().await?; Ok((cookie_jar, Redirect::to("/login"))) } diff --git a/crates/handlers/src/views/reauth.rs b/crates/handlers/src/views/reauth.rs index df1543d6..3daa2482 100644 --- a/crates/handlers/src/views/reauth.rs +++ b/crates/handlers/src/views/reauth.rs @@ -19,7 +19,7 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, - fancy_error, FancyError, SessionInfoExt, + FancyError, SessionInfoExt, }; use mas_config::Encrypter; use mas_router::Route; @@ -41,18 +41,12 @@ pub(crate) async fn get( Query(query): Query, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut conn).await?; let session = if let Some(session) = maybe_session { session @@ -64,10 +58,7 @@ pub(crate) async fn get( }; let ctx = ReauthContext::default(); - let next = query - .load_context(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let next = query.load_context(&mut conn).await?; let ctx = if let Some(next) = next { ctx.with_post_action(next) } else { @@ -75,33 +66,24 @@ pub(crate) async fn get( }; let ctx = ctx.with_session(session).with_csrf(csrf_token.form_value()); - let content = templates - .render_reauth(&ctx) - .await - .map_err(fancy_error(templates.clone()))?; + let content = templates.render_reauth(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } pub(crate) async fn post( - Extension(templates): Extension, Extension(pool): Extension, Query(query): Query, cookie_jar: PrivateCookieJar, Form(form): Form>, ) -> Result { - let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?; + let mut txn = pool.begin().await?; - let form = cookie_jar - .verify_form(form) - .map_err(fancy_error(templates.clone()))?; + let form = cookie_jar.verify_form(form)?; let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut txn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut txn).await?; let mut session = if let Some(session) = maybe_session { session @@ -113,11 +95,9 @@ pub(crate) async fn post( }; // TODO: recover from errors here - authenticate_session(&mut txn, &mut session, form.password) - .await - .map_err(fancy_error(templates.clone()))?; + authenticate_session(&mut txn, &mut session, form.password).await?; let cookie_jar = cookie_jar.set_session(&session); - txn.commit().await.map_err(fancy_error(templates.clone()))?; + txn.commit().await?; let reply = query.go_next(); Ok((cookie_jar, reply).into_response()) diff --git a/crates/handlers/src/views/register.rs b/crates/handlers/src/views/register.rs index 03daf30f..8357631b 100644 --- a/crates/handlers/src/views/register.rs +++ b/crates/handlers/src/views/register.rs @@ -22,7 +22,7 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, - fancy_error, FancyError, SessionInfoExt, + FancyError, SessionInfoExt, }; use mas_config::Encrypter; use mas_router::Route; @@ -46,28 +46,19 @@ pub(crate) async fn get( Query(query): Query, cookie_jar: PrivateCookieJar, ) -> Result { - let mut conn = pool - .acquire() - .await - .map_err(fancy_error(templates.clone()))?; + let mut conn = pool.acquire().await?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut conn).await?; if maybe_session.is_some() { let reply = query.go_next(); Ok((cookie_jar, reply).into_response()) } else { let ctx = RegisterContext::default(); - let next = query - .load_context(&mut conn) - .await - .map_err(fancy_error(templates.clone()))?; + let next = query.load_context(&mut conn).await?; let ctx = if let Some(next) = next { ctx.with_post_action(next) } else { @@ -77,43 +68,33 @@ pub(crate) async fn get( let ctx = ctx.with_login_link(login_link.to_string()); let ctx = ctx.with_csrf(csrf_token.form_value()); - let content = templates - .render_register(&ctx) - .await - .map_err(fancy_error(templates.clone()))?; + let content = templates.render_register(&ctx).await?; Ok((cookie_jar, Html(content)).into_response()) } } pub(crate) async fn post( - Extension(templates): Extension, Extension(pool): Extension, Query(query): Query, cookie_jar: PrivateCookieJar, Form(form): Form>, ) -> Result { // TODO: display nice form errors - let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?; + let mut txn = pool.begin().await?; - let form = cookie_jar - .verify_form(form) - .map_err(fancy_error(templates.clone()))?; + let form = cookie_jar.verify_form(form)?; if form.password != form.password_confirm { - return Err(anyhow::anyhow!("password mismatch")).map_err(fancy_error(templates.clone())); + return Err(anyhow::anyhow!("password mismatch").into()); } let pfh = Argon2::default(); - let user = register_user(&mut txn, pfh, &form.username, &form.password) - .await - .map_err(fancy_error(templates.clone()))?; + let user = register_user(&mut txn, pfh, &form.username, &form.password).await?; - let session = start_session(&mut txn, user) - .await - .map_err(fancy_error(templates.clone()))?; + let session = start_session(&mut txn, user).await?; - txn.commit().await.map_err(fancy_error(templates.clone()))?; + txn.commit().await?; let cookie_jar = cookie_jar.set_session(&session); let reply = query.go_next(); diff --git a/crates/handlers/src/views/verify.rs b/crates/handlers/src/views/verify.rs index b2c11782..0799f68e 100644 --- a/crates/handlers/src/views/verify.rs +++ b/crates/handlers/src/views/verify.rs @@ -18,7 +18,7 @@ use axum::{ }; use axum_extra::extract::PrivateCookieJar; use chrono::Duration; -use mas_axum_utils::{csrf::CsrfExt, fancy_error, FancyError, SessionInfoExt}; +use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt}; use mas_config::Encrypter; use mas_storage::user::{ consume_email_verification, lookup_user_email_verification_code, mark_user_email_as_verified, @@ -32,40 +32,29 @@ pub(crate) async fn get( Path(code): Path, cookie_jar: PrivateCookieJar, ) -> Result { - let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?; + let mut txn = pool.begin().await?; // TODO: make those 8 hours configurable - let verification = lookup_user_email_verification_code(&mut txn, &code, Duration::hours(8)) - .await - .map_err(fancy_error(templates.clone()))?; + let verification = + lookup_user_email_verification_code(&mut txn, &code, Duration::hours(8)).await?; // TODO: display nice errors if the code was already consumed or expired - let verification = consume_email_verification(&mut txn, verification) - .await - .map_err(fancy_error(templates.clone()))?; + let verification = consume_email_verification(&mut txn, verification).await?; - let _email = mark_user_email_as_verified(&mut txn, verification.email) - .await - .map_err(fancy_error(templates.clone()))?; + let _email = mark_user_email_as_verified(&mut txn, verification.email).await?; let (csrf_token, cookie_jar) = cookie_jar.csrf_token(); let (session_info, cookie_jar) = cookie_jar.session_info(); - let maybe_session = session_info - .load_session(&mut txn) - .await - .map_err(fancy_error(templates.clone()))?; + let maybe_session = session_info.load_session(&mut txn).await?; let ctx = EmptyContext .maybe_with_session(maybe_session) .with_csrf(csrf_token.form_value()); - let content = templates - .render_email_verification_done(&ctx) - .await - .map_err(fancy_error(templates.clone()))?; + let content = templates.render_email_verification_done(&ctx).await?; - txn.commit().await.map_err(fancy_error(templates.clone()))?; + txn.commit().await?; Ok((cookie_jar, Html(content))) } diff --git a/crates/static-files/src/lib.rs b/crates/static-files/src/lib.rs index ccf41a8a..82a5a012 100644 --- a/crates/static-files/src/lib.rs +++ b/crates/static-files/src/lib.rs @@ -153,7 +153,6 @@ pub fn service( let builtin = self::builtin::service(); let svc = if let Some(path) = path { - // TODO: fallback seems to have issues let handler = ServeDir::new(path) .append_index_html_on_directories(false) .fallback(builtin); diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 2b7774eb..e45d34a3 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -555,7 +555,7 @@ impl FormPostContext { } /// Context used by the `error.html` template -#[derive(Default, Serialize)] +#[derive(Default, Serialize, Debug, Clone)] pub struct ErrorContext { code: Option<&'static str>, description: Option,