diff --git a/frontend/src/app/app.tsx b/frontend/src/app/app.tsx index 9dc3ccb..b5f7055 100644 --- a/frontend/src/app/app.tsx +++ b/frontend/src/app/app.tsx @@ -3,7 +3,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { ThemeProvider } from '@mui/material'; import { Root } from '../pages/root'; import { Search } from '../pages/search'; -import { Header } from '../features/auth'; +import { Header } from '../features/auth/header'; import theme from '../features/theme'; import './normalize.css'; diff --git a/frontend/src/features/auth/header.scss b/frontend/src/features/auth/header/header.scss similarity index 100% rename from frontend/src/features/auth/header.scss rename to frontend/src/features/auth/header/header.scss diff --git a/frontend/src/features/auth/header.tsx b/frontend/src/features/auth/header/header.tsx similarity index 91% rename from frontend/src/features/auth/header.tsx rename to frontend/src/features/auth/header/header.tsx index fa4f750..62ffb3d 100644 --- a/frontend/src/features/auth/header.tsx +++ b/frontend/src/features/auth/header/header.tsx @@ -4,14 +4,14 @@ import { Button, IconButton } from '@mui/material'; import SettingsIcon from '@mui/icons-material/Settings'; import './header.scss'; import { useNavigate } from 'react-router-dom'; -import useKeycloakAuth from './keycloak'; +import { useKeycloakAuth } from '../keycloak'; const name = cn('Header'); export const Header: React.FC = () => { const navigate = useNavigate(); - const {authenticated, keycloak} = useKeycloakAuth(); + const { authenticated, keycloak } = useKeycloakAuth(); return (
@@ -36,7 +36,7 @@ export const Header: React.FC = () => { Logout document.location = `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/account/`} > @@ -56,7 +56,7 @@ export const Header: React.FC = () => { > Registration - } + }
); } \ No newline at end of file diff --git a/frontend/src/features/auth/header/index.ts b/frontend/src/features/auth/header/index.ts new file mode 100644 index 0000000..677ca79 --- /dev/null +++ b/frontend/src/features/auth/header/index.ts @@ -0,0 +1 @@ +export * from './header'; diff --git a/frontend/src/features/auth/keycloak.ts b/frontend/src/features/auth/keycloak.ts index af53ec1..bb4bf84 100644 --- a/frontend/src/features/auth/keycloak.ts +++ b/frontend/src/features/auth/keycloak.ts @@ -1,39 +1,44 @@ import Keycloak from 'keycloak-js'; import { useEffect, useState } from 'react'; -const kc = new Keycloak({ +const keycloakConfig = { url: process.env.KEYCLOAK_URL as string, realm: process.env.KEYCLOAK_REALM as string, clientId: process.env.KEYCLOAK_CLIENT as string, -}); - -const initKeycloak = () => { - return kc.init({ - onLoad: 'check-sso', - silentCheckSsoRedirectUri: window.location.origin + "/silent-check-sso.html", - checkLoginIframe: false, - pkceMethod: 'S256', - }); }; -export default function useKeycloakAuth() { +const kc = new Keycloak(keycloakConfig); + +let keycloakInitialized = false; // Prevent multiple initializations + +const initKeycloak = async () => { + if (!keycloakInitialized) { + keycloakInitialized = true; + return kc.init({ + onLoad: 'check-sso', + silentCheckSsoRedirectUri: window.location.origin + "/silent-check-sso.html", + checkLoginIframe: false, + pkceMethod: 'S256', + }); + } + return kc.authenticated ?? false; +}; + +export function useKeycloakAuth() { const [authenticated, setAuthenticated] = useState(null); useEffect(() => { - initKeycloak().then(auth => { - setAuthenticated(auth); - }); + initKeycloak().then(auth => setAuthenticated(auth)); - // Handle token expiration - if (kc.onTokenExpired) { + // Ensure event listeners are only set once + if (!kc.onTokenExpired) { kc.onTokenExpired = () => kc.updateToken().then(() => setAuthenticated(true)); } - // Listen for authentication events kc.onAuthSuccess = () => setAuthenticated(true); kc.onAuthError = () => setAuthenticated(false); kc.onAuthLogout = () => setAuthenticated(false); }, []); return { authenticated, keycloak: kc }; -} \ No newline at end of file +} diff --git a/frontend/src/features/auth/withAuth/index.ts b/frontend/src/features/auth/withAuth/index.ts new file mode 100644 index 0000000..6594e38 --- /dev/null +++ b/frontend/src/features/auth/withAuth/index.ts @@ -0,0 +1 @@ +export * from './withAuth'; diff --git a/frontend/src/features/auth/withAuth/withAuth.scss b/frontend/src/features/auth/withAuth/withAuth.scss new file mode 100644 index 0000000..4a40b57 --- /dev/null +++ b/frontend/src/features/auth/withAuth/withAuth.scss @@ -0,0 +1,10 @@ +@use "../../../shared/styles/colors.scss" as *; + +.NotAuthenticated { + font-family: Roboto, Helvetica, Arial, sans-serif; + font-weight: 400; + font-size: 2rem; + text-align: center; + margin-top: 5rem; + color: $text-secondary; +} \ No newline at end of file diff --git a/frontend/src/features/auth/withAuth/withAuth.tsx b/frontend/src/features/auth/withAuth/withAuth.tsx new file mode 100644 index 0000000..e150ef2 --- /dev/null +++ b/frontend/src/features/auth/withAuth/withAuth.tsx @@ -0,0 +1,17 @@ +import { cn } from '@bem-react/classname'; +import { useKeycloakAuth } from '../keycloak'; +import React from 'react'; + +import './withAuth.scss'; + +const name = cn('NotAuthenticated'); + +export function withAuth(Cmp: React.FC) { + return () => { + const { authenticated } = useKeycloakAuth(); + + return authenticated ? :
+ Not authenticated +
+ } +} diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index 2022ac5..2021ec1 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { CreateRecord } from '../features/createRecord'; import { Page } from '../shared/components/Page'; +import { withAuth } from '../features/auth/withAuth'; -export const Root: React.FC = () => { +export const Root: React.FC = withAuth(() => { return (
@@ -10,4 +11,4 @@ export const Root: React.FC = () => {
) -} \ No newline at end of file +}); diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx index 42a4333..70cd467 100644 --- a/frontend/src/pages/search.tsx +++ b/frontend/src/pages/search.tsx @@ -1,9 +1,10 @@ import React from 'react'; +import { withAuth } from '../features/auth/withAuth'; -export const Search: React.FC = () => { +export const Search: React.FC = withAuth(() => { return (
Search
) -} \ No newline at end of file +});