use actix_web::{
body::BoxBody,
dev::{ServiceRequest, ServiceResponse},
middleware::Next,
};
use actix_web::{Error, HttpMessage};
use crate::{pool::AppState, security::AuthenticatedUser};
#[derive(Clone)]
pub struct AccessCheckConfig {
pub resource_name: &'static str,
pub action: &'static str,
pub id_param_pattern: Option<&'static str>,
}
pub async fn access_guard_middleware(
req: ServiceRequest,
next: Next<BoxBody>,
) -> Result<ServiceResponse<BoxBody>, Error> {
let config = req.app_data::<AccessCheckConfig>().ok_or_else(|| {
actix_web::error::ErrorInternalServerError("AccessConfig missing on route")
})?;
let user = req
.extensions()
.get::<AuthenticatedUser>()
.copied()
.ok_or_else(|| actix_web::error::ErrorUnauthorized("User not authenticated"))?;
let mut instance_id: Option<i32> = None;
if let Some(param_name) = config.id_param_pattern {
if let Some(val) = req.match_info().get(param_name) {
instance_id = val.parse::<i32>().ok();
if instance_id.is_none() {
return Err(actix_web::error::ErrorBadRequest(
"Invalid ID format in URL",
));
}
}
}
let app_state = req
.app_data::<actix_web::web::Data<AppState>>()
.ok_or_else(|| actix_web::error::ErrorInternalServerError("AppState missing"))?;
let db_pool = match app_state.db_pool.clone() {
Some(pool) => pool,
None => {
return Err(actix_web::error::ErrorInternalServerError(
"Database pool missing",
))
}
};
let is_allowed = sqlx::query_scalar::<_, bool>("SELECT check_access($1, $2, $3, $4)")
.bind(user.id as i32)
.bind(config.resource_name)
.bind(config.action)
.bind(instance_id)
.fetch_one(&db_pool)
.await
.map_err(|e| {
eprintln!("{:?}", e);
actix_web::error::ErrorInternalServerError("Database error during access check")
})?;
if is_allowed {
next.call(req).await
} else {
Err(actix_web::error::ErrorForbidden("Insufficient permissions"))
}
}