From 06c401f9fc9b0c2eb3f284825df2d6ad508bf044 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Thu, 12 Dec 2024 08:36:57 -0500 Subject: [PATCH] add index authz for coaching session resources There is a lot of duplicated code here. The plan is to defer refactoring for now and take a look at a better error handling system. --- web/src/protect/actions.rs | 51 +++++++++++++++++++++++++++++++++++ web/src/protect/agreements.rs | 51 +++++++++++++++++++++++++++++++++++ web/src/protect/mod.rs | 3 +++ web/src/protect/notes.rs | 51 +++++++++++++++++++++++++++++++++++ web/src/router.rs | 9 +++++++ 5 files changed, 165 insertions(+) create mode 100644 web/src/protect/actions.rs create mode 100644 web/src/protect/agreements.rs create mode 100644 web/src/protect/notes.rs diff --git a/web/src/protect/actions.rs b/web/src/protect/actions.rs new file mode 100644 index 0000000..057d922 --- /dev/null +++ b/web/src/protect/actions.rs @@ -0,0 +1,51 @@ +use crate::{extractors::authenticated_user::AuthenticatedUser, AppState}; +use axum::{ + extract::{Query, Request, State}, + http::StatusCode, + middleware::Next, + response::IntoResponse, +}; +use entity::Id; +use entity_api::coaching_session; +use log::*; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub(crate) struct QueryParams { + coaching_session_id: Id, +} + +/// Checks that coaching relationship record associated with the coaching session +/// referenced by `coaching_session_id exists and that the authenticated user is associated with it. +/// Intended to be given to axum::middleware::from_fn_with_state in the router +pub(crate) async fn index( + State(app_state): State, + AuthenticatedUser(user): AuthenticatedUser, + Query(params): Query, + request: Request, + next: Next, +) -> impl IntoResponse { + match coaching_session::find_by_id_with_coaching_relationship( + app_state.db_conn_ref(), + params.coaching_session_id, + ) + .await + { + Ok((_coaching_session, coaching_relationship)) => { + if coaching_relationship.coach_id == user.id + || coaching_relationship.coachee_id == user.id + { + // User has access to coaching relationship + next.run(request).await + } else { + // User does not have access to coaching relationship + (StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response() + } + } + Err(e) => { + error!("Error authorizing overarching goals index{:?}", e); + + (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response() + } + } +} diff --git a/web/src/protect/agreements.rs b/web/src/protect/agreements.rs new file mode 100644 index 0000000..057d922 --- /dev/null +++ b/web/src/protect/agreements.rs @@ -0,0 +1,51 @@ +use crate::{extractors::authenticated_user::AuthenticatedUser, AppState}; +use axum::{ + extract::{Query, Request, State}, + http::StatusCode, + middleware::Next, + response::IntoResponse, +}; +use entity::Id; +use entity_api::coaching_session; +use log::*; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub(crate) struct QueryParams { + coaching_session_id: Id, +} + +/// Checks that coaching relationship record associated with the coaching session +/// referenced by `coaching_session_id exists and that the authenticated user is associated with it. +/// Intended to be given to axum::middleware::from_fn_with_state in the router +pub(crate) async fn index( + State(app_state): State, + AuthenticatedUser(user): AuthenticatedUser, + Query(params): Query, + request: Request, + next: Next, +) -> impl IntoResponse { + match coaching_session::find_by_id_with_coaching_relationship( + app_state.db_conn_ref(), + params.coaching_session_id, + ) + .await + { + Ok((_coaching_session, coaching_relationship)) => { + if coaching_relationship.coach_id == user.id + || coaching_relationship.coachee_id == user.id + { + // User has access to coaching relationship + next.run(request).await + } else { + // User does not have access to coaching relationship + (StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response() + } + } + Err(e) => { + error!("Error authorizing overarching goals index{:?}", e); + + (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response() + } + } +} diff --git a/web/src/protect/mod.rs b/web/src/protect/mod.rs index 28efda7..11c495b 100644 --- a/web/src/protect/mod.rs +++ b/web/src/protect/mod.rs @@ -1,3 +1,6 @@ +pub(crate) mod actions; +pub(crate) mod agreements; pub(crate) mod coaching_relationships; pub(crate) mod coaching_sessions; +pub(crate) mod notes; pub(crate) mod overarching_goals; diff --git a/web/src/protect/notes.rs b/web/src/protect/notes.rs new file mode 100644 index 0000000..057d922 --- /dev/null +++ b/web/src/protect/notes.rs @@ -0,0 +1,51 @@ +use crate::{extractors::authenticated_user::AuthenticatedUser, AppState}; +use axum::{ + extract::{Query, Request, State}, + http::StatusCode, + middleware::Next, + response::IntoResponse, +}; +use entity::Id; +use entity_api::coaching_session; +use log::*; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub(crate) struct QueryParams { + coaching_session_id: Id, +} + +/// Checks that coaching relationship record associated with the coaching session +/// referenced by `coaching_session_id exists and that the authenticated user is associated with it. +/// Intended to be given to axum::middleware::from_fn_with_state in the router +pub(crate) async fn index( + State(app_state): State, + AuthenticatedUser(user): AuthenticatedUser, + Query(params): Query, + request: Request, + next: Next, +) -> impl IntoResponse { + match coaching_session::find_by_id_with_coaching_relationship( + app_state.db_conn_ref(), + params.coaching_session_id, + ) + .await + { + Ok((_coaching_session, coaching_relationship)) => { + if coaching_relationship.coach_id == user.id + || coaching_relationship.coachee_id == user.id + { + // User has access to coaching relationship + next.run(request).await + } else { + // User does not have access to coaching relationship + (StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response() + } + } + Err(e) => { + error!("Error authorizing overarching goals index{:?}", e); + + (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response() + } + } +} diff --git a/web/src/router.rs b/web/src/router.rs index 31b368c..ed4ae71 100644 --- a/web/src/router.rs +++ b/web/src/router.rs @@ -124,6 +124,10 @@ fn action_routes(app_state: AppState) -> Router { .route("/actions", post(action_controller::create)) .route("/actions/:id", put(action_controller::update)) .route("/actions", get(action_controller::index)) + .route_layer(from_fn_with_state( + app_state.clone(), + protect::actions::index, + )) .route("/actions/:id", get(action_controller::read)) .route("/actions/:id/status", put(action_controller::update_status)) .route("/actions/:id", delete(action_controller::delete)) @@ -136,6 +140,10 @@ fn agreement_routes(app_state: AppState) -> Router { .route("/agreements", post(agreement_controller::create)) .route("/agreements/:id", put(agreement_controller::update)) .route("/agreements", get(agreement_controller::index)) + .route_layer(from_fn_with_state( + app_state.clone(), + protect::agreements::index, + )) .route("/agreements/:id", get(agreement_controller::read)) .route("/agreements/:id", delete(agreement_controller::delete)) .route_layer(login_required!(Backend, login_url = "/login")) @@ -165,6 +173,7 @@ fn note_routes(app_state: AppState) -> Router { .route("/notes", post(note_controller::create)) .route("/notes/:id", put(note_controller::update)) .route("/notes", get(note_controller::index)) + .route_layer(from_fn_with_state(app_state.clone(), protect::notes::index)) .route("/notes/:id", get(note_controller::read)) .route_layer(login_required!(Backend, login_url = "/login")) .with_state(app_state)