From a8387428826663d65c327b3469d05c65c6b52a63 Mon Sep 17 00:00:00 2001 From: Vomitblood Date: Tue, 12 Nov 2024 17:47:26 +0800 Subject: [PATCH] notification context and attack page --- client/package-lock.json | 16 +++- client/package.json | 3 +- .../HeaderBar/NavigationButtons.tsx | 28 ++++-- .../src/components/HeaderBar/RouteDisplay.tsx | 34 ++++++- .../src/components/HeaderBar/ServerStatus.tsx | 5 + client/src/components/Home/AttackItem.tsx | 37 ------- client/src/components/Home/Layout.tsx | 89 ----------------- .../src/components/Pages/Home/AttackItem.tsx | 37 +++++++ client/src/components/Pages/Home/Home.tsx | 96 +++++++++++++++++++ .../components/Pages/Shared/AttackPage.tsx | 15 +++ .../Pages/SqlInjection/SqlInjection.tsx | 21 ++++ client/src/components/Pages/Xss/Xss.tsx | 10 ++ client/src/contexts/NotificationContext.tsx | 67 +++++++++++++ client/src/lib/jotai.ts | 5 +- client/src/lib/utility.ts | 3 + client/src/pages/_app.tsx | 14 ++- client/src/pages/index.tsx | 60 +++++++++++- 17 files changed, 395 insertions(+), 145 deletions(-) delete mode 100644 client/src/components/Home/AttackItem.tsx delete mode 100644 client/src/components/Home/Layout.tsx create mode 100644 client/src/components/Pages/Home/AttackItem.tsx create mode 100644 client/src/components/Pages/Home/Home.tsx create mode 100644 client/src/components/Pages/Shared/AttackPage.tsx create mode 100644 client/src/components/Pages/SqlInjection/SqlInjection.tsx create mode 100644 client/src/components/Pages/Xss/Xss.tsx create mode 100644 client/src/contexts/NotificationContext.tsx create mode 100644 client/src/lib/utility.ts diff --git a/client/package-lock.json b/client/package-lock.json index 2b30cd5..fc04a14 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -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", diff --git a/client/package.json b/client/package.json index 7382f51..b672169 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/src/components/HeaderBar/NavigationButtons.tsx b/client/src/components/HeaderBar/NavigationButtons.tsx index 80e54c5..fd2528e 100644 --- a/client/src/components/HeaderBar/NavigationButtons.tsx +++ b/client/src/components/HeaderBar/NavigationButtons.tsx @@ -18,7 +18,7 @@ export const NavigationButtons = () => { }; const goHome = () => { - setRoute("index"); + setRoute("home"); }; return ( @@ -29,15 +29,27 @@ export const NavigationButtons = () => { flexDirection: "row", }} > - - - + + + - - + + - - + + diff --git a/client/src/components/HeaderBar/RouteDisplay.tsx b/client/src/components/HeaderBar/RouteDisplay.tsx index d961954..9d14291 100644 --- a/client/src/components/HeaderBar/RouteDisplay.tsx +++ b/client/src/components/HeaderBar/RouteDisplay.tsx @@ -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 Home; + // 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 {routeName}; }; diff --git a/client/src/components/HeaderBar/ServerStatus.tsx b/client/src/components/HeaderBar/ServerStatus.tsx index 78f9c74..3a2a44c 100644 --- a/client/src/components/HeaderBar/ServerStatus.tsx +++ b/client/src/components/HeaderBar/ServerStatus.tsx @@ -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"); } }; diff --git a/client/src/components/Home/AttackItem.tsx b/client/src/components/Home/AttackItem.tsx deleted file mode 100644 index 51b1500..0000000 --- a/client/src/components/Home/AttackItem.tsx +++ /dev/null @@ -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 = ({ attackName }) => { - // contexts - const theme = useTheme(); - - return ( - - { - console.log("clicked"); - }} - > - - {attackName} - - - - ); -}; diff --git a/client/src/components/Home/Layout.tsx b/client/src/components/Home/Layout.tsx deleted file mode 100644 index c408ecc..0000000 --- a/client/src/components/Home/Layout.tsx +++ /dev/null @@ -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 ( - - - - {/* main content goes here buddy */} - - - - CSPJ Application Attack Simulator - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/client/src/components/Pages/Home/AttackItem.tsx b/client/src/components/Pages/Home/AttackItem.tsx new file mode 100644 index 0000000..31773a8 --- /dev/null +++ b/client/src/components/Pages/Home/AttackItem.tsx @@ -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 = ({ attackName, routeTarget }) => { + // contexts + const theme = useTheme(); + + // atoms + const [route, setRoute] = useAtom(routeAtom); + + return ( + + { + setRoute(routeTarget); + }} + > + + {attackName} + + + + ); +}; diff --git a/client/src/components/Pages/Home/Home.tsx b/client/src/components/Pages/Home/Home.tsx new file mode 100644 index 0000000..dbd0290 --- /dev/null +++ b/client/src/components/Pages/Home/Home.tsx @@ -0,0 +1,96 @@ +import { Box, Typography, Button, Grid2 } from "@mui/material"; +import { AttackItem } from "./AttackItem"; + +export const Home = () => { + return ( + + + + + CSPJ Application Attack Simulator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/client/src/components/Pages/Shared/AttackPage.tsx b/client/src/components/Pages/Shared/AttackPage.tsx new file mode 100644 index 0000000..054d762 --- /dev/null +++ b/client/src/components/Pages/Shared/AttackPage.tsx @@ -0,0 +1,15 @@ +import { Box } from "@mui/material"; + +export const AttackPage = ({}) => { + return ( + + asdf + + ); +}; diff --git a/client/src/components/Pages/SqlInjection/SqlInjection.tsx b/client/src/components/Pages/SqlInjection/SqlInjection.tsx new file mode 100644 index 0000000..f871058 --- /dev/null +++ b/client/src/components/Pages/SqlInjection/SqlInjection.tsx @@ -0,0 +1,21 @@ +import { Box, Button } from "@mui/material"; +import { useNotification } from "../../../contexts/NotificationContext"; + +export const SqlInjection = () => { + // contexts + const { openNotification } = useNotification(); + + return ( + +

SQL Injection

+

SQL Injection is a code injection technique

+ +
+ ); +}; diff --git a/client/src/components/Pages/Xss/Xss.tsx b/client/src/components/Pages/Xss/Xss.tsx new file mode 100644 index 0000000..7025b48 --- /dev/null +++ b/client/src/components/Pages/Xss/Xss.tsx @@ -0,0 +1,10 @@ +import { Box } from "@mui/material"; + +export const Xss = () => { + return ( + +

XSS

+

Cross-site scripting (XSS) is a type of security vulnerability

+
+ ); +}; diff --git a/client/src/contexts/NotificationContext.tsx b/client/src/contexts/NotificationContext.tsx new file mode 100644 index 0000000..71ca678 --- /dev/null +++ b/client/src/contexts/NotificationContext.tsx @@ -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(undefined); + +export const NotificationProvider: FC<{ children: ReactNode }> = ({ children }) => { + const theme = useTheme(); + const [notifications, setNotifications] = useState([]); + + 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 ( + + {children} + {notifications.map((notification) => ( + + + {notification.message} + + + ))} + + ); +}; + +export const useNotification = () => { + const context = useContext(NotificationContext); + if (context === undefined) { + throw new Error("useNotification must be used within a NotificationProvider"); + } + return context; +}; diff --git a/client/src/lib/jotai.ts b/client/src/lib/jotai.ts index 9e348c6..44a2fb6 100644 --- a/client/src/lib/jotai.ts +++ b/client/src/lib/jotai.ts @@ -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("home"); // store the status of connection to backend type ServerConnection = "connected" | "connecting" | "disconnected"; diff --git a/client/src/lib/utility.ts b/client/src/lib/utility.ts new file mode 100644 index 0000000..d75f537 --- /dev/null +++ b/client/src/lib/utility.ts @@ -0,0 +1,3 @@ +export const sleep = async (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx index a688f32..2714b8a 100644 --- a/client/src/pages/_app.tsx +++ b/client/src/pages/_app.tsx @@ -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) { CSPJ Application - + - {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} - - + + {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} + + + ); diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx index 140e3b3..a651a8c 100644 --- a/client/src/pages/index.tsx +++ b/client/src/pages/index.tsx @@ -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 ; +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 ; + case "sql_injection": + return ; + case "xss": + return ; + // TODO: remove testing when done + case "testing": + return ; + default: + return ; + } + }; + + return ( + + + + {/* main content goes here buddy */} + {renderComponent()} + + + ); }