Skip to content

Commit

Permalink
LEA -> Use protected route component, add login page as fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
reimarstier committed Jan 29, 2025
1 parent d3da1e7 commit 5b5852d
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 35 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 46 additions & 1 deletion opendut-lea/src/components/auth.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use leptos::either::Either;
use leptos::prelude::*;
use leptos_oidc::{LoginLink, LogoutLink};
use opendut_auth::public::Authentication;

use crate::app::use_app_globals;

use crate::components::{AppGlobalsResource, BasePageContainer, Initialized, LoadingSpinner};
use crate::routing;

#[must_use]
#[component(transparent)]
Expand Down Expand Up @@ -39,3 +41,46 @@ pub fn LeaAuthenticated(
}
}


#[component]
pub fn LoginPage(app_globals: AppGlobalsResource) -> impl IntoView {

view! {
<BasePageContainer
title="Login page"
breadcrumbs=Vec::new()
controls=|| ()
>
<Initialized
app_globals
authentication_required=false
>
<LeaAuthenticated
unauthenticated=move || {
view! {
<p class="subtitle">"Please sign in."</p>
<LoginLink class="button">
<span class="ml-2 is-size-6">"Sign in"</span>
</LoginLink>
}
}
disabled_auth=move || {
view! {
<p class="subtitle">"Authentication disabled."</p>
<a href=routing::path::dashboard class="button">
<span class="ml-2 is-size-6">"Go to Dashboard"</span>
</a>
}
}
loading=LoadingSpinner
>
<p class="subtitle">"Authenticated"</p>
<LogoutLink class="button">
<span class="ml-1 is-size-6">"Sign out"</span>
</LogoutLink>
</LeaAuthenticated>
</Initialized>

</BasePageContainer>
}
}
2 changes: 1 addition & 1 deletion opendut-lea/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use warning_message::WarningMessage;
pub mod health;
pub mod tooltip;

