1
0
Fork 0
forked from 2sys/shoutdotdev

re-implement guards using AppError returns instead of macros

This commit is contained in:
Brent Schroeter 2025-02-26 13:10:47 -08:00
parent 7b6a84f011
commit da38946dbd
3 changed files with 53 additions and 48 deletions

View file

@ -7,6 +7,7 @@ use axum::response::{IntoResponse, Response};
#[derive(Debug)] #[derive(Debug)]
pub enum AppError { pub enum AppError {
InternalServerError(Error), InternalServerError(Error),
ForbiddenError(String),
} }
// Tell axum how to convert `AppError` into a response. // Tell axum how to convert `AppError` into a response.
@ -14,9 +15,13 @@ impl IntoResponse for AppError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
match self { match self {
Self::InternalServerError(err) => { Self::InternalServerError(err) => {
tracing::error!("Application error: {:#}", err); tracing::error!("Application error: {}", err);
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response()
} }
Self::ForbiddenError(client_message) => {
tracing::info!("Forbidden: {}", client_message);
(StatusCode::FORBIDDEN, client_message).into_response()
}
} }
} }
} }

View file

@ -1,45 +1,45 @@
macro_rules! require_team_membership { use deadpool_diesel::postgres::Connection;
($current_user:expr, $team_id:expr, $db_conn:expr) => {{ use diesel::prelude::*;
let current_user_id = $current_user.id.clone(); use uuid::Uuid;
match $db_conn
use crate::{
app_error::AppError, csrf::validate_csrf_token, team_memberships::TeamMembership, teams::Team,
users::User,
};
pub async fn require_team_membership(
current_user: &User,
team_id: &Uuid,
db_conn: &Connection,
) -> Result<Team, AppError> {
let current_user_id = current_user.id.clone();
let team_id = team_id.clone();
match db_conn
.interact(move |conn| { .interact(move |conn| {
crate::team_memberships::TeamMembership::all() TeamMembership::all()
.filter(crate::team_memberships::TeamMembership::with_user_id( .filter(TeamMembership::with_user_id(current_user_id))
current_user_id, .filter(TeamMembership::with_team_id(team_id))
))
.filter(crate::team_memberships::TeamMembership::with_team_id(
$team_id,
))
.first(conn) .first(conn)
.optional() .optional()
}) })
.await .await
.unwrap()? .unwrap()?
{ {
Some((team, _)) => team, Some((team, _)) => Ok(team),
None => { None => Err(AppError::ForbiddenError(
return Ok((
axum::http::StatusCode::FORBIDDEN,
"not a member of requested team".to_string(), "not a member of requested team".to_string(),
) )),
.into_response());
} }
} }
}};
}
pub(crate) use require_team_membership;
macro_rules! require_valid_csrf_token { pub async fn require_valid_csrf_token(
($csrf_token:expr, $current_user:expr, $db_conn:expr) => {{ csrf_token: &str,
if !crate::csrf::validate_csrf_token(&$db_conn, &$csrf_token, Some($current_user.id)) current_user: &User,
.await? db_conn: &Connection,
{ ) -> Result<(), AppError> {
return Ok(( if validate_csrf_token(db_conn, csrf_token, Some(current_user.id.clone())).await? {
axum::http::StatusCode::FORBIDDEN, Ok(())
"invalid CSRF token".to_string(), } else {
) Err(AppError::ForbiddenError("invalid CSRF token".to_string()))
.into_response());
} }
}};
} }
pub(crate) use require_valid_csrf_token;

View file

@ -111,8 +111,8 @@ async fn post_new_api_key(
CurrentUser(current_user): CurrentUser, CurrentUser(current_user): CurrentUser,
Form(form): Form<PostNewApiKeyForm>, Form(form): Form<PostNewApiKeyForm>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
guards::require_valid_csrf_token!(form.csrf_token, current_user, db_conn); guards::require_valid_csrf_token(&form.csrf_token, &current_user, &db_conn).await?;
let team = guards::require_team_membership!(current_user, team_id, db_conn); let team = guards::require_team_membership(&current_user, &team_id, &db_conn).await?;
ApiKey::generate_for_team(&db_conn, team.id.clone()).await?; ApiKey::generate_for_team(&db_conn, team.id.clone()).await?;
Ok(Redirect::to(&format!( Ok(Redirect::to(&format!(
@ -158,7 +158,7 @@ async fn post_new_team(
CurrentUser(current_user): CurrentUser, CurrentUser(current_user): CurrentUser,
Form(form): Form<PostNewTeamForm>, Form(form): Form<PostNewTeamForm>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
guards::require_valid_csrf_token!(form.csrf_token, current_user, db_conn); guards::require_valid_csrf_token(&form.csrf_token, &current_user, &db_conn).await?;
let team_id = Uuid::now_v7(); let team_id = Uuid::now_v7();
let team = Team { let team = Team {
@ -195,7 +195,7 @@ async fn projects_page(
Path(team_id): Path<Uuid>, Path(team_id): Path<Uuid>,
CurrentUser(current_user): CurrentUser, CurrentUser(current_user): CurrentUser,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
let team = guards::require_team_membership!(current_user, team_id, db_conn); let team = guards::require_team_membership(&current_user, &team_id, &db_conn).await?;
let team_id = team.id.clone(); let team_id = team.id.clone();
let api_keys = db_conn let api_keys = db_conn