192 lines
6.4 KiB
Rust
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(),
|
|
}
|
|
}
|