From 5de4c0f9a3626ee6626bf1e758ae31dc4d15f24c Mon Sep 17 00:00:00 2001 From: Jesus Balderrama Date: Mon, 9 Mar 2026 08:50:27 -0600 Subject: [PATCH 1/4] feat: validation in routes to redirect to login if user is not auth --- shell/router/AuthGuard.tsx | 54 ++++++++++++++++++++++++++++++++++++ shell/router/getAppRoutes.ts | 38 ++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 shell/router/AuthGuard.tsx diff --git a/shell/router/AuthGuard.tsx b/shell/router/AuthGuard.tsx new file mode 100644 index 00000000..0a6e8e9e --- /dev/null +++ b/shell/router/AuthGuard.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState } from 'react'; + +import { ensureAuthenticatedUser, getAuthenticatedUser } from '../../runtime/auth'; + +interface AuthGuardProps { + children: React.ReactNode, + requireAuthenticatedUser?: boolean, +} + +/** + * AuthGuard component ensures authentication before rendering children + */ +const AuthGuard: React.FC = ({ + children, + requireAuthenticatedUser = false, +}) => { + const [isChecking, setIsChecking] = useState(true); + + useEffect(() => { + const checkAuth = async () => { + // no auth required + if (!requireAuthenticatedUser) { + setIsChecking(false); + return; + } + + // user already authenticated + const user = getAuthenticatedUser(); + if (user) { + setIsChecking(false); + return; + } + + // User not authenticated but route requires auth + const redirectUrl = `${window.location.origin}${window.location.pathname}${window.location.search}`; + try { + await ensureAuthenticatedUser(redirectUrl); + } catch (error) { + if ((error as Error & { isRedirecting?: boolean })?.isRedirecting) return; + setIsChecking(false); + } + }; + + checkAuth(); + }, [requireAuthenticatedUser]); + + if (isChecking && requireAuthenticatedUser) { + return null; + } + + return <>{children}; +}; + +export default AuthGuard; diff --git a/shell/router/getAppRoutes.ts b/shell/router/getAppRoutes.ts index 519d3058..7d54d46a 100644 --- a/shell/router/getAppRoutes.ts +++ b/shell/router/getAppRoutes.ts @@ -1,18 +1,48 @@ -import { RouteObject } from 'react-router'; +import React from 'react'; import { getSiteConfig } from '../../runtime'; -import { App } from '../../types'; +import { App, RoleRouteObject } from '../../types'; +import AuthGuard from './AuthGuard'; + +function createAuthenticatedComponent(OriginalComponent: React.ComponentType) { + const AuthenticatedComponent: React.FC = (props) => { + return React.createElement( + AuthGuard, + { + requireAuthenticatedUser: true, + } as React.ComponentProps, + React.createElement(OriginalComponent, props) + ); + }; + + return AuthenticatedComponent; +} + +function wrapRouteWithAuth(route: RoleRouteObject): RoleRouteObject { + // Check if route has requireAuthenticatedUser property using type assertion + const routeWithAuth = route as RoleRouteObject & { requireAuthenticatedUser?: boolean }; + if (routeWithAuth.requireAuthenticatedUser && route.Component) { + return { + ...route, + Component: createAuthenticatedComponent(route.Component as React.ComponentType), + }; + } + return route; +} export default function getAppRoutes() { const { apps } = getSiteConfig(); - let routes: RouteObject[] = []; + let routes: RoleRouteObject[] = []; if (apps) { apps.forEach( (app: App) => { if (Array.isArray(app.routes)) { - routes = routes.concat(app.routes); + const wrappedRoutes = app.routes.map((route: RoleRouteObject) => + wrapRouteWithAuth(route) + ); + routes = routes.concat(wrappedRoutes); } } ); From a82e35bb176cb9bdc11f9d0a7b765340f3213522 Mon Sep 17 00:00:00 2001 From: Jesus Balderrama Date: Mon, 9 Mar 2026 11:20:05 -0600 Subject: [PATCH 2/4] chore: updated comment --- shell/router/AuthGuard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/router/AuthGuard.tsx b/shell/router/AuthGuard.tsx index 0a6e8e9e..8478590d 100644 --- a/shell/router/AuthGuard.tsx +++ b/shell/router/AuthGuard.tsx @@ -24,7 +24,7 @@ const AuthGuard: React.FC = ({ return; } - // user already authenticated + //check to validate if the user is already authenticated const user = getAuthenticatedUser(); if (user) { setIsChecking(false); From 0804c8d4398aa65c228656649906c19d1e573d75 Mon Sep 17 00:00:00 2001 From: Jesus Balderrama Date: Mon, 9 Mar 2026 13:39:24 -0600 Subject: [PATCH 3/4] chore: missing space --- shell/router/AuthGuard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/router/AuthGuard.tsx b/shell/router/AuthGuard.tsx index 8478590d..76ee6e32 100644 --- a/shell/router/AuthGuard.tsx +++ b/shell/router/AuthGuard.tsx @@ -24,7 +24,7 @@ const AuthGuard: React.FC = ({ return; } - //check to validate if the user is already authenticated + // check to validate if the user is already authenticated const user = getAuthenticatedUser(); if (user) { setIsChecking(false); From 237d40a63a16aa23db4433b5422d8b2003d48366 Mon Sep 17 00:00:00 2001 From: Jesus Balderrama Date: Tue, 10 Mar 2026 11:50:35 -0600 Subject: [PATCH 4/4] fix: refactor to protect the path via param using existing component --- shell/router/AuthGuard.tsx | 54 ------------------------------------ shell/router/getAppRoutes.ts | 36 ++++++++++++------------ 2 files changed, 19 insertions(+), 71 deletions(-) delete mode 100644 shell/router/AuthGuard.tsx diff --git a/shell/router/AuthGuard.tsx b/shell/router/AuthGuard.tsx deleted file mode 100644 index 76ee6e32..00000000 --- a/shell/router/AuthGuard.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import { ensureAuthenticatedUser, getAuthenticatedUser } from '../../runtime/auth'; - -interface AuthGuardProps { - children: React.ReactNode, - requireAuthenticatedUser?: boolean, -} - -/** - * AuthGuard component ensures authentication before rendering children - */ -const AuthGuard: React.FC = ({ - children, - requireAuthenticatedUser = false, -}) => { - const [isChecking, setIsChecking] = useState(true); - - useEffect(() => { - const checkAuth = async () => { - // no auth required - if (!requireAuthenticatedUser) { - setIsChecking(false); - return; - } - - // check to validate if the user is already authenticated - const user = getAuthenticatedUser(); - if (user) { - setIsChecking(false); - return; - } - - // User not authenticated but route requires auth - const redirectUrl = `${window.location.origin}${window.location.pathname}${window.location.search}`; - try { - await ensureAuthenticatedUser(redirectUrl); - } catch (error) { - if ((error as Error & { isRedirecting?: boolean })?.isRedirecting) return; - setIsChecking(false); - } - }; - - checkAuth(); - }, [requireAuthenticatedUser]); - - if (isChecking && requireAuthenticatedUser) { - return null; - } - - return <>{children}; -}; - -export default AuthGuard; diff --git a/shell/router/getAppRoutes.ts b/shell/router/getAppRoutes.ts index 7d54d46a..d2e107ff 100644 --- a/shell/router/getAppRoutes.ts +++ b/shell/router/getAppRoutes.ts @@ -1,32 +1,34 @@ import React from 'react'; -import { getSiteConfig } from '../../runtime'; +import { getSiteConfig, AuthenticatedPageRoute } from '../../runtime'; import { App, RoleRouteObject } from '../../types'; -import AuthGuard from './AuthGuard'; -function createAuthenticatedComponent(OriginalComponent: React.ComponentType) { - const AuthenticatedComponent: React.FC = (props) => { - return React.createElement( - AuthGuard, - { - requireAuthenticatedUser: true, - } as React.ComponentProps, - React.createElement(OriginalComponent, props) - ); - }; - - return AuthenticatedComponent; +interface AuthComponentProps { + redirectUrl?: string | null, + requireAuthenticatedUser?: boolean, + children?: React.ReactNode, } function wrapRouteWithAuth(route: RoleRouteObject): RoleRouteObject { - // Check if route has requireAuthenticatedUser property using type assertion - const routeWithAuth = route as RoleRouteObject & { requireAuthenticatedUser?: boolean }; + const routeWithAuth = route as RoleRouteObject & { + requireAuthenticatedUser?: boolean, + redirectUrl?: string, + }; if (routeWithAuth.requireAuthenticatedUser && route.Component) { + const OriginalComponent = route.Component; return { ...route, - Component: createAuthenticatedComponent(route.Component as React.ComponentType), + Component: (props) => + React.createElement( + AuthenticatedPageRoute as React.ComponentType, + { + redirectUrl: routeWithAuth.redirectUrl ?? null, + }, + React.createElement(OriginalComponent, props) + ), }; } + return route; }