feat: Implement permissions checks for hackathon and testimonial routes, enhancing security with header validation
This commit is contained in:
Generated
+1
@@ -2202,6 +2202,7 @@ dependencies = [
|
||||
"dotenvy",
|
||||
"http-body-util",
|
||||
"imphnen-entities",
|
||||
"imphnen-iam",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"lazy_static",
|
||||
|
||||
@@ -7,11 +7,12 @@ use super::{
|
||||
};
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{Extension, Json};
|
||||
use axum::{Extension, Json, http::HeaderMap};
|
||||
use imphnen_libs::{
|
||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||
ResponseSuccessDto,
|
||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||
ResponseSuccessDto,
|
||||
};
|
||||
use imphnen_iam::{PermissionsEnum, permissions_guard};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -68,10 +69,14 @@ pub async fn get_event_by_id(
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn post_create_event(
|
||||
Extension(state): Extension<AppState>,
|
||||
Json(payload): Json<EventsCreateRequestDto>,
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Json(payload): Json<EventsCreateRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
EventsService::create_event(&state, payload).await
|
||||
match permissions_guard(headers, Extension(state), vec![PermissionsEnum::Administrator]).await {
|
||||
Ok((_claims, state)) => EventsService::create_event(&state, payload).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -90,11 +95,15 @@ pub async fn post_create_event(
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn patch_update_event(
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(payload): Json<EventsUpdateRequestDto>,
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(payload): Json<EventsUpdateRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
EventsService::update_event(&state, id, payload).await
|
||||
match permissions_guard(headers, Extension(state), vec![PermissionsEnum::Administrator]).await {
|
||||
Ok((_claims, state)) => EventsService::update_event(&state, id, payload).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -112,8 +121,12 @@ pub async fn patch_update_event(
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn delete_event(
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
EventsService::delete_event(&state, id).await
|
||||
match permissions_guard(headers, Extension(state), vec![PermissionsEnum::Administrator]).await {
|
||||
Ok((_claims, state)) => EventsService::delete_event(&state, id).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ use super::{
|
||||
};
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{Extension, Json};
|
||||
use axum::{Extension, Json, http::HeaderMap};
|
||||
use imphnen_iam::UsersDetailQueryDto;
|
||||
use imphnen_libs::{
|
||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||
ResponseSuccessDto,
|
||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||
ResponseSuccessDto,
|
||||
};
|
||||
use imphnen_iam::permissions_guard;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -69,11 +70,15 @@ pub async fn get_testimonial_by_id(
|
||||
tag = "Testimonials"
|
||||
)]
|
||||
pub async fn post_create_testimonial(
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||
Json(payload): Json<TestimonialsCreateRequestDto>,
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||
Json(payload): Json<TestimonialsCreateRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
TestimonialsService::create_testimonial(&state, payload, &authenticated_user).await
|
||||
match permissions_guard(headers, Extension(state), vec![]).await {
|
||||
Ok((_claims, state)) => TestimonialsService::create_testimonial(&state, payload, &authenticated_user).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -92,13 +97,16 @@ pub async fn post_create_testimonial(
|
||||
tag = "Testimonials"
|
||||
)]
|
||||
pub async fn patch_update_testimonial(
|
||||
Path(id): Path<String>,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||
Json(payload): Json<TestimonialsUpdateRequestDto>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||
Json(payload): Json<TestimonialsUpdateRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
TestimonialsService::update_testimonial(&state, id, payload, &authenticated_user)
|
||||
.await
|
||||
match permissions_guard(headers, Extension(state), vec![]).await {
|
||||
Ok((_claims, state)) => TestimonialsService::update_testimonial(&state, id, payload, &authenticated_user).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -116,9 +124,13 @@ pub async fn patch_update_testimonial(
|
||||
tag = "Testimonials"
|
||||
)]
|
||||
pub async fn delete_testimonial(
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||
Path(id): Path<String>,
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
TestimonialsService::delete_testimonial(&state, id, &authenticated_user).await
|
||||
match permissions_guard(headers, Extension(state), vec![]).await {
|
||||
Ok((_claims, state)) => TestimonialsService::delete_testimonial(&state, id, &authenticated_user).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ use imphnen_iam::v1::teams::teams_dto::{TeamsCreateRequestDto, TeamsUpdateReques
|
||||
use imphnen_iam::v1::{auth, permissions, roles, users, teams};
|
||||
use imphnen_iam::v1::users::users_controller::FileUploadSchema;
|
||||
use utoipa::{
|
||||
Modify, OpenApi,
|
||||
openapi::security::{Http, HttpAuthScheme, SecurityScheme},
|
||||
Modify, OpenApi,
|
||||
openapi::security::{Http, HttpAuthScheme, SecurityScheme, SecurityRequirement},
|
||||
};
|
||||
|
||||
#[derive(OpenApi)]
|
||||
@@ -267,7 +267,43 @@ impl Modify for SecurityAddon {
|
||||
SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Walk all paths and add a Bearer security requirement to any operation
|
||||
// that declares 401 or 403 responses. This helps ensure protected
|
||||
// endpoints are shown with the Bearer lock in the generated docs
|
||||
// without having to annotate every controller manually.
|
||||
let paths = &mut openapi.paths;
|
||||
for (_path, path_item) in paths.paths.iter_mut() {
|
||||
// helper to process each possible operation on the path
|
||||
let mut process_op = |op: &mut Option<utoipa::openapi::path::Operation>| {
|
||||
if let Some(operation) = op.as_mut() {
|
||||
let mut has_auth_response = false;
|
||||
let responses = &operation.responses.responses;
|
||||
for status in responses.keys() {
|
||||
if status == "401" || status == "403" {
|
||||
has_auth_response = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if has_auth_response {
|
||||
// assign security requirement for Bearer if not already present
|
||||
if operation.security.is_none() {
|
||||
operation.security = Some(vec![SecurityRequirement::new::<&str, Vec<&str>, &str>("Bearer", vec![])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
process_op(&mut path_item.get);
|
||||
process_op(&mut path_item.post);
|
||||
process_op(&mut path_item.put);
|
||||
process_op(&mut path_item.patch);
|
||||
process_op(&mut path_item.delete);
|
||||
process_op(&mut path_item.options);
|
||||
process_op(&mut path_item.head);
|
||||
process_op(&mut path_item.trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn docs_router() -> utoipa::openapi::OpenApi {
|
||||
|
||||
@@ -8,6 +8,7 @@ imphnen-libs.workspace = true
|
||||
imphnen-utils.workspace = true
|
||||
|
||||
imphnen-entities.workspace = true
|
||||
imphnen-iam.workspace = true
|
||||
async-trait.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -14,10 +14,15 @@ use axum::{
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, post, put},
|
||||
};
|
||||
use axum::http::HeaderMap;
|
||||
use imphnen_iam::{PermissionsEnum, permissions_guard};
|
||||
|
||||
// Hackathon routes
|
||||
#[utoipa::path(
|
||||
post,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/hackathons",
|
||||
request_body = HackathonCreateRequestDto,
|
||||
responses(
|
||||
@@ -28,12 +33,16 @@ use axum::{
|
||||
tag = "Hackathons"
|
||||
)]
|
||||
pub async fn create_hackathon(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Json(payload): Json<HackathonCreateRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
match HackathonService::create_hackathon(payload, &state).await {
|
||||
match permissions_guard(headers, Extension(state), vec![PermissionsEnum::Administrator]).await {
|
||||
Ok((_claims, state)) => match HackathonService::create_hackathon(payload, &state).await {
|
||||
Ok(response) => (axum::http::StatusCode::CREATED, Json(response)).into_response(),
|
||||
Err(error) => (StatusCode::from_u16(error.status).unwrap(), Json(error)).into_response(),
|
||||
},
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +99,9 @@ pub async fn list_hackathons(
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/hackathons/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Hackathon ID")
|
||||
@@ -104,18 +116,25 @@ pub async fn list_hackathons(
|
||||
tag = "Hackathons"
|
||||
)]
|
||||
pub async fn update_hackathon(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(payload): Json<HackathonUpdateRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
match HackathonService::update_hackathon(id, payload, &state).await {
|
||||
Ok(response) => (axum::http::StatusCode::OK, Json(response)).into_response(),
|
||||
Err(error) => (StatusCode::from_u16(error.status).unwrap(), Json(error)).into_response(),
|
||||
match permissions_guard(headers, Extension(state), vec![PermissionsEnum::Administrator]).await {
|
||||
Ok((_claims, state)) => match HackathonService::update_hackathon(id, payload, &state).await {
|
||||
Ok(response) => (axum::http::StatusCode::OK, Json(response)).into_response(),
|
||||
Err(error) => (StatusCode::from_u16(error.status).unwrap(), Json(error)).into_response(),
|
||||
},
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/hackathons/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Hackathon ID")
|
||||
@@ -128,12 +147,16 @@ pub async fn update_hackathon(
|
||||
tag = "Hackathons"
|
||||
)]
|
||||
pub async fn delete_hackathon(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
match HackathonService::delete_hackathon(id, &state).await {
|
||||
Ok(response) => (axum::http::StatusCode::OK, Json(response)).into_response(),
|
||||
Err(error) => (StatusCode::from_u16(error.status).unwrap(), Json(error)).into_response(),
|
||||
match permissions_guard(headers, Extension(state), vec![PermissionsEnum::Administrator]).await {
|
||||
Ok((_claims, state)) => match HackathonService::delete_hackathon(id, &state).await {
|
||||
Ok(response) => (axum::http::StatusCode::OK, Json(response)).into_response(),
|
||||
Err(error) => (StatusCode::from_u16(error.status).unwrap(), Json(error)).into_response(),
|
||||
},
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user