Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Button } from '@mui/material';

import { useConfig } from '../../../config/ConfigService';
import { KeycloakAuthContext } from '../../../services/KeycloakAuthService';
import { ConcreteDialogProps, CustomDialog } from './CustomDialog';

export function RejectKeycloakRefreshUserDialog({
onClose,
reason,
keycloakAuth: { keycloak, initialized }
}: ConcreteDialogProps<{
reason: string;
keycloakAuth: KeycloakAuthContext;
}>) {
const { backendUrl } = useConfig();

return (
<CustomDialog
onClose={onClose}
alert={true}
title={`${backendUrl}: Authentication Failed`}
contentText={
`Hello ${keycloak!.tokenParsed?.preferred_username}.` +
`We could not authenticate you because of the following:\n${reason}\n` +
'Please contact with an administrator to resolve this issue.'
}>
<Button
onClick={() => {
onClose();

if (initialized && keycloak!.authenticated) keycloak!.logout();
}}
autoFocus>
I understand
</Button>
</CustomDialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ export function RejectKeycloakUserDialog({
<CustomDialog
onClose={onClose}
alert={true}
title='Keycloak User Rejected'
title='User Login Rejected'
contentText={
initialized
? `Hello ${keycloak!.tokenParsed
?.preferred_username}. We could not accept your authorisation because of the following:\n${reason}\nPlease contact with an administrator to resolve this issue.`
? `Hello ${
keycloak!.tokenParsed?.preferred_username
}. We could not accept your authorisation because of the following:\n${reason}\nPlease contact with an administrator to resolve this issue.`
: 'Connection could not be established with the authentication server. Please try again later.'
}>
<Button
Expand Down
83 changes: 47 additions & 36 deletions src/services/AuthService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ const Auth = ({ children }: GenericContextProviderProps) => {
const [keyCloakInterval, setKeyCloakInterval] = useState<number>();
const [isServerReachable, setIsServerReachable] = useState<boolean | null>(null);
const { enqueueSnackbar } = useSnackbar();
const { open: openRejectKeycloakDialog } = useDialog('rejectKeycloak');
const { open: openRejectKeycloakUserDialog } = useDialog('rejectKeycloak');
const { open: openRejectKeycloakRefreshUserDialog } = useDialog('rejectKeycloakRefresh');

const isAuthorized = useMemo(() => user !== null || demoMode, [demoMode, user]);

Expand Down Expand Up @@ -199,7 +200,7 @@ const Auth = ({ children }: GenericContextProviderProps) => {
return;
} else {
// keycloak authentication is initilized, so it makes sense to check if user is authenticated in keycloak
if (!keycloak.authenticated) {
if (!keycloak?.authenticated) {
// user not authenticated, forcing logout from yaptide app
logout();

Expand All @@ -209,52 +210,66 @@ const Auth = ({ children }: GenericContextProviderProps) => {
// user authenticated, we proceed with further checks
}

const setNewUserFromKeycloak = (username: string, accessExp: number) => {
setUser(prev => {
const sameUser = prev?.username === username;

return sameUser ? prev : { username, source: 'keycloak' };
});
setRefreshInterval(getRefreshDelay(accessExp));
};

const prepareKeycloakRefreshRejectionMessage = (err: HTTPError) => {
if (err.response?.status === 403) {
return 'You are not authorized to use this application.';
}

if (err.response?.status === undefined) {
return "we couldn't connect to YAPTIDE backend.";
}

if (err.message == undefined) {
return 'unknown error.';
}

return err.message;
};

checkPlgridAccessServices(keycloak.tokenParsed)
.then(() => {
const username = keycloak.tokenParsed?.preferred_username;
kyRef
.post(`auth/keycloak`, {
headers: {
Authorization: `Bearer ${keycloak.token}`
},
json: {
username
}
headers: { Authorization: `Bearer ${keycloak.token}` },
json: { username }
})
.json<ResponseAuthLogin>()
.then(({ accessExp }) => {
setUser(prev =>
prev?.username === username
? prev
: {
username,
source: 'keycloak'
}
);
setRefreshInterval(getRefreshDelay(accessExp));
})
.then(({ accessExp }) => setNewUserFromKeycloak(username, accessExp))
.catch((err: HTTPError) => {
setUser(null);
setRefreshInterval(undefined);
openRejectKeycloakDialog({
reason:
err.response?.status === 403
? 'You are not authorized to use this application.'
: err.message,
openRejectKeycloakRefreshUserDialog({
reason: prepareKeycloakRefreshRejectionMessage(err),
keycloakAuth: { keycloak, initialized }
});
});
})
.catch((reason: string) => {
openRejectKeycloakDialog({
openRejectKeycloakUserDialog({
reason,
keycloakAuth: { keycloak, initialized }
});
});
}, [initialized, keycloak, kyRef, openRejectKeycloakDialog]);
}, [
initialized,
keycloak,
kyRef,
openRejectKeycloakUserDialog,
openRejectKeycloakRefreshUserDialog
]);

useEffect(() => {
if (initialized && keycloak.authenticated)
if (initialized && keycloak?.authenticated)
setKeyCloakInterval(
keycloak.tokenParsed?.exp !== undefined
? getRefreshDelay(keycloak.tokenParsed.exp * 1000)
Expand All @@ -268,7 +283,7 @@ const Auth = ({ children }: GenericContextProviderProps) => {
setRefreshInterval(undefined);
setKeyCloakInterval(undefined);

if (initialized && keycloak.authenticated) keycloak.logout();
if (initialized && keycloak?.authenticated) keycloak.logout();
kyRef
.delete(`auth/logout`)
.json<YaptideResponse>()
Expand Down Expand Up @@ -300,20 +315,16 @@ const Auth = ({ children }: GenericContextProviderProps) => {
if (!initialized || user?.source !== 'keycloak') return Promise.resolve();

return keycloak
.updateToken(300) // 5 minutes in seconds minimum remaining lifetime for token before refresh is allowed
?.updateToken(300) // 5 minutes in seconds minimum remaining lifetime for token before refresh is allowed
.then(refreshed => {
if (refreshed)
enqueueSnackbar(
`Token refreshed ${keycloak.tokenParsed?.exp} -> ${keycloak.tokenParsed?.exp}`,
{ variant: 'success' }
);
if (refreshed) enqueueSnackbar('User token refreshed', { variant: 'success' });
})
.catch(reason => {});
}, [initialized, user?.source, keycloak, enqueueSnackbar]);

useIntervalAsync(
tokenRefresh,
initialized && keycloak.authenticated ? keyCloakInterval : undefined
initialized && keycloak?.authenticated ? keyCloakInterval : undefined
);

const refresh = useCallback(async () => {
Expand Down Expand Up @@ -356,7 +367,7 @@ const Auth = ({ children }: GenericContextProviderProps) => {
}}>
{children}
<Backdrop
open={initialized && !!keycloak.authenticated && !isAuthorized}
open={initialized && !!keycloak?.authenticated && !isAuthorized}
sx={{
zIndex: ({ zIndex }: Theme) => zIndex.drawer + 1,
color: '#fff'
Expand Down
3 changes: 3 additions & 0 deletions src/services/DialogService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,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 { RejectKeycloakRefreshUserDialog } from '../ThreeEditor/components/Dialog/RejectKeycloakRefreshUserDialog';
import { RejectKeycloakUserDialog } from '../ThreeEditor/components/Dialog/RejectKeycloakUserDialog';
import { RunSimulationDialog } from '../ThreeEditor/components/Dialog/RunSimulationDialog';
import { SaveFileDialog } from '../ThreeEditor/components/Dialog/SaveFileDialog';
Expand Down Expand Up @@ -39,6 +40,7 @@ export type DialogComponentTypeMap = EntriesToObj<
ValidComponentTypes<['saveFile', typeof SaveFileDialog]>,
ValidComponentTypes<['editProject', typeof EditProjectInfoDialog]>,
ValidComponentTypes<['rejectKeycloak', typeof RejectKeycloakUserDialog]>,
ValidComponentTypes<['rejectKeycloakRefresh', typeof RejectKeycloakRefreshUserDialog]>,
ValidComponentTypes<['changeScoringType', typeof ChangeScoringTypeDialog]>
]
>;
Expand Down Expand Up @@ -114,6 +116,7 @@ const DialogProvider = ({ children }: GenericContextProviderProps) => {
saveFile: SaveFileDialog,
editProject: EditProjectInfoDialog,
rejectKeycloak: RejectKeycloakUserDialog,
rejectKeycloakRefresh: RejectKeycloakRefreshUserDialog,
changeScoringType: ChangeScoringTypeDialog
}),
[]
Expand Down
Loading