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, headers: HeaderMap, Query(selection): Query, ) -> 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, headers: HeaderMap, Form(form): Form, ) -> 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, headers: HeaderMap, Form(form): Form, ) -> 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, headers: HeaderMap, Form(form): Form, ) -> 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, headers: HeaderMap, Form(form): Form, ) -> 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, headers: HeaderMap, Form(form): Form, ) -> 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, headers: HeaderMap, Form(form): Form, ) -> 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(headers: &HeaderMap, destination: &str, operation: F) -> Response where F: std::future::Future>, { 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(), } }