Files
komp_ac/web/src/pages/admin/permissions/logic.rs
2026-08-11 08:40:49 +02:00

192 lines
6.4 KiB
Rust

use axum::{
extract::{Query, State},
http::{HeaderMap, HeaderValue, StatusCode, header},
response::{Html, IntoResponse, Redirect, Response},
};
use axum_extra::extract::Form;
use crate::{
AppState,
auth::{
AddRoleRequest, AssignUserRoleRequest, GrantPermissionRequest, RemoveRoleRequest,
ResetUserPasswordRequest, RevokePermissionRequest,
},
services::{authenticated_request, reject_cross_site},
};
use super::{
loader,
state::{
AddRoleForm, AssignRoleForm, LoadError, PermissionForm, ResetPasswordForm, RoleForm,
Selection,
},
ui,
};
pub(crate) async fn page(
State(state): State<AppState>,
headers: HeaderMap,
Query(selection): Query<Selection>,
) -> Response {
match loader::load_page(state, &headers, selection).await {
Ok(page) => Html(ui::render_page(&page)).into_response(),
Err(error) => load_error(error),
}
}
pub(crate) async fn add_role(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<AddRoleForm>,
) -> Response {
let role = form.name.trim().to_string();
let destination = format!("/admin/permissions?role={role}&updated=true");
let request_headers = headers.clone();
mutate(&headers, &destination, async move {
let mut auth = state.auth;
auth.add_role(authenticated_request(&request_headers, AddRoleRequest {
name: role,
parent: form.parent.trim().to_string(),
}).map_err(|_| "Missing session".to_string())?)
.await.map_err(|error| error.message().to_string())?;
Ok(())
}).await
}
pub(crate) async fn remove_role(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<RoleForm>,
) -> Response {
let destination = "/admin/permissions?updated=true";
let request_headers = headers.clone();
mutate(&headers, destination, async move {
let mut auth = state.auth;
auth.remove_role(authenticated_request(&request_headers, RemoveRoleRequest {
name: form.role,
}).map_err(|_| "Missing session".to_string())?)
.await.map_err(|error| error.message().to_string())?;
Ok(())
}).await
}
pub(crate) async fn grant(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<PermissionForm>,
) -> Response {
let destination = format!("/admin/permissions?role={}&updated=true", form.role);
let request_headers = headers.clone();
mutate(&headers, &destination, async move {
let mut auth = state.auth;
auth.grant_permission(authenticated_request(&request_headers, GrantPermissionRequest {
role: form.role,
object: form.object,
action: form.action,
}).map_err(|_| "Missing session".to_string())?)
.await.map_err(|error| error.message().to_string())?;
Ok(())
}).await
}
pub(crate) async fn revoke(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<PermissionForm>,
) -> Response {
let destination = format!("/admin/permissions?role={}&updated=true", form.role);
let request_headers = headers.clone();
mutate(&headers, &destination, async move {
let mut auth = state.auth;
auth.revoke_permission(authenticated_request(&request_headers, RevokePermissionRequest {
role: form.role,
object: form.object,
action: form.action,
}).map_err(|_| "Missing session".to_string())?)
.await.map_err(|error| error.message().to_string())?;
Ok(())
}).await
}
pub(crate) async fn assign_user_role(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<AssignRoleForm>,
) -> Response {
let destination = "/admin/permissions?updated=true";
let request_headers = headers.clone();
mutate(&headers, destination, async move {
let mut auth = state.auth;
auth.assign_user_role(authenticated_request(&request_headers, AssignUserRoleRequest {
username: form.username,
role: form.role,
}).map_err(|_| "Missing session".to_string())?)
.await.map_err(|error| error.message().to_string())?;
Ok(())
}).await
}
pub(crate) async fn reset_user_password(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<ResetPasswordForm>,
) -> Response {
let destination = "/admin/permissions?updated=true";
let request_headers = headers.clone();
mutate(&headers, destination, async move {
let mut auth = state.auth;
auth.reset_user_password(authenticated_request(
&request_headers,
ResetUserPasswordRequest {
username: form.username,
new_password: form.new_password,
new_password_confirmation: form.new_password_confirmation,
},
).map_err(|_| "Missing session".to_string())?)
.await
.map_err(|error| error.message().to_string())?;
Ok(())
}).await
}
async fn mutate<F>(headers: &HeaderMap, destination: &str, operation: F) -> Response
where
F: std::future::Future<Output = Result<(), String>>,
{
if let Some(rejection) = reject_cross_site(headers) {
return rejection;
}
match operation.await {
Ok(()) => success_redirect(destination),
Err(message) => (
StatusCode::UNPROCESSABLE_ENTITY,
Html(ui::render_mutation_error(&message)),
).into_response(),
}
}
fn success_redirect(destination: &str) -> Response {
let mut response = StatusCode::SEE_OTHER.into_response();
let Ok(destination) = HeaderValue::try_from(destination) else {
return (StatusCode::INTERNAL_SERVER_ERROR, "Invalid redirect").into_response();
};
response.headers_mut().insert(
header::LOCATION,
destination.clone(),
);
response.headers_mut().insert(
"hx-redirect",
destination,
);
response
}
fn load_error(error: LoadError) -> Response {
match error {
LoadError::Unauthenticated => Redirect::to("/login").into_response(),
LoadError::Forbidden => (StatusCode::FORBIDDEN, Html(ui::render_error("You do not have role or user management permission."))).into_response(),
LoadError::InvalidSelection(message) => (StatusCode::BAD_REQUEST, Html(ui::render_error(&message))).into_response(),
LoadError::Backend(message) => (StatusCode::BAD_GATEWAY, Html(ui::render_error(&message))).into_response(),
}
}