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

Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a8838bf
Add VerifyConfig.tsx with route
atomicgamedeveloper Nov 21, 2024
89afd3a
Add edge-case for opaque responses
atomicgamedeveloper Nov 22, 2024
740e8b3
Add status code and more useful error messages
atomicgamedeveloper Nov 28, 2024
5092824
Refactor VerifyConfig
atomicgamedeveloper Nov 29, 2024
6976bbf
Reinstate status code information in tool tips
atomicgamedeveloper Nov 29, 2024
3260e98
Integrate verification and sign in
atomicgamedeveloper Nov 29, 2024
a2b9df1
Clean up ConfigItems.tsx, Signin.tsx and VerifyConfig.tsx
atomicgamedeveloper Dec 6, 2024
b514a82
Merge branch 'feature/distributed-demo' into 658-validate-client-config
atomicgamedeveloper Dec 6, 2024
258aada
Unmemoise ConfigItem
atomicgamedeveloper Dec 6, 2024
d18d766
Merge branch '658-validate-client-config' of https://github.com/atomi…
atomicgamedeveloper Dec 6, 2024
1b0e8d6
Adds Digital Twin create and Library features (#1081)
VanessaScherma Dec 8, 2024
12032ff
Make fetches synchronous and give them timeouts of 1s
atomicgamedeveloper Dec 12, 2024
fc33c92
Fixes wrong docker files for library microservice (#1102)
nichlaes Dec 13, 2024
0f127e7
Dependency upgrades for libms and runner (#1112)
prasadtalasila Dec 15, 2024
75492d7
Fix and expand SignIn unit tests, improve source code
atomicgamedeveloper Dec 16, 2024
238c686
Fix integration tests
atomicgamedeveloper Dec 16, 2024
f011687
Add e2e test
atomicgamedeveloper Dec 16, 2024
48c88ba
Remove useEffect dependency array in Signin.tsx
atomicgamedeveloper Dec 16, 2024
7d58e89
Add VerifyConfig.tsx with route
atomicgamedeveloper Nov 21, 2024
63b3914
Add edge-case for opaque responses
atomicgamedeveloper Nov 22, 2024
608b4dc
Add status code and more useful error messages
atomicgamedeveloper Nov 28, 2024
d4c1bcd
Refactor VerifyConfig
atomicgamedeveloper Nov 29, 2024
f9d7510
Reinstate status code information in tool tips
atomicgamedeveloper Nov 29, 2024
83587f7
Integrate verification and sign in
atomicgamedeveloper Nov 29, 2024
055234b
Clean up ConfigItems.tsx, Signin.tsx and VerifyConfig.tsx
atomicgamedeveloper Dec 6, 2024
92a7f28
Unmemoise ConfigItem
atomicgamedeveloper Dec 6, 2024
5090e2b
Make fetches synchronous and give them timeouts of 1s
atomicgamedeveloper Dec 12, 2024
4e69355
Fix and expand SignIn unit tests, improve source code
atomicgamedeveloper Dec 16, 2024
5620087
Fix integration tests
atomicgamedeveloper Dec 16, 2024
dd92a0e
Add e2e test
atomicgamedeveloper Dec 16, 2024
22fbc41
Remove useEffect dependency array in Signin.tsx
atomicgamedeveloper Dec 16, 2024
6492e66
Add fetchValidations dependency in VerifyConfig.tsx
atomicgamedeveloper Dec 16, 2024
9d8e81f
Add development dependencies to fix warnings
atomicgamedeveloper Dec 17, 2024
8b9c829
No changes
atomicgamedeveloper Dec 17, 2024
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
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"
},
"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;
}) {
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
256 changes: 256 additions & 0 deletions client/src/route/auth/VerifyConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import { Paper, Tooltip, Typography } from '@mui/material';
import * as React from 'react';
import { z } from 'zod';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';

const EnvironmentEnum = z.enum(['dev', 'local', 'prod', 'test']);
const PathString = z.string();
const ScopesString = z.literal('openid profile read_user read_repository api');

type validationType = {
value?: string;
status?: number;
error?: string;
};

async function urlIsReachable(url: string): Promise<validationType> {
try {
const response = await fetch(url, { method: 'HEAD' });

if (response.ok || response.status === 302) {
return { value: url, status: response.status, error: undefined };
}
return {
value: url,
status: response.status,
error: `Unexpected response code ${response.status} from ${url}.`,
};
} catch {
try {
const response = await fetch(url, { method: 'HEAD', mode: 'no-cors' });
if (response.type === 'opaque') {
return { value: url, status: response.status, error: undefined };
}
return {
value: url,
status: response.status,
error: `Unexpected response code ${response.status} from ${url}.`,
};
} catch (error) {
return {
value: url,
status: undefined,
error: `An error occurred when fetching ${url}. ${error}`,
};
}
}
}

const parseField = (
parser: {
safeParse: (value: string) => {
success: boolean;
error?: { message?: string };
};
},
value: string,
): validationType => {
const result = parser.safeParse(value);
return result.success
? { value, error: undefined }
: { value: undefined, error: result.error?.message };
};

const getValidationResults = async (): Promise<{
[key: string]: validationType;
}> => {
const results: { [key: string]: validationType } = {
environment: parseField(EnvironmentEnum, window.env.REACT_APP_ENVIRONMENT),
url: await urlIsReachable(window.env.REACT_APP_URL),
url_basename: parseField(PathString, window.env.REACT_APP_URL_BASENAME),
url_dtlink: parseField(PathString, window.env.REACT_APP_URL_DTLINK),
url_liblink: parseField(PathString, window.env.REACT_APP_URL_LIBLINK),
workbenchlink_vncdesktop: parseField(
PathString,
window.env.REACT_APP_WORKBENCHLINK_VNCDESKTOP,
),
workbenchlink_vscode: parseField(
PathString,
window.env.REACT_APP_WORKBENCHLINK_VSCODE,
),
workbenchlink_jupyterlab: parseField(
PathString,
window.env.REACT_APP_WORKBENCHLINK_JUPYTERLAB,
),
workbenchlink_jupyternotebook: parseField(
PathString,
window.env.REACT_APP_WORKBENCHLINK_JUPYTERNOTEBOOK,
),
client_id: parseField(PathString, window.env.REACT_APP_CLIENT_ID),
auth_authority: await urlIsReachable(window.env.REACT_APP_AUTH_AUTHORITY),
redirect_uri: await urlIsReachable(window.env.REACT_APP_REDIRECT_URI),
logout_redirect_uri: await urlIsReachable(
window.env.REACT_APP_LOGOUT_REDIRECT_URI,
),
gitlab_scopes: parseField(ScopesString, window.env.REACT_APP_GITLAB_SCOPES),
};
return results;
};

const configIcon = (validation: validationType, label: string): JSX.Element => {
const root = document.getElementById('root');
if (!validation.error) {
const title = `${label} field is configured correctly. ${validation.status !== undefined ? `${validation.value} responded with status code ${validation.status}.` : ``}`;
return validation.status === undefined ||
(validation.status >= 200 && validation.status < 300) ? (
<Tooltip title={title} PopperProps={{ container: root }}>
<CheckCircleIcon color="success" />
</Tooltip>
) : (
<Tooltip
title={`${label} field may not be configured correctly.`}
PopperProps={{ container: root }}
>
<ErrorOutlineIcon color="warning" />
</Tooltip>
);
}
return (
<Tooltip title={validation.error} PopperProps={{ container: root }}>
<ErrorOutlineIcon color="error" />
</Tooltip>
);
};

const ConfigItem: React.FC<{
label: string;
value: string;
validation?: validationType;
}> = React.memo(
({ label, value, validation = { error: 'Validation is not available' } }) => (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
margin: '5px 0',
}}
>
{configIcon(validation, label)}
<div>
<strong>{label}:</strong> {value}
</div>
</div>
),
);
ConfigItem.displayName = 'ConfigItem';

