notification context and attack page
This commit is contained in:
parent
0afbdd97d9
commit
a838742882
16
client/package-lock.json
generated
16
client/package-lock.json
generated
|
@ -22,7 +22,8 @@
|
|||
"jotai": "^2.10.1",
|
||||
"next": "^15.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-dom": "^18.3.1",
|
||||
"uuid": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.1.0",
|
||||
|
@ -5553,6 +5554,19 @@
|
|||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
|
||||
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
"jotai": "^2.10.1",
|
||||
"next": "^15.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-dom": "^18.3.1",
|
||||
"uuid": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.1.0",
|
||||
|
|
|
@ -18,7 +18,7 @@ export const NavigationButtons = () => {
|
|||
};
|
||||
|
||||
const goHome = () => {
|
||||
setRoute("index");
|
||||
setRoute("home");
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -29,15 +29,27 @@ export const NavigationButtons = () => {
|
|||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<IconButton onClick={goBack} size="small">
|
||||
<ArrowBack fontSize="inherit" />
|
||||
<Stack
|
||||
direction='row'
|
||||
spacing={1}
|
||||
>
|
||||
<IconButton
|
||||
onClick={goBack}
|
||||
size='small'
|
||||
>
|
||||
<ArrowBack fontSize='inherit' />
|
||||
</IconButton>
|
||||
<IconButton onClick={goForward} size="small">
|
||||
<ArrowForward fontSize="inherit" />
|
||||
<IconButton
|
||||
onClick={goForward}
|
||||
size='small'
|
||||
>
|
||||
<ArrowForward fontSize='inherit' />
|
||||
</IconButton>
|
||||
<IconButton onClick={goHome} size="small">
|
||||
<HomeOutlined fontSize="inherit" />
|
||||
<IconButton
|
||||
onClick={goHome}
|
||||
size='small'
|
||||
>
|
||||
<HomeOutlined fontSize='inherit' />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
|
|
@ -1,5 +1,37 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { routeAtom } from "../../lib/jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const RouteDisplay = () => {
|
||||
return <Box>Home</Box>;
|
||||
// atoms
|
||||
const [route, setRoute] = useAtom(routeAtom);
|
||||
|
||||
// states
|
||||
const [routeName, setRouteName] = useState("Home");
|
||||
|
||||
// update the route name based on the route value
|
||||
useEffect(() => {
|
||||
switch (route) {
|
||||
case "home":
|
||||
setRouteName("Home");
|
||||
break;
|
||||
case "sql_injection":
|
||||
setRouteName("SQL Injection");
|
||||
break;
|
||||
case "xss":
|
||||
setRouteName("Cross-site Scripting");
|
||||
break;
|
||||
case "cmd_injection":
|
||||
setRouteName("Command Injection");
|
||||
break;
|
||||
case "testing":
|
||||
setRouteName("Testing");
|
||||
break;
|
||||
default:
|
||||
setRouteName("Home");
|
||||
}
|
||||
}, [route]);
|
||||
|
||||
return <Box>{routeName}</Box>;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ import { MouseEvent, useEffect, useState } from "react";
|
|||
import { serverConnectionAtom, serverUrlAtom } from "../../lib/jotai";
|
||||
import { defaultSettings } from "../../lib/settings";
|
||||
import { ServerUrlInput } from "./ServerUrlInput";
|
||||
import { sleep } from "../../lib/utility";
|
||||
|
||||
export const ServerStatus = () => {
|
||||
// atoms
|
||||
|
@ -24,6 +25,8 @@ export const ServerStatus = () => {
|
|||
|
||||
// function to check server health
|
||||
const checkServerConnection = async () => {
|
||||
setServerConnection("connecting");
|
||||
|
||||
// remove trailing slash
|
||||
setServerUrl(serverUrl.replace(/\/$/, ""));
|
||||
|
||||
|
@ -34,10 +37,12 @@ export const ServerStatus = () => {
|
|||
setServerConnection("connected");
|
||||
} else {
|
||||
console.log("disconnected");
|
||||
await sleep(500);
|
||||
setServerConnection("disconnected");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("disconnected", e);
|
||||
await sleep(500);
|
||||
setServerConnection("disconnected");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import {
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { FC } from "react";
|
||||
|
||||
type AttackItemProps = {
|
||||
attackName: string;
|
||||
};
|
||||
|
||||
export const AttackItem: FC<AttackItemProps> = ({ attackName }) => {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
variant="outlined"
|
||||
>
|
||||
<CardActionArea
|
||||
onClick={() => {
|
||||
console.log("clicked");
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography component="div">{attackName}</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
};
|
|
@ -1,89 +0,0 @@
|
|||
import { Box, Button, Container, Grid2, Switch, TextField, Typography, useTheme } from "@mui/material";
|
||||
|
||||
import { HeaderBar } from "../HeaderBar/HeaderBar";
|
||||
import { AttackItem } from "./AttackItem";
|
||||
import { ServerUrlInput } from "../HeaderBar/ServerUrlInput";
|
||||
|
||||
export const Layout = () => {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<HeaderBar />
|
||||
<Container
|
||||
maxWidth='lg'
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
{/* main content goes here buddy */}
|
||||
<Box
|
||||
sx={{
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
mb: 2,
|
||||
}}
|
||||
variant='h3'
|
||||
>
|
||||
CSPJ Application Attack Simulator
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button href='https://github.com/cspj-nyp/cspj-application'>Need help getting started?</Button>
|
||||
</Box>
|
||||
<Grid2
|
||||
container
|
||||
spacing={2}
|
||||
>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='SQL Injection' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='Cross Site Scripting' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='Command Injection' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='File Inclusion Attacks' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='CSRF' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='Directory Traversal' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='Insecure Desrialization' />
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem attackName='Session Hijacking' />
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
37
client/src/components/Pages/Home/AttackItem.tsx
Normal file
37
client/src/components/Pages/Home/AttackItem.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Card, CardActionArea, CardContent, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { routeAtom, Routes } from "../../../lib/jotai";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
type AttackItemProps = {
|
||||
attackName: string;
|
||||
routeTarget: Routes;
|
||||
};
|
||||
|
||||
export const AttackItem: FC<AttackItemProps> = ({ attackName, routeTarget }) => {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
|
||||
// atoms
|
||||
const [route, setRoute] = useAtom(routeAtom);
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
variant='outlined'
|
||||
>
|
||||
<CardActionArea
|
||||
onClick={() => {
|
||||
setRoute(routeTarget);
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography component='div'>{attackName}</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
};
|
96
client/src/components/Pages/Home/Home.tsx
Normal file
96
client/src/components/Pages/Home/Home.tsx
Normal file
|
@ -0,0 +1,96 @@
|
|||
import { Box, Typography, Button, Grid2 } from "@mui/material";
|
||||
import { AttackItem } from "./AttackItem";
|
||||
|
||||
export const Home = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
mb: 2,
|
||||
}}
|
||||
variant='h3'
|
||||
>
|
||||
CSPJ Application Attack Simulator
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button href='https://github.com/cspj-nyp/cspj-application'>Need help getting started?</Button>
|
||||
</Box>
|
||||
<Grid2
|
||||
container
|
||||
spacing={2}
|
||||
>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='SQL Injection'
|
||||
routeTarget='sql_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='Cross Site Scripting'
|
||||
routeTarget='xss'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='Command Injection'
|
||||
routeTarget='cmd_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='File Inclusion Attacks'
|
||||
routeTarget='cmd_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='CSRF'
|
||||
routeTarget='cmd_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='Directory Traversal'
|
||||
routeTarget='cmd_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='Insecure Desrialization'
|
||||
routeTarget='cmd_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='Session Hijacking'
|
||||
routeTarget='cmd_injection'
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 size={3}>
|
||||
<AttackItem
|
||||
attackName='Testing page'
|
||||
routeTarget='testing'
|
||||
/>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Box>
|
||||
);
|
||||
};
|
15
client/src/components/Pages/Shared/AttackPage.tsx
Normal file
15
client/src/components/Pages/Shared/AttackPage.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
export const AttackPage = ({}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "start",
|
||||
}}
|
||||
>
|
||||
asdf
|
||||
</Box>
|
||||
);
|
||||
};
|
21
client/src/components/Pages/SqlInjection/SqlInjection.tsx
Normal file
21
client/src/components/Pages/SqlInjection/SqlInjection.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Box, Button } from "@mui/material";
|
||||
import { useNotification } from "../../../contexts/NotificationContext";
|
||||
|
||||
export const SqlInjection = () => {
|
||||
// contexts
|
||||
const { openNotification } = useNotification();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<h1>SQL Injection</h1>
|
||||
<p>SQL Injection is a code injection technique</p>
|
||||
<Button
|
||||
onClick={() => {
|
||||
openNotification("deez nuts", "", 3000);
|
||||
}}
|
||||
>
|
||||
clicky
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
10
client/src/components/Pages/Xss/Xss.tsx
Normal file
10
client/src/components/Pages/Xss/Xss.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
export const Xss = () => {
|
||||
return (
|
||||
<Box>
|
||||
<h1>XSS</h1>
|
||||
<p>Cross-site scripting (XSS) is a type of security vulnerability</p>
|
||||
</Box>
|
||||
);
|
||||
};
|
67
client/src/contexts/NotificationContext.tsx
Normal file
67
client/src/contexts/NotificationContext.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { Alert, Snackbar, useTheme } from "@mui/material";
|
||||
import { FC, ReactNode, createContext, useContext, useState } from "react";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
type Notification = {
|
||||
id: string;
|
||||
message: string;
|
||||
color?: string;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
type NotificationContextProps = {
|
||||
notifications: Notification[];
|
||||
openNotification: (message: string, color?: string, duration?: number) => void;
|
||||
closeNotification: (id: string) => void;
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextProps | undefined>(undefined);
|
||||
|
||||
export const NotificationProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
|
||||
const openNotification = (message: string, color?: string, duration: number = 3000) => {
|
||||
const id = v4();
|
||||
setNotifications((prevNotifications) => [...prevNotifications, { id, message, color, duration }]);
|
||||
|
||||
setTimeout(() => {
|
||||
closeNotification(id);
|
||||
}, duration);
|
||||
};
|
||||
|
||||
const closeNotification = (id: string) => {
|
||||
setNotifications((prevNotifications) => prevNotifications.filter((notification) => notification.id !== id));
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={{ notifications, openNotification, closeNotification }}>
|
||||
{children}
|
||||
{notifications.map((notification) => (
|
||||
<Snackbar
|
||||
key={notification.id}
|
||||
autoHideDuration={notification.duration}
|
||||
open={true}
|
||||
>
|
||||
<Alert
|
||||
icon={false}
|
||||
variant='filled'
|
||||
sx={{
|
||||
backgroundColor: notification.color || theme.palette.primary.main,
|
||||
}}
|
||||
>
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
))}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useNotification = () => {
|
||||
const context = useContext(NotificationContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useNotification must be used within a NotificationProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
|
@ -4,7 +4,10 @@ import { atom } from "jotai";
|
|||
// store which page the user is currently on
|
||||
// no actual routing is done here,
|
||||
// full page components are used render the different pages
|
||||
export const routeAtom = atom("index");
|
||||
// TODO: incomplete, to be updated as more pages are added
|
||||
// TODO: also remove testing when done
|
||||
export type Routes = "home" | "sql_injection" | "xss" | "cmd_injection" | "testing";
|
||||
export const routeAtom = atom<Routes>("home");
|
||||
|
||||
// store the status of connection to backend
|
||||
type ServerConnection = "connected" | "connecting" | "disconnected";
|
||||
|
|
3
client/src/lib/utility.ts
Normal file
3
client/src/lib/utility.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const sleep = async (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
|
@ -5,6 +5,7 @@ import Head from "next/head";
|
|||
import { UserThemeProvider } from "../contexts/ThemeContext";
|
||||
import createEmotionCache from "../lib/createEmotionCache";
|
||||
import "../styles/global.css";
|
||||
import { NotificationProvider } from "../contexts/NotificationContext";
|
||||
|
||||
// Client-side cache, shared for the whole session of the user in the browser.
|
||||
const clientSideEmotionCache = createEmotionCache();
|
||||
|
@ -19,12 +20,17 @@ export default function MyApp(props: MyAppProps) {
|
|||
<CacheProvider value={emotionCache}>
|
||||
<Head>
|
||||
<title>CSPJ Application</title>
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
<meta
|
||||
name='viewport'
|
||||
content='initial-scale=1, width=device-width'
|
||||
/>
|
||||
</Head>
|
||||
<UserThemeProvider>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
<NotificationProvider>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
</NotificationProvider>
|
||||
</UserThemeProvider>
|
||||
</CacheProvider>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,59 @@
|
|||
import { Layout } from "../components/Home/Layout";
|
||||
import { Box, Container, useTheme } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { HeaderBar } from "../components/HeaderBar/HeaderBar";
|
||||
import { Home } from "../components/Pages/Home/Home";
|
||||
import { SqlInjection } from "../components/Pages/SqlInjection/SqlInjection";
|
||||
import { Xss } from "../components/Pages/Xss/Xss";
|
||||
import { routeAtom } from "../lib/jotai";
|
||||
import { AttackPage } from "../components/Pages/Shared/AttackPage";
|
||||
|
||||
export default function Home() {
|
||||
return <Layout />;
|
||||
export default function Index() {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
|
||||
// atoms
|
||||
const [route, setRoute] = useAtom(routeAtom);
|
||||
|
||||
// render different components based on the route value
|
||||
const renderComponent = () => {
|
||||
switch (route) {
|
||||
case "home":
|
||||
return <Home />;
|
||||
case "sql_injection":
|
||||
return <SqlInjection />;
|
||||
case "xss":
|
||||
return <Xss />;
|
||||
// TODO: remove testing when done
|
||||
case "testing":
|
||||
return <AttackPage />;
|
||||
default:
|
||||
return <Home />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<HeaderBar />
|
||||
<Container
|
||||
maxWidth='lg'
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
{/* main content goes here buddy */}
|
||||
{renderComponent()}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue