Skip to content

Commit 6e761fe

Browse files
authored
Merge pull request #287 from Tauffer-Consulting/dev
Dev
2 parents 1db8614 + 6a5a967 commit 6e761fe

File tree

16 files changed

+310
-118
lines changed

16 files changed

+310
-118
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ pyvenv.cfg
183183
pip-selfcheck.json
184184

185185
### VisualStudioCode ###
186-
.vscode/*
186+
**/.vscode/*
187187

188188
### VisualStudioCode Patch ###
189189
# Ignore all local history of files
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useWorkspaces } from "@context/workspaces";
2+
import ReportProblemIcon from "@mui/icons-material/ReportProblem";
3+
import { Box, Button, type SxProps, type Theme, Tooltip } from "@mui/material";
4+
import { type Roles } from "@utils/roles";
5+
import React, { type ReactNode } from "react";
6+
7+
interface IProps {
8+
children?: ReactNode;
9+
allowedRoles: Roles[];
10+
sx?: SxProps<Theme>;
11+
}
12+
13+
export const AuthorizationComponent: React.FC<IProps> = ({
14+
allowedRoles,
15+
children,
16+
sx,
17+
}) => {
18+
const { workspace } = useWorkspaces();
19+
20+
const authorized = allowedRoles.some(
21+
(item) => workspace?.user_permission === item,
22+
);
23+
24+
return authorized ? (
25+
<>{children}</>
26+
) : React.isValidElement(children) && children.type === Button ? (
27+
<Tooltip
28+
title="You don't have enough permissions to access this feature"
29+
sx={sx}
30+
>
31+
<span>
32+
<Button disabled variant="contained">
33+
<ReportProblemIcon />
34+
Unauthorized
35+
</Button>
36+
</span>
37+
</Tooltip>
38+
) : (
39+
<Box
40+
sx={{
41+
height: "100%",
42+
width: "100%",
43+
display: "flex",
44+
alignItems: "center",
45+
justifyContent: "center",
46+
...sx,
47+
}}
48+
>
49+
<Tooltip title="You don't have enough permissions to access this feature">
50+
<ReportProblemIcon fontSize="large" color="warning" />
51+
</Tooltip>
52+
</Box>
53+
);
54+
};

frontend/src/components/PrivateLayout/index.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Box, Container } from "@mui/material";
22
import { type FC, type ReactNode } from "react";
33

4+
import { DrawerHeader } from "./header/drawerMenu.style";
45
import { Header } from "./header/header";
56

67
interface Props {
@@ -9,10 +10,16 @@ interface Props {
910

1011
export const PrivateLayout: FC<Props> = ({ children }) => {
1112
return (
12-
<Box sx={{ display: "flex", width: "100%", marginTop: "64px" }}>
13+
<Box sx={{ display: "flex" }}>
1314
<Header />
1415

15-
<Container component="main" maxWidth={false} sx={{ padding: 3 }}>
16+
<Container
17+
component="main"
18+
maxWidth={false}
19+
sx={{ padding: 3, overflow: "auto" }}
20+
>
21+
<DrawerHeader />
22+
1623
<Box sx={{ paddingLeft: 0 }}>{children}</Box>
1724
</Container>
1825
</Box>

frontend/src/components/RoleComponent/index.tsx

Lines changed: 0 additions & 21 deletions
This file was deleted.

frontend/src/components/Routes/AuthorizationRoute/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Props = {
2424
* | requireWorkspace | allowedRoles.length | workspace | Result |
2525
* |------------------|---------------------|-----------|---------------------------|
2626
* | false | 0 | null | Render children |
27-
* | false | 0 | not null | Redirect to unauthorized |
27+
* | false | 0 | not null | Redirect to forbidden |
2828
* | false | > 0 | null | Redirect to workspaces |
2929
* | false | > 0 | not null | Check permission |
3030
* | true | 0 | null | Redirect to workspaces |
@@ -68,10 +68,10 @@ export const AuthorizationRoute = ({
6868
return hasPermission ? (
6969
<>{children}</>
7070
) : (
71-
<Navigate to="/unauthorized" replace />
71+
<Navigate to="/forbidden" replace />
7272
);
7373
}
7474

75-
// Redirect to unauthorized if no other conditions met
76-
return <Navigate to="/unauthorized" replace />;
75+
// Redirect to forbidden if no other conditions met
76+
return <Navigate to="/forbidden" replace />;
7777
};
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import PrivateLayout from "@components/PrivateLayout";
2+
import { useAuthentication } from "@context/authentication";
3+
import {
4+
Button,
5+
Card,
6+
CardContent,
7+
CardHeader,
8+
Divider,
9+
Typography,
10+
} from "@mui/material";
11+
import React from "react";
12+
import { type NavigateFunction, useNavigate } from "react-router-dom";
13+
14+
export const ForbiddenPage: React.FC = () => {
15+
const { isLogged } = useAuthentication();
16+
const navigate = useNavigate();
17+
18+
return (
19+
<>
20+
{isLogged ? (
21+
<PrivateLayout>
22+
<ForbiddenCard navigate={navigate} />
23+
</PrivateLayout>
24+
) : (
25+
<ForbiddenCard navigate={navigate} />
26+
)}
27+
</>
28+
);
29+
};
30+
31+
const ForbiddenCard: React.FC<{ navigate: NavigateFunction }> = ({
32+
navigate,
33+
}) => (
34+
<Card
35+
sx={{
36+
maxWidth: 400,
37+
margin: "auto",
38+
marginTop: 4,
39+
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
40+
}}
41+
>
42+
<CardHeader
43+
title="Forbidden"
44+
sx={{
45+
backgroundColor: (theme) => theme.palette.error.main,
46+
color: "white",
47+
textAlign: "center",
48+
}}
49+
/>
50+
<Divider />
51+
<CardContent
52+
sx={{
53+
display: "flex",
54+
flexDirection: "column",
55+
alignItems: "center",
56+
justifyContent: "center",
57+
minHeight: "50vh",
58+
}}
59+
>
60+
<Typography variant="body1" align="center">
61+
You are not authorized to access this page.
62+
</Typography>
63+
<Button
64+
onClick={() => {
65+
navigate(-1);
66+
}}
67+
variant="outlined"
68+
sx={{ marginTop: 2 }}
69+
>
70+
<Typography component="span">{`< Go back `}</Typography>
71+
</Button>
72+
</CardContent>
73+
</Card>
74+
);

frontend/src/features/auth/routes/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ForbiddenPage } from "@components/Routes/ForbiddenPage";
12
import { NotFoundRoute } from "@components/Routes/NotFoundRoute";
23
import { PublicRoute } from "@components/Routes/PublicRoute";
34
import React from "react";
@@ -14,6 +15,7 @@ export const AuthRoutes: React.FC = () => {
1415
<Route path="sign-up" element={<SignUpPage />} />
1516
<Route path="recover-password" element={<h1>Recover password</h1>} />
1617
<Route path="404" element={<NotFoundRoute />} />,
18+
<Route path="forbidden" element={<ForbiddenPage />} />
1719
<Route
1820
path="*"
1921
element={

frontend/src/features/myWorkflows/components/WorkflowDetail/WorkflowRunTableFooter/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AuthorizationComponent } from "@components/AuthorizationComponent";
12
import RefreshIcon from "@mui/icons-material/Refresh";
23
import { Button, Grid } from "@mui/material";
34
import {
@@ -38,9 +39,13 @@ export const WorkflowRunTableFooter = React.forwardRef<HTMLDivElement, Props>(
3839
sx={{ height: "100%" }}
3940
>
4041
<Grid item sx={{ paddingLeft: "1rem" }}>
41-
<Button variant="contained" onClick={triggerRun}>
42-
Run
43-
</Button>
42+
<AuthorizationComponent
43+
allowedRoles={["owner", "admin", "write"]}
44+
>
45+
<Button variant="contained" onClick={triggerRun}>
46+
Run
47+
</Button>
48+
</AuthorizationComponent>
4449
</Grid>
4550
<Grid item sx={{ paddingLeft: "1rem" }}>
4651
<Button

frontend/src/features/workflowEditor/components/ButtonsMenu/index.tsx

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AuthorizationComponent } from "@components/AuthorizationComponent";
12
import ClearIcon from "@mui/icons-material/Clear";
23
import DownloadIcon from "@mui/icons-material/Download";
34
import IosShareIcon from "@mui/icons-material/IosShare";
@@ -121,55 +122,63 @@ export const ButtonsMenu: React.FC<Props> = ({
121122
style={{ marginBottom: 10 }}
122123
>
123124
<Grid item>
124-
<Button
125-
color="primary"
126-
variant="contained"
127-
startIcon={<SettingsSuggestIcon />}
128-
onClick={handleSettings}
129-
>
130-
Settings
131-
</Button>
125+
<AuthorizationComponent allowedRoles={["admin", "owner", "write"]}>
126+
<Button
127+
color="primary"
128+
variant="contained"
129+
startIcon={<SettingsSuggestIcon />}
130+
onClick={handleSettings}
131+
>
132+
Settings
133+
</Button>
134+
</AuthorizationComponent>
132135
</Grid>
133136
<Grid item>
134-
<Button
135-
color="primary"
136-
variant="contained"
137-
startIcon={<SaveIcon />}
138-
onClick={handleSave}
139-
>
140-
Create
141-
</Button>
137+
<AuthorizationComponent allowedRoles={["admin", "owner", "write"]}>
138+
<Button
139+
color="primary"
140+
variant="contained"
141+
startIcon={<SaveIcon />}
142+
onClick={handleSave}
143+
>
144+
Create
145+
</Button>
146+
</AuthorizationComponent>
142147
</Grid>
143148
<Grid item>
144-
<Button
145-
color="primary"
146-
variant="contained"
147-
startIcon={<IosShareIcon />}
148-
onClick={handleExport}
149-
>
150-
Export
151-
</Button>
149+
<AuthorizationComponent allowedRoles={["admin", "owner", "write"]}>
150+
<Button
151+
color="primary"
152+
variant="contained"
153+
startIcon={<IosShareIcon />}
154+
onClick={handleExport}
155+
>
156+
Export
157+
</Button>
158+
</AuthorizationComponent>
152159
</Grid>
153160
<Grid item>
154-
<Button
155-
variant="contained"
156-
startIcon={<DownloadIcon />}
157-
id="import-button"
158-
aria-controls={importMenuOpen ? "import-menu" : undefined}
159-
aria-haspopup="true"
160-
aria-expanded={importMenuOpen ? "true" : undefined}
161-
onClick={handleClickImportMenu}
162-
>
163-
<VisuallyHiddenInput
164-
type="file"
165-
onChange={async (e) => {
166-
const json = await importJsonWorkflow(e);
167-
handleImportedJson(json);
168-
}}
169-
ref={fileInputRef}
170-
/>
171-
Import
172-
</Button>
161+
<AuthorizationComponent allowedRoles={["admin", "owner", "write"]}>
162+
<Button
163+
variant="contained"
164+
startIcon={<DownloadIcon />}
165+
id="import-button"
166+
aria-controls={importMenuOpen ? "import-menu" : undefined}
167+
aria-haspopup="true"
168+
aria-expanded={importMenuOpen ? "true" : undefined}
169+
onClick={handleClickImportMenu}
170+
>
171+
<VisuallyHiddenInput
172+
type="file"
173+
onChange={async (e) => {
174+
const json = await importJsonWorkflow(e);
175+
handleImportedJson(json);
176+
}}
177+
ref={fileInputRef}
178+
/>
179+
Import
180+
</Button>
181+
</AuthorizationComponent>
173182
<Menu
174183
id="import-menu"
175184
anchorEl={menuElement}
@@ -207,14 +216,16 @@ export const ButtonsMenu: React.FC<Props> = ({
207216
/>
208217
</Grid>
209218
<Grid item>
210-
<Button
211-
color="primary"
212-
variant="contained"
213-
startIcon={<ClearIcon />}
214-
onClick={handleClear}
215-
>
216-
Clear
217-
</Button>
219+
<AuthorizationComponent allowedRoles={["admin", "owner", "write"]}>
220+
<Button
221+
color="primary"
222+
variant="contained"
223+
startIcon={<ClearIcon />}
224+
onClick={handleClear}
225+
>
226+
Clear
227+
</Button>
228+
</AuthorizationComponent>
218229
</Grid>
219230
</Grid>
220231
);

0 commit comments

Comments
 (0)