From 5df3686216dd8a06c4dcdd6475d101675545f651 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Tue, 17 Oct 2023 21:09:25 +0200 Subject: [PATCH 01/12] Handle failed login when waiting for a keycloack --- src/services/AuthService.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index 7051a6819..f7128c3d7 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -86,6 +86,12 @@ const Auth = ({ children }: GenericContextProviderProps) => { const [isServerReachable, setIsServerReachable] = useState(null); const { enqueueSnackbar } = useSnackbar(); + const isAuthorized = useMemo(() => user !== null || demoMode, [demoMode, user]); + + const [isWaitingForVerification, setIsWaitingForVerification] = useState( + Boolean(isServerReachable && keycloak.authenticated && !isAuthorized) + ); + useEffect(() => { setReachInterval(isServerReachable ? 180000 : undefined); }, [isServerReachable]); @@ -195,6 +201,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { }) .catch((_: HTTPError) => { setUser(null); + setIsWaitingForVerification(false); setRefreshInterval(undefined); }); }, [kyRef]); @@ -266,8 +273,6 @@ const Auth = ({ children }: GenericContextProviderProps) => { useIntervalAsync(tokenRefresh, keycloak.authenticated ? keyCloakInterval : undefined); - const isAuthorized = useMemo(() => user !== null || demoMode, [demoMode, user]); - const refresh = useCallback(async () => { if (user?.source === 'keycloak' && isAuthorized) return tokenVerification(); @@ -308,7 +313,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { refresh }}> {children} - + Waiting for verification... From fc35a379bb7b2d235aa7b4a9a2505a744fcee288 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Wed, 18 Oct 2023 17:15:05 +0200 Subject: [PATCH 02/12] use keycloak-react-web --- package-lock.json | 37 ++++++++ package.json | 1 + src/App.tsx | 4 +- .../components/Dialog/CustomDialog.tsx | 5 +- .../Dialog/RejectKeycloakUserDialog.tsx | 32 +++++++ .../BooleanAlgebra/OperationToggle.tsx | 11 ++- .../ZoneManagerPanel/ZoneManagerPanel.tsx | 7 +- .../components/Panels/LoginPanel.tsx | 7 +- .../components/Results/ResultsPanel.tsx | 6 +- src/services/AuthService.tsx | 89 +++++++++++-------- src/services/DialogService.tsx | 7 +- src/services/KeycloakAuthService.tsx | 14 +++ src/services/keycloakConfig.ts | 6 -- 13 files changed, 163 insertions(+), 63 deletions(-) create mode 100644 src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx create mode 100644 src/services/KeycloakAuthService.tsx delete mode 100644 src/services/keycloakConfig.ts diff --git a/package-lock.json b/package-lock.json index 18edee5e1..5650e4875 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "date-fns": "^2.30.0", "glob": "^10.3.10", "keycloak-js": "^22.0.4", + "keycloak-react-web": "^0.1.19", "ky": "^0.33.2", "material-ui-popup-state": "^5.0.9", "notistack": "^3.0.1", @@ -14247,6 +14248,23 @@ "js-sha256": "^0.9.0" } }, + "node_modules/keycloak-react-web": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/keycloak-react-web/-/keycloak-react-web-0.1.19.tgz", + "integrity": "sha512-ECpJ75rajWPB77zjUcigzpCofi6ynqACKNDyhzx7fUI17JlsCsoRGGGDrDGPlVh+sMvurQNcmZHGhSNWnzCZAw==", + "dependencies": { + "keycloak-js": "^19.0.2" + } + }, + "node_modules/keycloak-react-web/node_modules/keycloak-js": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-19.0.3.tgz", + "integrity": "sha512-mzCBxrzfl+vB551Q7MB+T9+40IHU4i0a6g1eTatzeEGrQMis5m/BqvPC3kxTsI+/LxHbB9XYQE3u9SlWKDHQCw==", + "dependencies": { + "base64-js": "^1.5.1", + "js-sha256": "^0.9.0" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -32589,6 +32607,25 @@ "js-sha256": "^0.9.0" } }, + "keycloak-react-web": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/keycloak-react-web/-/keycloak-react-web-0.1.19.tgz", + "integrity": "sha512-ECpJ75rajWPB77zjUcigzpCofi6ynqACKNDyhzx7fUI17JlsCsoRGGGDrDGPlVh+sMvurQNcmZHGhSNWnzCZAw==", + "requires": { + "keycloak-js": "^19.0.2" + }, + "dependencies": { + "keycloak-js": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-19.0.3.tgz", + "integrity": "sha512-mzCBxrzfl+vB551Q7MB+T9+40IHU4i0a6g1eTatzeEGrQMis5m/BqvPC3kxTsI+/LxHbB9XYQE3u9SlWKDHQCw==", + "requires": { + "base64-js": "^1.5.1", + "js-sha256": "^0.9.0" + } + } + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", diff --git a/package.json b/package.json index 29851cf17..bd5ec1813 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "date-fns": "^2.30.0", "glob": "^10.3.10", "keycloak-js": "^22.0.4", + "keycloak-react-web": "^0.1.19", "ky": "^0.33.2", "material-ui-popup-state": "^5.0.9", "notistack": "^3.0.1", diff --git a/src/App.tsx b/src/App.tsx index cd85d7948..ea7ff7970 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import { ConfigProvider } from './config/ConfigService'; import { PythonConverterService } from './PythonConverter/PythonConverterService'; import { Auth } from './services/AuthService'; import { DialogProvider } from './services/DialogService'; +import { KeycloakAuth } from './services/KeycloakAuthService'; import { Loader } from './services/LoaderService'; import { ShSimulation } from './services/ShSimulatorService'; import { Store } from './services/StoreService'; @@ -83,12 +84,13 @@ function App() { , , , + , , + , , , , , - , ]}> diff --git a/src/ThreeEditor/components/Dialog/CustomDialog.tsx b/src/ThreeEditor/components/Dialog/CustomDialog.tsx index e3598c4d3..2d4e2e96f 100644 --- a/src/ThreeEditor/components/Dialog/CustomDialog.tsx +++ b/src/ThreeEditor/components/Dialog/CustomDialog.tsx @@ -7,7 +7,8 @@ import { DialogProps, DialogTitle, DialogTitleProps, - IconButton + IconButton, + Theme } from '@mui/material'; import { ReactNode } from 'react'; @@ -87,7 +88,7 @@ export function CustomDialogTitle({ children, onClose, ...other }: CustomTitlePr position: 'absolute', right: 8, top: 8, - color: theme => theme.palette.grey[500] + color: ({ palette }: Theme) => palette.grey[500] }}> diff --git a/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx b/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx new file mode 100644 index 000000000..a64e747b9 --- /dev/null +++ b/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx @@ -0,0 +1,32 @@ +import { Button } from '@mui/material'; +import { useKeycloak } from 'keycloak-react-web'; + +import { ConcreteDialogProps, CustomDialog } from './CustomDialog'; + +export function RejectKeycloakUserDialog({ + onClose, + reason +}: ConcreteDialogProps & { reason: string }) { + const { keycloak, initialized } = useKeycloak(); + + return ( + + + + ); +} diff --git a/src/ThreeEditor/components/ZoneManagerPanel/BooleanAlgebra/OperationToggle.tsx b/src/ThreeEditor/components/ZoneManagerPanel/BooleanAlgebra/OperationToggle.tsx index 05244c9b5..0adaa6ca5 100644 --- a/src/ThreeEditor/components/ZoneManagerPanel/BooleanAlgebra/OperationToggle.tsx +++ b/src/ThreeEditor/components/ZoneManagerPanel/BooleanAlgebra/OperationToggle.tsx @@ -6,6 +6,7 @@ import { Chip, IconButton, SvgIconProps, + Theme, ToggleButton, ToggleButtonGroup, ToggleButtonGroupProps, @@ -169,10 +170,8 @@ function OperationInput({ 'height': 55, 'width': 55, 'margin': '0 auto', - 'color': theme => - theme.palette.mode === 'dark' - ? 'text.primary' - : 'secondary.main', + 'color': ({ palette }: Theme) => + palette.mode === 'dark' ? 'text.primary' : 'secondary.main', '&:hover': { color: 'error.main' } @@ -217,8 +216,8 @@ function OperationInput({ 'padding': '4px 0', 'minWidth': '56px', 'flexDirection': 'column', - '&.Mui-selected': theme => - theme.palette.mode === 'dark' + '&.Mui-selected': ({ palette }: Theme) => + palette.mode === 'dark' ? {} : { backgroundColor: 'primary.main', diff --git a/src/ThreeEditor/components/ZoneManagerPanel/ZoneManagerPanel.tsx b/src/ThreeEditor/components/ZoneManagerPanel/ZoneManagerPanel.tsx index dbc91717b..1322e9a8c 100644 --- a/src/ThreeEditor/components/ZoneManagerPanel/ZoneManagerPanel.tsx +++ b/src/ThreeEditor/components/ZoneManagerPanel/ZoneManagerPanel.tsx @@ -8,6 +8,7 @@ import { SelectChangeEvent, Tab, Tabs, + Theme, Tooltip } from '@mui/material'; import { @@ -379,8 +380,8 @@ function ZoneManagerPanel(props: BooleanZoneManagerPanelProps) { 'width': '24px', 'height': '24px', 'borderRadius': 0, - 'color': theme => - theme.palette.mode === 'dark' + 'color': ({ palette }: Theme) => + palette.mode === 'dark' ? 'text.primary' : 'secondary.main', '&:hover': { @@ -398,7 +399,7 @@ function ZoneManagerPanel(props: BooleanZoneManagerPanelProps) { ref={backdropRef} sx={{ color: '#fff', - zIndex: theme => theme.zIndex.drawer + 1, + zIndex: ({ zIndex }: Theme) => zIndex.drawer + 1, position: 'absolute', left: 0, bottom: 0, diff --git a/src/WrapperApp/components/Panels/LoginPanel.tsx b/src/WrapperApp/components/Panels/LoginPanel.tsx index 4451304cf..19ecc223a 100644 --- a/src/WrapperApp/components/Panels/LoginPanel.tsx +++ b/src/WrapperApp/components/Panels/LoginPanel.tsx @@ -1,4 +1,5 @@ import { Box, Button, Card, CardContent, TextField, Typography, useTheme } from '@mui/material'; +import { useKeycloak } from 'keycloak-react-web'; import { ChangeEvent, useCallback, useEffect, useState } from 'react'; import { useConfig } from '../../../config/ConfigService'; @@ -6,7 +7,8 @@ import { useAuth } from '../../../services/AuthService'; export default function LoginPanel() { const { altAuth } = useConfig(); - const { login, tokenLogin } = useAuth(); + const { login } = useAuth(); + const { keycloak, initialized } = useKeycloak(); const theme = useTheme(); const [username, setUsername] = useState(''); @@ -94,8 +96,9 @@ export default function LoginPanel() { diff --git a/src/WrapperApp/components/Results/ResultsPanel.tsx b/src/WrapperApp/components/Results/ResultsPanel.tsx index aee50a45a..55bee595c 100644 --- a/src/WrapperApp/components/Results/ResultsPanel.tsx +++ b/src/WrapperApp/components/Results/ResultsPanel.tsx @@ -9,7 +9,7 @@ import { Tabs, Typography } from '@mui/material'; -import { SyntheticEvent, useEffect, useState } from 'react'; +import { ChangeEvent, SyntheticEvent, useEffect, useState } from 'react'; import { Estimator, generateGraphs, isPage0d, Page, Page0D } from '../../../JsRoot/GraphData'; import { useDialog } from '../../../services/DialogService'; @@ -97,7 +97,9 @@ function ResultsPanel() { control={ setGroupQuantities(e.target.checked)} + onChange={(e: ChangeEvent) => + setGroupQuantities(e.target.checked) + } /> } label='Group Quantities' diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index f7128c3d7..c50f5f7b6 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -1,5 +1,5 @@ -import { Backdrop, CircularProgress, Typography } from '@mui/material'; -import Keycloak from 'keycloak-js'; +import { Backdrop, CircularProgress, Theme, Typography } from '@mui/material'; +import { useKeycloak } from 'keycloak-react-web'; import ky, { HTTPError } from 'ky'; import { KyInstance } from 'ky/distribution/types/ky'; import { useSnackbar } from 'notistack'; @@ -16,8 +16,8 @@ import { import { hasFields } from '../util/customGuards'; import useIntervalAsync from '../util/hooks/useIntervalAsync'; import { snakeToCamelCase } from '../util/Notation/Notation'; +import { useDialog } from './DialogService'; import { createGenericContext, GenericContextProviderProps } from './GenericContext'; -import { keycloakConfig } from './keycloakConfig'; type AuthUser = Pick; @@ -67,14 +67,12 @@ export interface AuthContext { isAuthorized: boolean; isServerReachable: boolean; login: (...args: RequestAuthLogin) => void; - tokenLogin: () => void; logout: (...args: RequestAuthLogout) => void; refresh: (...args: RequestAuthRefresh) => void; authKy: KyInstance; } const [useAuth, AuthContextProvider] = createGenericContext(); -const keycloak = new Keycloak(keycloakConfig); const ignored_messages = ['No token provided']; const Auth = ({ children }: GenericContextProviderProps) => { @@ -82,16 +80,14 @@ const Auth = ({ children }: GenericContextProviderProps) => { const [user, setUser] = useState(load(StorageKey.USER, isAuthUser)); const [reachInterval, setReachInterval] = useState(); const [refreshInterval, setRefreshInterval] = useState(180000); // 3 minutes in ms default interval for refresh token + const { keycloak, initialized } = useKeycloak(); const [keyCloakInterval, setKeyCloakInterval] = useState(); const [isServerReachable, setIsServerReachable] = useState(null); const { enqueueSnackbar } = useSnackbar(); + const [open] = useDialog('rejectKeycloak'); const isAuthorized = useMemo(() => user !== null || demoMode, [demoMode, user]); - const [isWaitingForVerification, setIsWaitingForVerification] = useState( - Boolean(isServerReachable && keycloak.authenticated && !isAuthorized) - ); - useEffect(() => { setReachInterval(isServerReachable ? 180000 : undefined); }, [isServerReachable]); @@ -174,16 +170,21 @@ const Auth = ({ children }: GenericContextProviderProps) => { }, [reachServer]); useEffect(() => { - if (user !== null && user.source !== 'keycloak' && isServerReachable) + if (!demoMode && user?.source !== 'keycloak' && isServerReachable) setRefreshInterval(prev => (prev === undefined ? 3000 : prev)); // 3 seconds in ms default interval for refresh when logged in with username and password else if (!isServerReachable || !user?.source) setRefreshInterval(undefined); - }, [isServerReachable, user]); + }, [demoMode, isServerReachable, user]); const tokenVerification = useCallback(() => { - if (!keycloak.authenticated) return Promise.reject(); + if (!initialized || !keycloak.authenticated) return Promise.reject(); const username = keycloak.tokenParsed?.preferred_username; + if (true) + open({ + reason: 'You are not authorized to use this application.' + }); + return kyRef .post(`auth/keycloak`, { headers: { @@ -199,43 +200,46 @@ const Auth = ({ children }: GenericContextProviderProps) => { source: 'keycloak' }); }) - .catch((_: HTTPError) => { + .catch((err: HTTPError) => { setUser(null); - setIsWaitingForVerification(false); setRefreshInterval(undefined); + open({ + reason: + err.response?.status === 403 + ? 'You are not authorized to use this application.' + : err.message + }); }); - }, [kyRef]); + }, [ + initialized, + keycloak.authenticated, + keycloak.token, + keycloak.tokenParsed?.preferred_username, + kyRef, + open + ]); useEffect(() => { - keycloak - .init({ - pkceMethod: 'S256', - checkLoginIframe: false - }) - .then(auth => { - console.log('after init', auth); - - if (auth) { - setKeyCloakInterval( - keycloak.tokenParsed?.exp !== undefined - ? getRefreshDelay(keycloak.tokenParsed.exp * 1000) - : undefined - ); - tokenVerification(); - } - }); - }, [tokenVerification]); + if (initialized && keycloak.authenticated) + setKeyCloakInterval( + keycloak.tokenParsed?.exp !== undefined + ? getRefreshDelay(keycloak.tokenParsed.exp * 1000) + : undefined + ); + tokenVerification(); + }, [initialized, keycloak, tokenVerification]); const logout = useCallback(() => { setUser(null); setRefreshInterval(undefined); + setKeyCloakInterval(undefined); - if (user?.source === 'keycloak') keycloak.logout(); + if (keycloak.authenticated) keycloak.logout(); kyRef .delete(`auth/logout`) .json() .catch((_: HTTPError) => {}); - }, [kyRef, user?.source]); + }, [keycloak, kyRef]); const login = useCallback( (...[username, password]: RequestAuthLogin) => { @@ -269,9 +273,12 @@ const Auth = ({ children }: GenericContextProviderProps) => { ); }) .catch(reason => {}); - }, [enqueueSnackbar]); + }, [enqueueSnackbar, keycloak]); - useIntervalAsync(tokenRefresh, keycloak.authenticated ? keyCloakInterval : undefined); + useIntervalAsync( + tokenRefresh, + initialized && keycloak.authenticated ? keyCloakInterval : undefined + ); const refresh = useCallback(async () => { if (user?.source === 'keycloak' && isAuthorized) return tokenVerification(); @@ -307,13 +314,17 @@ const Auth = ({ children }: GenericContextProviderProps) => { isAuthorized, isServerReachable: Boolean(isServerReachable), login, - tokenLogin: keycloak.login, logout, authKy, refresh }}> {children} - + zIndex.drawer + 1, + color: '#fff' + }}> Waiting for verification... diff --git a/src/services/DialogService.tsx b/src/services/DialogService.tsx index ccbae1d5d..ce258d06a 100644 --- a/src/services/DialogService.tsx +++ b/src/services/DialogService.tsx @@ -5,6 +5,7 @@ import { EditProjectInfoDialog } from '../ThreeEditor/components/Dialog/EditProj import { LoadFileDialog } from '../ThreeEditor/components/Dialog/LoadFileDialog'; import { NewProjectDialog } from '../ThreeEditor/components/Dialog/NewProjectDialog'; import { OpenFileDialog } from '../ThreeEditor/components/Dialog/OpenFileDialog'; +import { RejectKeycloakUserDialog } from '../ThreeEditor/components/Dialog/RejectKeycloakUserDialog'; import { RunSimulationDialog } from '../ThreeEditor/components/Dialog/RunSimulationDialog'; import { SaveFileDialog } from '../ThreeEditor/components/Dialog/SaveFileDialog'; import { @@ -35,7 +36,8 @@ export type DialogComponentTypeMap = EntriesToObj< ValidComponentTypes<['openFile', typeof OpenFileDialog]>, ValidComponentTypes<['runSimulation', typeof RunSimulationDialog]>, ValidComponentTypes<['saveFile', typeof SaveFileDialog]>, - ValidComponentTypes<['editProject', typeof EditProjectInfoDialog]> + ValidComponentTypes<['editProject', typeof EditProjectInfoDialog]>, + ValidComponentTypes<['rejectKeycloak', typeof RejectKeycloakUserDialog]> ] >; @@ -104,7 +106,8 @@ const DialogProvider = ({ children }: GenericContextProviderProps) => { openFile: OpenFileDialog, runSimulation: RunSimulationDialog, saveFile: SaveFileDialog, - editProject: EditProjectInfoDialog + editProject: EditProjectInfoDialog, + rejectKeycloak: RejectKeycloakUserDialog }), [] ); diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx new file mode 100644 index 000000000..15abc335f --- /dev/null +++ b/src/services/KeycloakAuthService.tsx @@ -0,0 +1,14 @@ +import Keycloak from 'keycloak-js'; +import { KeycloakProvider } from 'keycloak-react-web'; + +import { GenericContextProviderProps } from './GenericContext'; + +const authInstance = new Keycloak({ + url: process.env.KEYCLOAK_AUTH_URL ?? 'https://sso.pre.plgrid.pl/auth/', + realm: process.env.KEYCLOAK_REALM ?? 'PLGrid', + clientId: process.env.KEYCLOAK_CLIENT ?? 'yaptide-staging' +}) as any; + +export const KeycloakAuth = ({ children }: GenericContextProviderProps) => { + return {children}; +}; diff --git a/src/services/keycloakConfig.ts b/src/services/keycloakConfig.ts deleted file mode 100644 index 75f2b30f8..000000000 --- a/src/services/keycloakConfig.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const keycloakConfig = { - url: 'https://sso.pre.plgrid.pl/auth/', - realm: 'PLGrid', - clientId: 'yaptide-staging', - enableLogging: true -}; From 90c8aa0ca145d890d60b87684fc7172671484875 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Wed, 18 Oct 2023 19:10:01 +0200 Subject: [PATCH 03/12] Add new config order --- src/App.tsx | 2 +- .../Dialog/RejectKeycloakUserDialog.tsx | 3 +- .../components/Panels/LoginPanel.tsx | 6 +- src/services/AuthService.tsx | 60 ++++++++++--------- src/services/KeycloakAuthService.tsx | 17 +++++- 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ea7ff7970..a5c6f69c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -84,9 +84,9 @@ function App() { , , , - , , , + , , , , diff --git a/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx b/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx index a64e747b9..81001014c 100644 --- a/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx +++ b/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx @@ -22,7 +22,8 @@ export function RejectKeycloakUserDialog({ diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index c50f5f7b6..8b1d208a0 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -177,39 +177,39 @@ const Auth = ({ children }: GenericContextProviderProps) => { }, [demoMode, isServerReachable, user]); const tokenVerification = useCallback(() => { - if (!initialized || !keycloak.authenticated) return Promise.reject(); + if (!initialized || !keycloak.authenticated) return Promise.resolve(); const username = keycloak.tokenParsed?.preferred_username; if (true) open({ reason: 'You are not authorized to use this application.' }); - - return kyRef - .post(`auth/keycloak`, { - headers: { - Authorization: `Bearer ${keycloak.token}` - }, - json: { - username - } - }) - .then(() => { - setUser({ - username, - source: 'keycloak' - }); - }) - .catch((err: HTTPError) => { - setUser(null); - setRefreshInterval(undefined); - open({ - reason: - err.response?.status === 403 - ? 'You are not authorized to use this application.' - : err.message + else if (initialized) + return kyRef + .post(`auth/keycloak`, { + headers: { + Authorization: `Bearer ${keycloak.token}` + }, + json: { + username + } + }) + .then(() => { + setUser({ + username, + source: 'keycloak' + }); + }) + .catch((err: HTTPError) => { + setUser(null); + setRefreshInterval(undefined); + open({ + reason: + err.response?.status === 403 + ? 'You are not authorized to use this application.' + : err.message + }); }); - }); }, [ initialized, keycloak.authenticated, @@ -234,12 +234,12 @@ const Auth = ({ children }: GenericContextProviderProps) => { setRefreshInterval(undefined); setKeyCloakInterval(undefined); - if (keycloak.authenticated) keycloak.logout(); + if (initialized && keycloak.authenticated) keycloak.logout(); kyRef .delete(`auth/logout`) .json() .catch((_: HTTPError) => {}); - }, [keycloak, kyRef]); + }, [initialized, keycloak, kyRef]); const login = useCallback( (...[username, password]: RequestAuthLogin) => { @@ -263,6 +263,8 @@ const Auth = ({ children }: GenericContextProviderProps) => { ); const tokenRefresh = useCallback(() => { + if (!initialized) return Promise.resolve(); + return keycloak .updateToken(300) // 5 minutes in seconds minimum remaining lifetime for token before refresh is allowed .then(refreshed => { @@ -273,7 +275,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { ); }) .catch(reason => {}); - }, [enqueueSnackbar, keycloak]); + }, [enqueueSnackbar, keycloak, initialized]); useIntervalAsync( tokenRefresh, diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index 15abc335f..d568eae54 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -1,4 +1,4 @@ -import Keycloak from 'keycloak-js'; +import Keycloak, { KeycloakInitOptions } from 'keycloak-js'; import { KeycloakProvider } from 'keycloak-react-web'; import { GenericContextProviderProps } from './GenericContext'; @@ -9,6 +9,19 @@ const authInstance = new Keycloak({ clientId: process.env.KEYCLOAK_CLIENT ?? 'yaptide-staging' }) as any; +const initOptions = { + pkceMethod: 'S256', + onLoad: 'check-sso', + checkLoginIframe: false, + enableLogging: true +} as const satisfies KeycloakInitOptions; + export const KeycloakAuth = ({ children }: GenericContextProviderProps) => { - return {children}; + return ( + + {children} + + ); }; From 2b1945f6e8ed6f426130c2bd5ece704a9a0f3a40 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Wed, 18 Oct 2023 19:47:00 +0200 Subject: [PATCH 04/12] disable logging --- src/services/KeycloakAuthService.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index d568eae54..a245e4601 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -13,7 +13,7 @@ const initOptions = { pkceMethod: 'S256', onLoad: 'check-sso', checkLoginIframe: false, - enableLogging: true + enableLogging: false } as const satisfies KeycloakInitOptions; export const KeycloakAuth = ({ children }: GenericContextProviderProps) => { From 44d59cfed7bf586ae043eaa3e4ec05adfdd48065 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Wed, 18 Oct 2023 19:51:59 +0200 Subject: [PATCH 05/12] Update process env --- src/services/KeycloakAuthService.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index a245e4601..cfb229531 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -4,9 +4,13 @@ import { KeycloakProvider } from 'keycloak-react-web'; import { GenericContextProviderProps } from './GenericContext'; const authInstance = new Keycloak({ - url: process.env.KEYCLOAK_AUTH_URL ?? 'https://sso.pre.plgrid.pl/auth/', - realm: process.env.KEYCLOAK_REALM ?? 'PLGrid', - clientId: process.env.KEYCLOAK_CLIENT ?? 'yaptide-staging' + url: `${ + process.env.KEYCLOAK_BASE_URL + ? process.env.KEYCLOAK_BASE_URL + '/auth/' + : 'https://localhost:8080/auth/' + }`, + realm: `${process.env.KEYCLOAK_REALM ?? ''}`, + clientId: `${process.env.KEYCLOAK_CLIENT_ID ?? ''}` }) as any; const initOptions = { From 5a12d7f45d5651244df820b9ed947fe68d7e1ce0 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Wed, 18 Oct 2023 20:00:55 +0200 Subject: [PATCH 06/12] Add Todo for keycloak user verification --- src/services/AuthService.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index 8b1d208a0..cd0e5ff7f 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -180,7 +180,12 @@ const Auth = ({ children }: GenericContextProviderProps) => { if (!initialized || !keycloak.authenticated) return Promise.resolve(); const username = keycloak.tokenParsed?.preferred_username; - if (true) + /** + * TODO: Check if user is authorized to use this application (e.g. by checking if user is in a certain group) + */ + const validUser = true; + + if (!validUser) open({ reason: 'You are not authorized to use this application.' }); From c775239f51337f4cb7238ecc60ee0577ce5debfd Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Thu, 19 Oct 2023 18:02:14 +0200 Subject: [PATCH 07/12] Add conditional Keycloak provider --- src/App.tsx | 2 +- .../Dialog/RejectKeycloakUserDialog.tsx | 4 +- src/ThreeEditor/js/Storage.js | 53 ++++++++++++++----- .../components/Panels/LoginPanel.tsx | 4 +- src/services/AuthService.tsx | 13 ++--- src/services/GenericContext.ts | 22 ++++++++ src/services/KeycloakAuthService.tsx | 33 ++++++++++-- 7 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a5c6f69c8..213d272de 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -86,11 +86,11 @@ function App() { , , , + , , , , , - , ]}> diff --git a/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx b/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx index 81001014c..162b39e1a 100644 --- a/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx +++ b/src/ThreeEditor/components/Dialog/RejectKeycloakUserDialog.tsx @@ -1,13 +1,13 @@ import { Button } from '@mui/material'; -import { useKeycloak } from 'keycloak-react-web'; +import { useKeycloakAuth } from '../../../services/KeycloakAuthService'; import { ConcreteDialogProps, CustomDialog } from './CustomDialog'; export function RejectKeycloakUserDialog({ onClose, reason }: ConcreteDialogProps & { reason: string }) { - const { keycloak, initialized } = useKeycloak(); + const { keycloak, initialized } = useKeycloakAuth(); return ( ; @@ -80,7 +80,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { const [user, setUser] = useState(load(StorageKey.USER, isAuthUser)); const [reachInterval, setReachInterval] = useState(); const [refreshInterval, setRefreshInterval] = useState(180000); // 3 minutes in ms default interval for refresh token - const { keycloak, initialized } = useKeycloak(); + const { keycloak, initialized } = useKeycloakAuth(); const [keyCloakInterval, setKeyCloakInterval] = useState(); const [isServerReachable, setIsServerReachable] = useState(null); const { enqueueSnackbar } = useSnackbar(); @@ -215,14 +215,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { : err.message }); }); - }, [ - initialized, - keycloak.authenticated, - keycloak.token, - keycloak.tokenParsed?.preferred_username, - kyRef, - open - ]); + }, [initialized, keycloak, kyRef, open]); useEffect(() => { if (initialized && keycloak.authenticated) diff --git a/src/services/GenericContext.ts b/src/services/GenericContext.ts index 4190277a0..7821375ce 100644 --- a/src/services/GenericContext.ts +++ b/src/services/GenericContext.ts @@ -21,3 +21,25 @@ export const createGenericContext = () => { return [useGenericContext, genericContext.Provider] as const; }; + +export const createSubstituteContext = (useSubstituted: () => T) => { + // Create a context with a generic parameter or undefined + const genericContext = createContext(undefined); + + // Check if the value provided to the context is defined or call the substitute + const useSubstituteOrGenericContext = () => { + const contextIsDefined = useContext(genericContext); + + try { + return useSubstituted(); + } catch (e) { + if (!contextIsDefined) { + throw new Error('useSubstituteOrGenericContext must be used within a Provider'); + } + + return contextIsDefined; + } + }; + + return [useSubstituteOrGenericContext, genericContext.Provider] as const; +}; diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index cfb229531..d37b3d162 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -1,7 +1,8 @@ import Keycloak, { KeycloakInitOptions } from 'keycloak-js'; -import { KeycloakProvider } from 'keycloak-react-web'; +import { KeycloakProvider, useKeycloak } from 'keycloak-react-web'; -import { GenericContextProviderProps } from './GenericContext'; +import { useConfig } from '../config/ConfigService'; +import { createSubstituteContext, GenericContextProviderProps } from './GenericContext'; const authInstance = new Keycloak({ url: `${ @@ -20,12 +21,36 @@ const initOptions = { enableLogging: false } as const satisfies KeycloakInitOptions; -export const KeycloakAuth = ({ children }: GenericContextProviderProps) => { - return ( +export type KeycloakAuthContext = + | { + initialized: false; + keycloak?: Keycloak; + } + | { + initialized: true; + keycloak: Keycloak; + }; + +const [useKeycloakAuth, KeycloakAuthContextProvider] = + createSubstituteContext(useKeycloak); + +const KeycloakAuth = ({ children }: GenericContextProviderProps) => { + const { altAuth } = useConfig(); + + return altAuth ? ( {children} + ) : ( + + {children} + ); }; + +export { KeycloakAuth, useKeycloakAuth }; From f6289f832cbdc4ffdab9d72ad72d68bbc6487dc7 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Thu, 19 Oct 2023 18:43:11 +0200 Subject: [PATCH 08/12] Add keycloak auth context to both authorization types --- src/services/KeycloakAuthService.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index d37b3d162..bed90ec9b 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -41,7 +41,12 @@ const KeycloakAuth = ({ children }: GenericContextProviderProps) => { - {children} + + {children} + ) : ( Date: Thu, 19 Oct 2023 23:43:09 +0200 Subject: [PATCH 09/12] Remove dependency from dialog components to move provider higher --- src/App.tsx | 2 +- .../components/Dialog/ClearHistoryDialog.tsx | 9 ++-- .../Dialog/EditProjectInfoDialog.tsx | 8 ++-- .../components/Dialog/LoadFileDialog.tsx | 17 +++---- .../components/Dialog/NewProjectDialog.tsx | 9 ++-- .../Dialog/RejectKeycloakUserDialog.tsx | 17 ++++--- .../components/Dialog/RunSimulationDialog.tsx | 46 +++++++++++-------- .../components/Dialog/SaveFileDialog.tsx | 18 ++++---- .../Editor/EditorAppBar/EditorAppBar.tsx | 13 ++++-- .../components/EditorTitlebar.tsx | 8 +++- .../Editor/EditorMenu/EditorMenu.tsx | 6 ++- .../InputEditor/InputFilesEditor.tsx | 14 ++++-- .../components/Results/ResultsPanel.tsx | 16 ++++--- .../components/Simulation/SimulationCard.tsx | 22 +++++---- .../Simulation/SimulationCardGrid.tsx | 15 ++++-- src/services/AuthService.tsx | 14 +++--- src/services/DialogService.tsx | 20 ++++---- src/services/LoaderService.tsx | 14 +++--- 18 files changed, 163 insertions(+), 105 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 213d272de..6d77afa1c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -85,9 +85,9 @@ function App() { , , , + , , , - , , , , diff --git a/src/ThreeEditor/components/Dialog/ClearHistoryDialog.tsx b/src/ThreeEditor/components/Dialog/ClearHistoryDialog.tsx index 6603bafb5..e5e594ee4 100644 --- a/src/ThreeEditor/components/Dialog/ClearHistoryDialog.tsx +++ b/src/ThreeEditor/components/Dialog/ClearHistoryDialog.tsx @@ -1,11 +1,12 @@ import { Button } from '@mui/material'; -import { useStore } from '../../../services/StoreService'; +import { StoreContext } from '../../../services/StoreService'; import { ConcreteDialogProps, CustomDialog } from './CustomDialog'; -export function ClearHistoryDialog({ onClose }: ConcreteDialogProps) { - const { yaptideEditor } = useStore(); - +export function ClearHistoryDialog({ + onClose, + yaptideEditor +}: ConcreteDialogProps>>) { return ( >>) { const [title, setTitle] = useState(yaptideEditor?.config.getKey('project/title')); const [description, setDescription] = useState( yaptideEditor?.config.getKey('project/description') diff --git a/src/ThreeEditor/components/Dialog/LoadFileDialog.tsx b/src/ThreeEditor/components/Dialog/LoadFileDialog.tsx index 4c8ae48d0..9ae869a82 100644 --- a/src/ThreeEditor/components/Dialog/LoadFileDialog.tsx +++ b/src/ThreeEditor/components/Dialog/LoadFileDialog.tsx @@ -1,20 +1,21 @@ import { Button } from '@mui/material'; import Typography from '@mui/material/Typography'; -import { useStore } from '../../../services/StoreService'; +import { StoreContext } from '../../../services/StoreService'; import { EditorJson } from '../../js/EditorJson'; import { ConcreteDialogProps, CustomDialog } from './CustomDialog'; export function LoadFileDialog({ onClose, validVersion = true, - data -}: ConcreteDialogProps<{ - validVersion: boolean; - data: EditorJson; -}>) { - const { yaptideEditor } = useStore(); - + data, + yaptideEditor +}: ConcreteDialogProps< + { + validVersion: boolean; + data: EditorJson; + } & Required> +>) { return ( >>) { return ( ) { return ( diff --git a/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx b/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx index b2206b93d..d0fca4466 100644 --- a/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx +++ b/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx @@ -14,6 +14,7 @@ import { import { FC, useState } from 'react'; import { useDialog } from '../../../services/DialogService'; +import { useShSimulation } from '../../../services/ShSimulatorService'; import { useStore } from '../../../services/StoreService'; import { SimulatorType } from '../../../types/RequestTypes'; import { JobStatusData, SimulationInputFiles } from '../../../types/ResponseTypes'; @@ -210,7 +211,9 @@ export function PaginatedSimulationsFromBackend({ ...other }: SimulationsFromBackendProps) { const { setTrackedId } = useStore(); - const [open] = useDialog('runSimulation'); + const { open: openRunSimulationDialog } = useDialog('runSimulation'); + const { yaptideEditor } = useStore(); + const { postJobDirect, postJobBatch } = useShSimulation(); return ( @@ -222,11 +225,15 @@ export function PaginatedSimulationsFromBackend({ variant='contained' color='info' startIcon={} - disabled={!isBackendAlive} + disabled={!Boolean(isBackendAlive && yaptideEditor)} onClick={() => - open({ + yaptideEditor && + openRunSimulationDialog({ onSubmit: setTrackedId, - simulator: SimulatorType.SHIELDHIT + simulator: SimulatorType.SHIELDHIT, + yaptideEditor, + postJobDirect, + postJobBatch }) }> Run new simulation diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index c74e75da0..acb4b1d73 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -84,7 +84,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { const [keyCloakInterval, setKeyCloakInterval] = useState(); const [isServerReachable, setIsServerReachable] = useState(null); const { enqueueSnackbar } = useSnackbar(); - const [open] = useDialog('rejectKeycloak'); + const { open: openRejectKeycloakDialog } = useDialog('rejectKeycloak'); const isAuthorized = useMemo(() => user !== null || demoMode, [demoMode, user]); @@ -186,8 +186,9 @@ const Auth = ({ children }: GenericContextProviderProps) => { const validUser = true; if (!validUser) - open({ - reason: 'You are not authorized to use this application.' + openRejectKeycloakDialog({ + reason: 'You are not authorized to use this application.', + keycloakAuth: { keycloak, initialized } }); else if (initialized) return kyRef @@ -208,14 +209,15 @@ const Auth = ({ children }: GenericContextProviderProps) => { .catch((err: HTTPError) => { setUser(null); setRefreshInterval(undefined); - open({ + openRejectKeycloakDialog({ reason: err.response?.status === 403 ? 'You are not authorized to use this application.' - : err.message + : err.message, + keycloakAuth: { keycloak, initialized } }); }); - }, [initialized, keycloak, kyRef, open]); + }, [initialized, keycloak, kyRef, openRejectKeycloakDialog]); useEffect(() => { if (initialized && keycloak.authenticated) diff --git a/src/services/DialogService.tsx b/src/services/DialogService.tsx index ce258d06a..387449122 100644 --- a/src/services/DialogService.tsx +++ b/src/services/DialogService.tsx @@ -45,17 +45,21 @@ const [useDialogContext, DialogContextProvider] = createGenericContext( name: T -): readonly [(props: RestDialogPropsType[T]) => void, () => void, boolean]; +): { + open: (props: RestDialogPropsType[T]) => void; + close: () => void; + isOpen: boolean; +}; function useDialog( name: T -): readonly [(props?: RestDialogPropsType[T]) => void, () => void, boolean]; +): { open: (props?: RestDialogPropsType[T]) => void; close: () => void; isOpen: boolean }; function useDialog( name: T -): readonly [ - ((props?: RestDialogPropsType[T]) => void) | ((props: RestDialogPropsType[T]) => void), - () => void, - boolean -] { +): { + open: ((props?: RestDialogPropsType[T]) => void) | ((props: RestDialogPropsType[T]) => void); + close: () => void; + isOpen: boolean; +} { const { openDialog, closeDialog, getIsOpen } = useDialogContext(); const open = useCallback( (props: RestDialogPropsType[T] = {} as RestDialogPropsType[T]) => { @@ -69,7 +73,7 @@ function useDialog( }, [name, closeDialog]); const isOpen = useMemo(() => getIsOpen(name), [name, getIsOpen]); - return [open, close, isOpen] as const; + return { open, close, isOpen }; } const DialogProvider = ({ children }: GenericContextProviderProps) => { diff --git a/src/services/LoaderService.tsx b/src/services/LoaderService.tsx index cc9658717..cfcf17b1d 100644 --- a/src/services/LoaderService.tsx +++ b/src/services/LoaderService.tsx @@ -43,7 +43,7 @@ const readFile = (file: File) => { }; const Loader = ({ children }: GenericContextProviderProps) => { - const [open] = useDialog('loadFile'); + const { open: openLoadFileDialog } = useDialog('loadFile'); const { yaptideEditor, setResultsSimulationData, setLocalResultsSimulationData } = useStore(); const [, setUrlInPath] = useState(); @@ -53,10 +53,12 @@ const Loader = ({ children }: GenericContextProviderProps) => { switch (type) { case 'Editor': - open({ - data: json, - validVersion: json.metadata.version === JSON_VERSION - }); + if (yaptideEditor) + openLoadFileDialog({ + data: json, + validVersion: json.metadata.version === JSON_VERSION, + yaptideEditor + }); break; default: @@ -65,7 +67,7 @@ const Loader = ({ children }: GenericContextProviderProps) => { break; } }, - [open] + [openLoadFileDialog, yaptideEditor] ); const loadData = useCallback( From fdd32f1cc0f0465e0eab37a95102462c7cd810a7 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Fri, 20 Oct 2023 18:09:12 +0200 Subject: [PATCH 10/12] Change env names to include required prefix --- src/services/KeycloakAuthService.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index bed90ec9b..d277b9b62 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -4,15 +4,13 @@ import { KeycloakProvider, useKeycloak } from 'keycloak-react-web'; import { useConfig } from '../config/ConfigService'; import { createSubstituteContext, GenericContextProviderProps } from './GenericContext'; -const authInstance = new Keycloak({ - url: `${ - process.env.KEYCLOAK_BASE_URL - ? process.env.KEYCLOAK_BASE_URL + '/auth/' - : 'https://localhost:8080/auth/' - }`, - realm: `${process.env.KEYCLOAK_REALM ?? ''}`, - clientId: `${process.env.KEYCLOAK_CLIENT_ID ?? ''}` -}) as any; +const keycloakParams = { + url: `${process.env.REACT_APP_KEYCLOAK_BASE_URL ?? 'https://localhost:8080'}/auth/`, + realm: `${process.env.REACT_APP_KEYCLOAK_REALM ?? ''}`, + clientId: `${process.env.REACT_APP_KEYCLOAK_CLIENT_ID ?? ''}` +}; + +const authInstance = new Keycloak(keycloakParams) as any; const initOptions = { pkceMethod: 'S256', From 52d0eeb3dfa2796cc7b5ce6e2580ce0bd970a1d3 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Mon, 23 Oct 2023 13:47:08 +0200 Subject: [PATCH 11/12] Prevent unnecessary token auth --- src/services/AuthService.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index acb4b1d73..2a64e4660 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -200,11 +200,17 @@ const Auth = ({ children }: GenericContextProviderProps) => { username } }) - .then(() => { - setUser({ - username, - source: 'keycloak' - }); + .json() + .then(({ accessExp }) => { + setUser(prev => + prev?.username === username + ? prev + : { + username, + source: 'keycloak' + } + ); + setRefreshInterval(getRefreshDelay(accessExp)); }) .catch((err: HTTPError) => { setUser(null); @@ -299,7 +305,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { return setRefreshInterval(getRefreshDelay(accessExp)); } catch (_) {} - }, [demoMode, isAuthorized, isServerReachable, kyIntervalRef, tokenVerification, user?.source]); + }, [demoMode, isAuthorized, isServerReachable, kyIntervalRef, tokenVerification, user]); const authKy = useMemo(() => kyRef, [kyRef]); From 389d0b3b2eee3de3347a0621b1b0ddc5ccff5c94 Mon Sep 17 00:00:00 2001 From: Jakub Niechaj Date: Thu, 26 Oct 2023 15:37:26 +0200 Subject: [PATCH 12/12] Apply requested changes --- src/ThreeEditor/js/Storage.js | 31 +++++++++++-------- .../components/Simulation/SimulationCard.tsx | 4 +-- .../Simulation/SimulationCardGrid.tsx | 2 +- src/services/AuthService.tsx | 8 ++--- src/services/KeycloakAuthService.tsx | 24 +++++++------- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/ThreeEditor/js/Storage.js b/src/ThreeEditor/js/Storage.js index 698d682a9..2af75efdb 100644 --- a/src/ThreeEditor/js/Storage.js +++ b/src/ThreeEditor/js/Storage.js @@ -20,6 +20,21 @@ function Storage() { let database; + const deleteDatabase = function () { + const req = indexedDB.deleteDatabase(name); + req.onsuccess = function () { + console.log('Deleted database successfully'); + }; + + req.onerror = function () { + console.log('Could not delete database'); + }; + + req.onblocked = function () { + console.log('Could not delete database due to the operation being blocked'); + }; + }; + return { init: function (callback) { const request = indexedDB.open(name, version); @@ -40,18 +55,7 @@ function Storage() { request.onerror = function (event) { console.error('IndexedDB', event); // delete database - const req = indexedDB.deleteDatabase(name); - req.onsuccess = function () { - console.log('Deleted database successfully'); - }; - - req.onerror = function () { - console.log('Could not delete database'); - }; - - req.onblocked = function () { - console.log('Could not delete database due to the operation being blocked'); - }; + deleteDatabase(); }; }, @@ -65,7 +69,7 @@ function Storage() { }; } catch (error) { console.error(error); - callback(); + deleteDatabase(); } }, @@ -84,6 +88,7 @@ function Storage() { }; } catch (error) { console.error(error); + deleteDatabase(); } }, diff --git a/src/WrapperApp/components/Simulation/SimulationCard.tsx b/src/WrapperApp/components/Simulation/SimulationCard.tsx index d5b7a6578..16cec2289 100644 --- a/src/WrapperApp/components/Simulation/SimulationCard.tsx +++ b/src/WrapperApp/components/Simulation/SimulationCard.tsx @@ -430,9 +430,7 @@ export default function SimulationCard({ size='small' onClick={onClickSaveToFile} disabled={ - !Boolean( - simulationStatus.jobState === StatusState.COMPLETED && yaptideEditor - ) + !(simulationStatus.jobState === StatusState.COMPLETED && yaptideEditor) }> Save to file diff --git a/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx b/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx index d0fca4466..27418fd21 100644 --- a/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx +++ b/src/WrapperApp/components/Simulation/SimulationCardGrid.tsx @@ -225,7 +225,7 @@ export function PaginatedSimulationsFromBackend({ variant='contained' color='info' startIcon={} - disabled={!Boolean(isBackendAlive && yaptideEditor)} + disabled={!(isBackendAlive && yaptideEditor)} onClick={() => yaptideEditor && openRunSimulationDialog({ diff --git a/src/services/AuthService.tsx b/src/services/AuthService.tsx index 2a64e4660..e6e0f9355 100644 --- a/src/services/AuthService.tsx +++ b/src/services/AuthService.tsx @@ -173,11 +173,11 @@ const Auth = ({ children }: GenericContextProviderProps) => { if (!demoMode && user?.source !== 'keycloak' && isServerReachable) setRefreshInterval(prev => (prev === undefined ? 3000 : prev)); // 3 seconds in ms default interval for refresh when logged in with username and password - else if (!isServerReachable || !user?.source) setRefreshInterval(undefined); + else setRefreshInterval(undefined); }, [demoMode, isServerReachable, user]); const tokenVerification = useCallback(() => { - if (!initialized || !keycloak.authenticated) return Promise.resolve(); + if (!initialized || !keycloak.authenticated) return; const username = keycloak.tokenParsed?.preferred_username; /** @@ -191,7 +191,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { keycloakAuth: { keycloak, initialized } }); else if (initialized) - return kyRef + kyRef .post(`auth/keycloak`, { headers: { Authorization: `Bearer ${keycloak.token}` @@ -289,7 +289,7 @@ const Auth = ({ children }: GenericContextProviderProps) => { ); const refresh = useCallback(async () => { - if (user?.source === 'keycloak' && isAuthorized) return tokenVerification(); + if (user?.source === 'keycloak' && isAuthorized) return await tokenVerification(); if (demoMode || !isServerReachable) { setRefreshInterval(undefined); diff --git a/src/services/KeycloakAuthService.tsx b/src/services/KeycloakAuthService.tsx index d277b9b62..7e7da738d 100644 --- a/src/services/KeycloakAuthService.tsx +++ b/src/services/KeycloakAuthService.tsx @@ -1,5 +1,6 @@ import Keycloak, { KeycloakInitOptions } from 'keycloak-js'; import { KeycloakProvider, useKeycloak } from 'keycloak-react-web'; +import { ReactNode, useCallback } from 'react'; import { useConfig } from '../config/ConfigService'; import { createSubstituteContext, GenericContextProviderProps } from './GenericContext'; @@ -34,25 +35,26 @@ const [useKeycloakAuth, KeycloakAuthContextProvider] = const KeycloakAuth = ({ children }: GenericContextProviderProps) => { const { altAuth } = useConfig(); - - return altAuth ? ( - + const proxyContextProvider = useCallback( + (children: ReactNode) => ( {children} + ), + [] + ); + + return altAuth ? ( + + {proxyContextProvider(children)} ) : ( - - {children} - + proxyContextProvider(children) ); };