137 lines
5.2 KiB
Rust
137 lines
5.2 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 fetches the current user's authentication and admin status from the
|
|
/// `/api/users/current` endpoint upon mounting. 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.
|
|
/// - **Not Authenticated**: Redirects to the login page (`crate::Route::Login`).
|
|
/// - **Authenticated**:
|
|
/// - 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>{ "Loading..." } </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>{ "Checking permissions..." }</div> },
|
|
}
|
|
} else {
|
|
props.children.clone().into()
|
|
}
|
|
}
|
|
}
|
|
}
|