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

Validate form fields #112

Merged
merged 4 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
339 changes: 169 additions & 170 deletions frontend/src/app/bounty/[bountyId]/exploit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import {
Paper,
Tabs,
} from "@mantine/core";
import { FileWithPath } from "@mantine/dropzone";
import { isNotEmpty, useForm } from "@mantine/form";

import { useWaitForTransaction } from "wagmi";
import { SendExploit } from "../../../../model/inputs";
import { TestExploit } from "../../../../model/inputs";
import { usePrepareSendExploit } from "../../../../hooks/bug-buster";
import { useInputBoxAddInput } from "../../../../hooks/contracts";

import { BountyParams } from "../utils.tsx";
import { BountyParams, ConcreteBountyParams } from "../utils.tsx";
import { useBounty } from "../../../../model/reader";
import { FileDrop } from "../../../../components/filedrop";

Expand All @@ -44,214 +44,213 @@ const FileDropText: FC<FileDropTextParams> = ({ filename }) => {
}
};

const SendExploitPage: FC<BountyParams> = ({ params: { bountyId } }) => {
const theme = useMantineTheme();
interface SendExploitFormValues {
name: string;
imgLink?: string;
exploit: string;
}

const [name, setName] = useState("");
const [imgLink, setImgLink] = useState("");
const [testStatus, setTestStatus] = useState("");
const [exploitFile, setExploitFile] = useState<string | null>(null);
const [filename, setFilename] = useState<string | undefined>();
const SendExploitForm: FC<ConcreteBountyParams> = ({ bountyIndex, bounty }) => {
const [filename, setFilename] = useState<string>();

const readFile = (f: FileWithPath | null) => {
if (f) {
f.arrayBuffer().then((buf) => {
//setAppFile(bytesToHex(new Uint8Array(buf)));
setExploitFile(
btoa(
Array.from(new Uint8Array(buf))
.map((b) => String.fromCharCode(b))
.join(""),
),
);
setFilename(f.name);
});
}
};
const form = useForm<SendExploitFormValues>({
initialValues: {
name: "",
exploit: "",
},
validate: {
name: isNotEmpty("An exploiter name is required"),
},
});

const { name, imgLink, exploit } = form.values;

// Test an exploit
async function TestExploit(bountyIndex: number, exploit: string) {
const json = { bountyIndex, exploit };
setTestStatus("Testing..");
let buffer = Buffer.from(JSON.stringify(json));
const [exploitOutput, setExploitOutput] = useState("");

async function testExploitAsync(bountyIndex: number, exploit: string) {
const inspectRequest: TestExploit = { bountyIndex, exploit };
setExploitOutput("Testing...");

const INSPECT_URL = process.env.NEXT_PUBLIC_INSPECT_URL;
if (INSPECT_URL === undefined || INSPECT_URL == "")
throw new Error("An inspect URL is required");
if (INSPECT_URL === undefined || INSPECT_URL == "") {
setExploitOutput("Inspect URL is not set");
return;
}

const response = await fetch(INSPECT_URL, {
method: "POST",
headers: {
"Content-Type": "application/octet-stream",
},
body: buffer,
body: Buffer.from(JSON.stringify(inspectRequest)),
});

const body = await response.json();
if (body.status == "Accepted") {
setTestStatus("Exploit test succeeded!");
setExploitOutput("Exploit test succeeded!");
} else {
let status = "";
body.reports.forEach((report: any) => {
const text = Buffer.from(
report.payload.substring(2),
"hex",
).toString("ascii");
status = status + text;
});
setTestStatus(status);
// prettier-ignore
setExploitOutput(body.reports
.map((report: { payload: string }) => Buffer.from(report.payload.substring(2), "hex").toString("ascii"))
.reduce((text: string, acc: string) => text + acc));
}
}

const bountyIndex = Number(bountyId);

const testExploit = () => {
TestExploit(bountyIndex, exploitFile!);
testExploitAsync(bountyIndex, exploit);
};

const exploit = {
const config = usePrepareSendExploit({
name,
imgLink,
bountyIndex,
exploit: exploitFile,
} as SendExploit;

const config = usePrepareSendExploit(exploit);
exploit,
});

const { data, write } = useInputBoxAddInput(config);
const { isLoading, isSuccess } = useWaitForTransaction({
hash: data?.hash,
});

const bountyResult = useBounty(bountyIndex);

switch (bountyResult.kind) {
case "loading":
return <Center>Loading bounty info...</Center>;
case "error":
return <Center>{bountyResult.message}</Center>;
}

const bounty = bountyResult.response;

return (
<Center>
<Box p={20} mt={50} bg={theme.colors.dark[7]}>
<Stack w={800}>
<Title size="h1">Submit bounty exploit</Title>
<Text size="lg" fw={700} c="dimmed">
{bounty.name}
</Text>
<TextInput
withAsterisk
size="lg"
label="Name"
value={name}
placeholder="Hacker"
onChange={(e) => setName(e.target.value)}
/>

<TextInput
size="lg"
label="Avatar URL"
value={imgLink}
placeholder="https://"
onChange={(e) => setImgLink(e.target.value)}
/>

<Tabs defaultValue="script">
<Tabs.List>
<Tabs.Tab value="script">Script</Tabs.Tab>
<Tabs.Tab value="file">File</Tabs.Tab>
</Tabs.List>
<form onSubmit={form.onSubmit(() => write && write())}>
<Stack w={800}>
<Title size="h1">Submit exploit</Title>
<Text size="lg" fw={700} c="dimmed">
{bounty.name}
</Text>
<TextInput
withAsterisk
size="lg"
label="Name"
placeholder="Hacker"
{...form.getInputProps("name")}
/>
<TextInput
size="lg"
label="Avatar URL"
placeholder="https://"
{...form.getInputProps("imgLink")}
/>
<Tabs defaultValue="script">
<Tabs.List>
<Tabs.Tab value="script">Script</Tabs.Tab>
<Tabs.Tab value="file">File</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="script">
<Textarea
autosize
minRows={10}
maxRows={40}
multiline={true}
styles={{
input: { fontFamily: "monospace" },
}}
placeholder="Exploit code"
error={form.getInputProps("exploit").error}
onChange={(e) =>
form.setFieldValue(
"exploit",
btoa(e.target.value),
)
}
/>
</Tabs.Panel>
<Tabs.Panel value="file">
<FileDrop
onDrop={(filesWithPath) => {
const fileWithPath = filesWithPath.at(0);
if (fileWithPath !== undefined) {
// prettier-ignore
fileWithPath
.arrayBuffer()
.then((buf) => {
// prettier-ignore
const str = Array.from(new Uint8Array(buf))
.map((b) => String.fromCharCode(b))
.join("");

<Tabs.Panel value="script">
<Textarea
autosize
minRows={10}
maxRows={40}
multiline={true}
styles={{
input: { fontFamily: "monospace" },
}}
placeholder="Exploit code"
onChange={(e) =>
setExploitFile(btoa(e.target.value))
form.setFieldValue("exploit", btoa(str));
setFilename(fileWithPath.name);
});
}
/>
</Tabs.Panel>

<Tabs.Panel value="file">
<FileDrop onDrop={(files) => readFile(files[0])}>
<FileDropText filename={filename} />
</FileDrop>
</Tabs.Panel>
</Tabs>

{testStatus && (
<Paper
withBorder
radius="sm"
p={4}
}}
>
<FileDropText filename={filename} />
</FileDrop>
</Tabs.Panel>
</Tabs>
{exploitOutput && (
<Paper
withBorder
radius="sm"
p={4}
styles={{
root: {
backgroundColor: "black",
color: "white",
},
}}
>
<Text
styles={{
root: {
backgroundColor: "black",
color: "white",
whiteSpace: "pre-wrap",
fontFamily: "monospace",
},
}}
>
<Text
styles={{
root: {
backgroundColor: "black",
color: "white",
whiteSpace: "pre-wrap",
fontFamily: "monospace",
},
}}
>
{testStatus}
</Text>
</Paper>
)}

<Group justify="center" mt="md">
<Button
size="lg"
type="submit"
disabled={
!write ||
!exploitFile ||
isLoading ||
name.trim().length === 0
}
onClick={write}
>
{isLoading
? "Submitting Exploit.."
: "Submit Exploit"}
</Button>
<Button
size="lg"
type="submit"
disabled={!exploitFile}
onClick={testExploit}
>
Test Exploit
</Button>
</Group>
{isSuccess && (
<>
<Group justify="center">
<Text size="lg">
Submit Exploit transaction successful!
</Text>
</Group>
</>
)}
</Stack>
</Box>
</Center>
{exploitOutput}
</Text>
</Paper>
)}
<Group justify="center" grow>
<Button size="lg" onClick={testExploit}>
Test
</Button>
<Button
size="lg"
type="submit"
disabled={!write || isLoading || isSuccess}
>
{isSuccess
? "Submitted!"
: isLoading
? "Submitting..."
: "Submit"}
</Button>
</Group>
</Stack>
</form>
);
};

const SendExploitPage: FC<BountyParams> = ({ params: { bountyId } }) => {
const theme = useMantineTheme();

const bountyIndex = Number(bountyId);

const bountyResult = useBounty(bountyIndex);

switch (bountyResult.kind) {
case "loading":
return <Center>Loading bounty info...</Center>;
case "error":
return <Center>{bountyResult.message}</Center>;
case "success":
return (
<Center>
<Box p={20} mt={50} bg={theme.colors.dark[7]}>
<SendExploitForm
bountyIndex={bountyIndex}
bounty={bountyResult.response}
/>
</Box>
</Center>
);
}
};

export default SendExploitPage;
Loading