Files
ticketsystem/frontend/src/auth.rs
schn33fuchs 3000bb0e5d Translation
Everything is now German
2026-05-26 20:20:36 +02:00

137 lines
5.3 KiB
Rust

use gloo_net::http::Request;
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew_router::prelude::*;
/// Represents the authentication state of the current user.
///
/// This struct holds information about whether a user is authenticated and if they
/// possess administrator privileges.
///
/// # Fields
/// - `is_authenticated`: An `Option<bool>` indicating if the user is logged in.
/// `None` means the status is still being checked.
/// - `is_admin`: An `Option<bool>` indicating if the authenticated user is an administrator.
/// `None` means the admin status is still being checked or is not applicable.
#[derive(Clone, Debug, PartialEq)]
pub struct AuthState {
pub is_authenticated: Option<bool>,
pub is_admin: Option<bool>,
}
/// Properties for the [`ProtectedRoute`] component.
///
/// # Fields
/// - `children`: The child components that this protected route will render if access is granted.
/// - `admin_page`: A boolean flag indicating whether this route requires administrator privileges.
/// If `true`, the user must be authenticated AND be an administrator to access the `children`.
#[derive(Properties, PartialEq)]
pub struct ProtectedRouteProps {
pub children: Children,
pub admin_page: bool,
}
/// A component that protects routes by enforcing authentication and optional administrator privileges.
///
/// This component uses the backend's validation middleware by fetching the current user's authentication
/// and admin status from the `/api/users/current` endpoint (which requires a valid JWT token).
/// Based on the [`AuthState`] and the `admin_page` property, it either renders its children or redirects the user.
///
/// # Behavior
/// - **Initial Load**: Displays "Loading..." while checking authentication status via the backend.
/// - **Not Authenticated**: Redirects to the login page (`crate::Route::Login`).
/// - **Authenticated** (valid JWT token from backend):
/// - If `admin_page` is `true`:
/// - If the user is an administrator (`is_admin: Some(true)`), it renders `children`.
/// - If the user is not an administrator (`is_admin: Some(false)`), it redirects to
/// the permission denied page (`crate::Route::PermissionDenied`).
/// - If admin status is still being checked (`is_admin: None`), it displays "Checking permissions...".
/// - If `admin_page` is `false`: It renders `children` directly, as only authentication is required.
///
/// # Example Usage
/// ```ignore
/// html! {
/// <ProtectedRoute admin_page={true}>
/// <AdminDashboard />
/// </ProtectedRoute>
/// }
/// ```
#[component(ProtectedRoute)]
pub fn protected_route(props: &ProtectedRouteProps) -> Html {
let auth_state = use_state(|| AuthState {
is_authenticated: None,
is_admin: None,
});
{
let auth_state = auth_state.clone();
use_effect_with((), move |_| {
let auth_state = auth_state.clone();
spawn_local(async move {
match Request::get("/api/users/current")
.credentials(web_sys::RequestCredentials::Include)
.send()
.await
{
Ok(resp) => {
let status = resp.status();
web_sys::console::log_1(&format!("Auth check: status {}", status).into());
if status == 200 {
let user_data: serde_json::Value =
resp.json().await.unwrap_or_default();
let is_admin = user_data["data"]["is_admin"].as_bool();
auth_state.set(AuthState {
is_authenticated: Some(true),
is_admin,
});
} else {
auth_state.set(AuthState {
is_authenticated: Some(false),
is_admin: Some(false),
});
}
}
Err(err) => {
web_sys::console::log_1(&format!("Auth check error: {:?}", err).into());
auth_state.set(AuthState {
is_authenticated: Some(false),
is_admin: Some(false),
});
}
}
});
|| ()
});
}
match *auth_state {
AuthState {
is_authenticated: None,
..
} => html! { <div>{ "Lade..." } </div> },
AuthState {
is_authenticated: Some(false),
..
} => html! {
<Redirect<crate::Route> to={crate::Route::Login}/>
},
AuthState {
is_authenticated: Some(true),
is_admin: admin_flag,
} => {
if props.admin_page {
match admin_flag {
Some(true) => props.children.clone().into(),
Some(false) => {
html! { <Redirect<crate::Route> to={crate::Route::PermissionDenied}/> }
}
None => html! { <div>{ "Berechtigungen werden geprüft..." }</div> },
}
} else {
props.children.clone().into()
}
}
}
}