Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validates client config #1083

Draft
wants to merge 10 commits into
base: feature/distributed-demo
Choose a base branch
from
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@
"resize-observer-polyfill": "^1.5.1",
"serve": "^14.2.1",
"styled-components": "^6.1.1",
"typescript": "5.1.6"
"typescript": "5.1.6",
"zod": "^3.23.8"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atomicgamedeveloper
Is this used? Overall, it is better to move the config verification logic into util/config.ts. The zod can be used there.

},
"devDependencies": {
"@babel/core": "7.25.8",
Expand Down
12 changes: 9 additions & 3 deletions client/src/page/LayoutPublic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import AppBar from '@mui/material/AppBar';
import Footer from 'page/Footer';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { Container } from '@mui/material';
import { Breakpoint, Container } from '@mui/material';
import LinkButtons from 'components/LinkButtons';
import toolbarLinkValues from 'util/toolbarUtil';

Expand All @@ -26,7 +26,10 @@ const DTappBar = () => (
</AppBar>
);

function LayoutPublic(props: { children: React.ReactNode }) {
function LayoutPublic(props: {
children: React.ReactNode;
containerMaxWidth?: Breakpoint;
atomicgamedeveloper marked this conversation as resolved.
Show resolved Hide resolved
}) {
return (
<Box
sx={{
Expand All @@ -38,7 +41,10 @@ function LayoutPublic(props: { children: React.ReactNode }) {
>
<DTappBar />
<Toolbar />
<Container component="main" maxWidth="xs">
<Container
component="main"
maxWidth={props.containerMaxWidth ? props.containerMaxWidth : 'xs'}
>
{props.children}
</Container>

Expand Down
125 changes: 125 additions & 0 deletions client/src/route/auth/ConfigItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { Tooltip } from '@mui/material';
import React from 'react';
import { validationType } from './VerifyConfig';

const ConfigIcon = (toolTipTitle: string, icon: JSX.Element): JSX.Element => (
<Tooltip
title={toolTipTitle}
PopperProps={{ container: document.getElementById('root') }}
>
{icon}
</Tooltip>
);

export const getConfigIcon = (
validation: validationType,
label: string,
): JSX.Element => {
let icon = <ErrorOutlineIcon color="error" />;
let toolTipTitle = `${label} threw the following error: ${validation.error}`;
const configHasStatus = validation.status !== undefined;
const configHasError = validation.error !== undefined;
if (!configHasError) {
const statusMessage = configHasStatus
? `${validation.value} responded with status code ${validation.status}.`
: '';
const validationStatusIsOK =
configHasStatus &&
((validation.status! >= 200 && validation.status! <= 299) ||
validation.status! === 302);
icon =
validationStatusIsOK || !configHasStatus ? (
<CheckCircleIcon color="success" />
) : (
<ErrorOutlineIcon color="warning" />
);
toolTipTitle =
validationStatusIsOK || !configHasStatus
? `${label} field is configured correctly.`
: `${label} field may not be configured correctly.`;
toolTipTitle += ` ${statusMessage}`;
}
return ConfigIcon(toolTipTitle, icon);
};

export const ConfigItem: React.FC<{
label: string;
value: string;
validation?: validationType;
}> = ({ label, value, validation = { error: 'Validating unavailable' } }) => (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
margin: '5px 0',
}}
>
{getConfigIcon(validation, label)}
<div>
<strong>{label}:</strong> {value}
</div>
</div>
);
ConfigItem.displayName = 'ConfigItem';

export const windowEnvironmentVariables: { value: string; key: string }[] = [
{
value: window.env.REACT_APP_ENVIRONMENT,
key: 'environment',
},
{
value: window.env.REACT_APP_URL,
key: 'url',
},
{
value: window.env.REACT_APP_URL_BASENAME,
key: 'url_basename',
},
{
value: window.env.REACT_APP_URL_DTLINK,
key: 'url_dtlink',
},
{
value: window.env.REACT_APP_URL_LIBLINK,
key: 'url_liblink',
},
{
value: window.env.REACT_APP_WORKBENCHLINK_VNCDESKTOP,
key: 'workbenchlink_vncdesktop',
},
{
value: window.env.REACT_APP_WORKBENCHLINK_VSCODE,
key: 'workbenchlink_vscode',
},
{
value: window.env.REACT_APP_WORKBENCHLINK_JUPYTERLAB,
key: 'workbenchlink_jupyterlab',
},
{
value: window.env.REACT_APP_WORKBENCHLINK_JUPYTERNOTEBOOK,
key: 'workbenchlink_jupyternotebook',
},
{
value: window.env.REACT_APP_CLIENT_ID,
key: 'client_id',
},
{
value: window.env.REACT_APP_AUTH_AUTHORITY,
key: 'auth_authority',
},
{
value: window.env.REACT_APP_REDIRECT_URI,
key: 'redirect_uri',
},
{
value: window.env.REACT_APP_LOGOUT_REDIRECT_URI,
key: 'logout_redirect_uri',
},
{
value: window.env.REACT_APP_GITLAB_SCOPES,
key: 'gitlab_scopes',
},
];
154 changes: 104 additions & 50 deletions client/src/route/auth/Signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,120 @@ import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';

import { useAuth } from 'react-oidc-context';
import Button from '@mui/material/Button';
import { useState } from 'react';
import { CircularProgress } from '@mui/material';
import VerifyConfig, {
getValidationResults,
validationType,
} from './VerifyConfig';

const loadingComponent = (
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
Verifying configuration
<CircularProgress />
</Box>
);

const signInComponent = (startAuthProcess: () => void) => (
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Button
onClick={startAuthProcess}
variant="contained"
sx={{
display: 'inline-flex',
alignItems: 'center',
padding: '10px 20px',
backgroundColor: '#fc6d27',
color: 'white',
border: 'none',
borderRadius: '5px',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textTransform: 'none',
'&:hover': {
backgroundColor: '#fc6d27',
textDecoration: 'none',
},
}}
startIcon={
<img
src={
'https://gitlab.com/gitlab-com/gitlab-artwork/-/raw/master/logo/logo-square.png'
}
alt="GitLab logo"
style={{
height: '20px',
marginRight: '10px',
}}
/>
}
>
Sign In with GitLab
</Button>
</Box>
);

function SignIn() {
const auth = useAuth();
const [validationResults, setValidationResults] = useState<{
[key: string]: validationType;
}>({});
const [isLoading, setIsLoading] = useState(true);
const configsToVerify = [
'url',
'auth_authority',
'redirect_uri',
'logout_redirect_uri',
];
const verifyConfigComponent = VerifyConfig({
keys: configsToVerify,
title: 'Config validation failed',
});
React.useEffect(() => {
const fetchValidationResults = async () => {
const results = await getValidationResults();
setValidationResults(results);
setIsLoading(false);
};
fetchValidationResults();
}, []);

const startAuthProcess = () => {
auth.signinRedirect();
};

return (
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Button
onClick={startAuthProcess}
variant="contained"
sx={{
display: 'inline-flex',
alignItems: 'center',
padding: '10px 20px',
backgroundColor: '#fc6d27',
color: 'white',
border: 'none',
borderRadius: '5px',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textTransform: 'none',
'&:hover': {
backgroundColor: '#fc6d27',
textDecoration: 'none',
},
}}
startIcon={
<img
src={
'https://gitlab.com/gitlab-com/gitlab-artwork/-/raw/master/logo/logo-square.png'
}
alt="GitLab logo"
style={{
height: '20px',
marginRight: '10px',
}}
/>
}
>
Sign In with GitLab
</Button>
</Box>
);
let displayedComponent: React.ReactNode = loadingComponent;
if (!isLoading) {
const configHasKeyErrors = configsToVerify.reduce(
(accumulator, currentValue) =>
accumulator || validationResults[currentValue].error !== undefined,
false,
);
displayedComponent = configHasKeyErrors
? verifyConfigComponent
: signInComponent(startAuthProcess);
}
return displayedComponent;
}

export default SignIn;
Loading
Loading