mod auth;
pub mod auth;
mod authenticated;
mod buttons;
mod breadcrumbs;
Expand Down
60 changes: 46 additions & 14 deletions opendut-lea/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,55 +70,83 @@ impl WellKnownRoutes {

mod routes {
use leptos::prelude::*;
use leptos_router::components::{Route, FlatRoutes};
use leptos_router::path;
use leptos_router::components::{Route, ProtectedRoute, Routes};
use leptos_router::{path};

use crate::clusters::{ClusterConfigurator, ClustersOverview};
use crate::dashboard::Dashboard;
use crate::error::ErrorPage;
use crate::licenses::LicensesOverview;
use crate::peers::{PeerConfigurator, PeersOverview};
use crate::routing::NotFound;
use crate::user::UserOverview;
use crate::user::{UserAuthenticationSignal, UserOverview};
use crate::about::AboutOverview;
use crate::downloads::Downloads;
use crate::components::{Initialized, AppGlobalsResource};
use crate::components::{Initialized, AppGlobalsResource, LoadingSpinner};
use crate::components::auth::LoginPage;

#[component]
pub fn AppRoutes(app_globals: AppGlobalsResource) -> impl IntoView {
let user = use_context::<UserAuthenticationSignal>().expect("UserAuthenticationSignal should be provided in the context.");
let opendut_user = move || user.get().is_authenticated();

view! {
<FlatRoutes fallback=NotFound>
<Route
<Routes fallback=NotFound>
<ProtectedRoute
path=path!("/")
view=move || view! { <Initialized app_globals><Dashboard/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/clusters")
view=move || view! { <Initialized app_globals><ClustersOverview/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/clusters/:id/configure/:tab")
view=move || view! { <Initialized app_globals><ClusterConfigurator/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/peers")
view=move || view! { <Initialized app_globals><PeersOverview/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/peers/:id/configure/:tab")
view=move || view! { <Initialized app_globals><PeerConfigurator/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/downloads")
view=move || view! { <Initialized app_globals><Downloads/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/user")
view=move || view! { <Initialized app_globals><UserOverview/></Initialized> }
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
<ProtectedRoute
path=path!("/about")
view=move || view! { <Initialized app_globals><AboutOverview/></Initialized> } //require Initialized to protect with authentication
condition=opendut_user
fallback=LoadingSpinner
redirect_path=|| "/login"
/>
<Route
path=path!("/licenses")
Expand All @@ -128,11 +156,15 @@ mod routes {
path=path!("/error")
view=ErrorPage
/>
<Route
path=path!("/login")
view=move || view!{ <LoginPage app_globals/> }
/>
<Route
path=path!("/*any")
view=NotFound
/>
</FlatRoutes>
</Routes>
}
}
}
Expand Down
54 changes: 43 additions & 11 deletions opendut-lea/src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use leptos_router::hooks::use_navigate;
use opendut_auth::types::Claims;
pub use overview::UserOverview;
use crate::routing::{navigate_to, WellKnownRoutes};

use leptos_oidc::AuthenticatedData as LeptosOidcAuthenticatedData;
/// In case authentication is disabled the user identity is not known
pub const UNAUTHENTICATED_USER: &str = "unknown-user";
mod overview;
Expand All @@ -27,26 +27,59 @@ pub enum UserAuthentication {
Loading,
Disabled,
Unauthenticated,
Authenticated(Option<Box<TokenData<Claims>>>),
Authenticated(Box<AuthenticatedData>),
}

#[derive(Debug, Clone)]
pub struct AuthenticatedData {
auth: LeptosOidcAuthenticatedData,
token: Option<TokenData<Claims>>,
}

impl UserAuthentication {
#[allow(unused)] // TODO: use this shorthand for authorization
pub fn user(&self) -> Option<Box<TokenData<Claims>>> {
pub fn _user(&self) -> Option<TokenData<Claims>> {
match self {
UserAuthentication::Authenticated(token) => {
token.clone()
UserAuthentication::Authenticated(data) => {
data.token.clone()
}
_ => { None }
}
}
pub fn _has_group(&self, group: &str) -> Option<bool> {

match self {
UserAuthentication::Loading => { None }
UserAuthentication::Disabled=> { Some(true) }
UserAuthentication::Unauthenticated=> { Some(false) }
UserAuthentication::Authenticated(data) => {
match data.token.as_ref() {
None => { Some(false) }
Some(user) => {
Some(user.claims.additional_claims.has_group(group))
}
}
}
}
}

pub fn is_authenticated(&self) -> Option<bool> {
match self {
UserAuthentication::Loading => { None }
UserAuthentication::Disabled=> { Some(true) }
UserAuthentication::Unauthenticated=> { Some(false) }
UserAuthentication::Authenticated(data) => {
Some(data.auth.is_authenticated())
}
}
}

pub fn username(&self) -> String {
let name = match self {
UserAuthentication::Loading => { "loading" }
UserAuthentication::Disabled=> { "disabled" }
UserAuthentication::Unauthenticated=> { "unauthenticated" }
UserAuthentication::Authenticated(user) => {
match user {
UserAuthentication::Authenticated(data) => {
match data.token.as_ref() {
None => { UNAUTHENTICATED_USER }
Some(user) => { &user.claims.preferred_username }
}
Expand Down Expand Up @@ -90,9 +123,8 @@ pub(crate) fn provide_authentication_signals_in_context() -> AuthSignal {
}
Auth::Authenticated(auth) => {
tracing::debug!("user authenticated");
let token = auth.decoded_access_token::<Claims>(Algorithm::RS256, &[DEFAULT_TOKEN_AUDIENCE])
.map(Box::new);
user_auth.set(UserAuthentication::Authenticated(token));
let token = auth.decoded_access_token::<Claims>(Algorithm::RS256, &[DEFAULT_TOKEN_AUDIENCE]);
user_auth.set(UserAuthentication::Authenticated(Box::new(AuthenticatedData { auth, token })));
}
Auth::Error(error) => {
user_auth.set(UserAuthentication::Unauthenticated);
Expand Down
4 changes: 2 additions & 2 deletions opendut-lea/src/user/overview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub fn UserOverview() -> impl IntoView {
UserAuthentication::Loading | UserAuthentication::Disabled | UserAuthentication::Unauthenticated => {
EitherOf3::A(view!{ <AbsentUserTableView/> })
}
UserAuthentication::Authenticated(token) => {
match token {
UserAuthentication::Authenticated(data) => {
match data.token {
None => {
EitherOf3::B(view! { <AbsentUserTableView/> })
}
Expand Down
6 changes: 6 additions & 0 deletions opendut-util/opendut-auth/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ pub struct MyAdditionalClaims {

impl MyAdditionalClaims {
fn empty_vector() -> Vec<String> { Vec::new() }
pub fn has_group(&self, group: &str) -> bool {
self.groups.contains(&format!("/{group}"))
}
pub fn has_role(&self, role: &str) -> bool {
self.roles.contains(&role.to_string())
}
}

cfg_if! {
Expand Down

0 comments on commit 5b5852d

Please sign in to comment.