const VerifyConfig: React.FC = () => {
const [validationResults, setValidationResults] = React.useState<{
[key: string]: validationType;
}>({});

React.useEffect(() => {
const fetchValidations = async () => {
const results = await getValidationResults();
setValidationResults(results);
};
fetchValidations();
}, []);

const configItems = [
{
label: 'APP ENVIRONMENT',
value: window.env.REACT_APP_ENVIRONMENT,
key: 'environment',
},
{ label: 'APP URL', value: window.env.REACT_APP_URL, key: 'url' },
{
label: 'APP URL BASENAME',
value: window.env.REACT_APP_URL_BASENAME,
key: 'url_basename',
},
{
label: 'APP URL DTLINK',
value: window.env.REACT_APP_URL_DTLINK,
key: 'url_dtlink',
},
{
label: 'APP URL LIBLINK',
value: window.env.REACT_APP_URL_LIBLINK,
key: 'url_liblink',
},
{
label: 'WORKBENCHLINK VNCDESKTOP',
value: window.env.REACT_APP_WORKBENCHLINK_VNCDESKTOP,
key: 'workbenchlink_vncdesktop',
},
{
label: 'WORKBENCHLINK VSCODE',
value: window.env.REACT_APP_WORKBENCHLINK_VSCODE,
key: 'workbenchlink_vscode',
},
{
label: 'WORKBENCHLINK JUPYTERLAB',
value: window.env.REACT_APP_WORKBENCHLINK_JUPYTERLAB,
key: 'workbenchlink_jupyterlab',
},
{
label: 'WORKBENCHLINK JUPYTERNOTEBOOK',
value: window.env.REACT_APP_WORKBENCHLINK_JUPYTERNOTEBOOK,
key: 'workbenchlink_jupyternotebook',
},
{
label: 'CLIENT ID',
value: window.env.REACT_APP_CLIENT_ID,
key: 'client_id',
},
{
label: 'AUTH AUTHORITY',
value: window.env.REACT_APP_AUTH_AUTHORITY,
key: 'auth_authority',
},
{
label: 'REDIRECT URI',
value: window.env.REACT_APP_REDIRECT_URI,
key: 'redirect_uri',
},
{
label: 'LOGOUT REDIRECT URI',
value: window.env.REACT_APP_LOGOUT_REDIRECT_URI,
key: 'logout_redirect_uri',
},
{
label: 'GITLAB SCOPES',
value: window.env.REACT_APP_GITLAB_SCOPES,
key: 'gitlab_scopes',
},
];

return (
<Paper
sx={{
p: 2,
height: '100%',
display: 'flex',
flexDirection: 'column',
position: 'relative',
}}
>
<Typography variant="h4">Configuration Verification</Typography>
<div>
{configItems.map(({ label, value, key }) => (
<ConfigItem
key={key}
label={label}
value={value}
validation={validationResults[key]}
/>
))}
</div>
</Paper>
);
};

export default VerifyConfig;
9 changes: 9 additions & 0 deletions client/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DigitalTwins from './route/digitaltwins/DigitalTwins';
import DigitalTwinsPreview from './preview/route/digitaltwins/DigitalTwinsPreview';
import SignIn from './route/auth/Signin';
import Account from './route/auth/Account';
import VerifyConfig from './route/auth/VerifyConfig';

export const routes = [
{
Expand All @@ -17,6 +18,14 @@ export const routes = [
</LayoutPublic>
),
},
{
path: 'verify',
element: (
<LayoutPublic containerMaxWidth="md">
<VerifyConfig />
</LayoutPublic>
),
},
{
path: 'library',
element: (
Expand Down
Loading