diff --git a/app/(auth)/admin/multi-logs/[slug]/page.tsx b/app/(auth)/admin/multi-logs/[slug]/page.tsx index 3a66674..f43f2ce 100644 --- a/app/(auth)/admin/multi-logs/[slug]/page.tsx +++ b/app/(auth)/admin/multi-logs/[slug]/page.tsx @@ -16,6 +16,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => { const { toast } = useToast(); const [title, setTitle] = useState(""); const [content, setContent] = useState(""); + const [vulnerabilities, setVulnerabilities] = useState(""); const [loading, setLoading] = useState(false); useEffect(() => { @@ -25,6 +26,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => { .then((data) => { setTitle(data.title); setContent(data.content); + setVulnerabilities(data.vulnerabilities || ""); }) .catch((error) => console.error("Failed to load page", error)); } @@ -35,7 +37,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => { const response = await fetch(`/api/mdx/pages/${slug}`, { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ title, content }), + body: JSON.stringify({ title, content, vulnerabilities }), }); if (response.ok) { @@ -44,9 +46,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => { router.push(`/admin/multi-logs/`); } else { setLoading(false); - // TEMPERARORY ERROR - router.push(`/admin/multi-logs/`); - toast({ description: "Updated but cant return data" }); + toast({ description: "Page Updated" }); } }; @@ -57,7 +57,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => { }; return ( -
+

Edit Page: {slug}

{ onChange={handleEditorChange} height={560} /> +

Vulnerabilities

+ setVulnerabilities(value || "")} + height={200} + /> diff --git a/app/(auth)/admin/vulnerabilities/mods/page.tsx b/app/(auth)/admin/vulnerabilities/mods/page.tsx new file mode 100644 index 0000000..0763745 --- /dev/null +++ b/app/(auth)/admin/vulnerabilities/mods/page.tsx @@ -0,0 +1,276 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { useForm, SubmitHandler, useFieldArray } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "@/components/ui/select"; +import { z } from "zod"; +import { useToast } from "@/components/ui/use-toast"; +import { + ModsVulnerability, + vulnerabilitiesSchema, +} from "@/lib/validations/validation"; + +interface VulnerabilityEntry { + _id: string; + title: string; + category: string; + bullets: { point: string }[]; +} + +type VulnerabilitiesForm = z.infer; + +const AdminLogPage = () => { + const [logs, setLogs] = useState([]); + const [categories, setCategories] = useState< + { title: string; _id: string }[] + >([]); + const [selectedCategory, setSelectedCategory] = useState(""); + const [error, setError] = useState(""); + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(vulnerabilitiesSchema), + defaultValues: { + title: "", + category: "", + bullets: [{ point: "" }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control: form.control, + name: "bullets", + }); + + const fetchLogs = async () => { + try { + const response = await fetch("/api/vulnerabilities", { method: "GET" }); + if (response.ok) { + const data: VulnerabilityEntry[] = await response.json(); + setLogs(data); + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error: any) { + setError(error.message || "Failed to fetch logs"); + } + }; + + const fetchCategories = async () => { + try { + const response = await fetch("/api/mdx/pages", { method: "GET" }); + if (response.ok) { + const data = await response.json(); + setCategories(data); + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error: any) { + setError(error.message || "Failed to fetch categories"); + } + }; + + const onSubmit: SubmitHandler = async (data) => { + setLoading(true); + const response = await fetch("/api/vulnerability/mods", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + ...data, + category: selectedCategory, + }), + }); + + if (response.ok) { + form.reset(); + fetchLogs(); + setLoading(false); + toast({ description: "Logs successfully added" }); + } else { + setLoading(false); + toast({ description: "Upload Failed", variant: "destructive" }); + } + }; + + const deleteLog = async (id: string) => { + try { + const response = await fetch(`/api/delete/vulnerability/${id}`, { + method: "DELETE", + }); + if (response.ok) { + fetchLogs(); + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error: any) { + setError(error.message || "Failed to delete log"); + } + }; + + useEffect(() => { + fetchLogs(); + fetchCategories(); + const interval = setInterval(() => { + fetchLogs(); + }, 10000); + + return () => clearInterval(interval); + }, []); + + return ( +
+

Server Vulnerabilities Form

+
+ + ( + + Title + + + + + + )} + /> + + ( + + Category + + + )} + /> + + {fields.map((field, index) => ( + ( + + Bullet Point {index + 1} + + + + + + + )} + /> + ))} + + + + + + {/* Section to list and delete logs */} +
+

+ Existing Vulnerabilities +

+ {error &&

{error}

} + + + + Title + Category + Actions + + + + {logs + .slice() + .reverse() + .map((log) => ( + + + {log.title} + + + {log.category} + + + + + + ))} + +
+
+
+ ); +}; + +export default AdminLogPage; diff --git a/app/(auth)/admin/vulnerabilities/page.tsx b/app/(auth)/admin/vulnerabilities/page.tsx index eef7dfa..65a3e3e 100644 --- a/app/(auth)/admin/vulnerabilities/page.tsx +++ b/app/(auth)/admin/vulnerabilities/page.tsx @@ -22,30 +22,34 @@ import { TableRow, } from "@/components/ui/table"; import { Input } from "@/components/ui/input"; -import { logsSchema } from "@/lib/validations/validation"; import { z } from "zod"; import { useToast } from "@/components/ui/use-toast"; +import { vulnerabilitiesSchema } from "@/lib/validations/validation"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@/components/ui/select"; -interface LogEntry { +interface VulnerabiltyEntry { _id: string; version: string; - date: string; bullets: { point: string }[]; } -type LogsFormValues = z.infer; +type VulnerabiltiesForm = z.infer; const AdminLogPage = () => { - const [logs, setLogs] = useState([]); + const [logs, setLogs] = useState([]); const [error, setError] = useState(""); const { toast } = useToast(); const [loading, setLoading] = useState(false); - const form = useForm({ - resolver: zodResolver(logsSchema), + const form = useForm({ + resolver: zodResolver(vulnerabilitiesSchema), defaultValues: { version: "", - date: "", bullets: [{ point: "" }], }, }); @@ -59,7 +63,7 @@ const AdminLogPage = () => { try { const response = await fetch("/api/vulnerabilities", { method: "GET" }); if (response.ok) { - const data: LogEntry[] = await response.json(); + const data: VulnerabiltyEntry[] = await response.json(); setLogs(data); } else { throw new Error(`HTTP error! status: ${response.status}`); @@ -69,7 +73,7 @@ const AdminLogPage = () => { } }; - const onSubmit: SubmitHandler = async (data) => { + const onSubmit: SubmitHandler = async (data) => { setLoading(true); const response = await fetch("/api/uploadvulnerabilities", { method: "POST", @@ -90,7 +94,7 @@ const AdminLogPage = () => { const deleteLog = async (id: string) => { try { - const response = await fetch(`/api/delete/logs/${id}`, { + const response = await fetch(`/api/delete/vulnerability/${id}`, { method: "DELETE", }); if (response.ok) { diff --git a/app/(root)/changelogs/[slug]/page.tsx b/app/(root)/changelogs/[slug]/page.tsx index f420fb7..c502361 100644 --- a/app/(root)/changelogs/[slug]/page.tsx +++ b/app/(root)/changelogs/[slug]/page.tsx @@ -74,7 +74,7 @@ const Page = ({ params }: { params: { slug: string } }) => { <>

- {page.title} + {page.title} Change Log

{page.content} diff --git a/app/(root)/vulnerabilities/page.tsx b/app/(root)/vulnerabilities/page.tsx index 11a83aa..a1519c4 100644 --- a/app/(root)/vulnerabilities/page.tsx +++ b/app/(root)/vulnerabilities/page.tsx @@ -1,7 +1,7 @@ "use client"; import ReactMarkdown from "react-markdown"; -import { vulnerabilities } from "@/constants/guidelines"; +import { VULNERABILITY } from "@/constants/guidelines"; import { useEffect, useState } from "react"; import { Skeleton } from "@/components/ui/skeleton"; @@ -11,14 +11,22 @@ interface Bullet { interface Vulnerabilities { _id: string; - date: string; version: string; bullets?: Bullet[]; // Make bullets optional } +interface ModsVulnerability { + _id: string; + title: string; + slug: string; + content: string; + vulnerabilities: string; +} + const Vulnerabilities = () => { const [loading, setLoading] = useState(true); const [downloads, setDownloads] = useState([]); + const [mods, setMods] = useState([]); const [error, setError] = useState(""); const fetchData = async () => { @@ -29,7 +37,7 @@ const Vulnerabilities = () => { if (response.ok) { const data: Vulnerabilities[] = await response.json(); setDownloads(data); - return (document.title = "Vulnerabilities | SVRJS"); + document.title = "Vulnerabilities | SVRJS"; } else { throw new Error(`HTTP error! status: ${response.status}`); } @@ -40,18 +48,43 @@ const Vulnerabilities = () => { } }; + const fetchMods = async () => { + try { + const response = await fetch(`/api/mdx/pages`, { + method: "GET", + }); + if (response.ok) { + const data: ModsVulnerability[] = await response.json(); + // Filter out entries where vulnerabilities is undefined or an empty string + const filteredMods = data.filter( + (mod) => mod.vulnerabilities && mod.vulnerabilities.trim() !== "" + ); + setMods(filteredMods); + document.title = "Vulnerabilities | SVRJS"; + } else { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error: any) { + setError(error.message || "Failed to fetch vulnerabilities"); + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchData(); - + fetchMods(); const interval = setInterval(() => { fetchData(); + fetchMods(); }, 10000); return () => clearInterval(interval); }, []); - const reversedDownloads = [...downloads].reverse(); - // initially loading = true + const reversedDownloads = [...downloads].reverse(); + const reversedMods = [...mods].reverse(); + if (loading) { return (
@@ -98,9 +131,27 @@ const Vulnerabilities = () => { ))} +
- {vulnerabilities} + {VULNERABILITY}
+ + {/* Section with MODS content */} + {reversedMods.map((mod) => ( +
+

+ {mod.title} +

+ {mod.vulnerabilities && ( +
+ {mod.vulnerabilities} +
+ )} +
+ ))}
); }; diff --git a/app/api/delete/vulnerability/[id]/route.ts b/app/api/delete/vulnerability/[id]/route.ts new file mode 100644 index 0000000..a4018ac --- /dev/null +++ b/app/api/delete/vulnerability/[id]/route.ts @@ -0,0 +1,30 @@ +// app/api/delete/[id]/route.ts +import clientPromise from "@/lib/db"; +import { ObjectId } from "mongodb"; +import { NextResponse } from "next/server"; + +export async function DELETE( + request: Request, + { params }: { params: { id: string } } +) { + const { id } = params; + + try { + const client = await clientPromise; + const db = client.db("downloadsDatabase"); + const collection = db.collection("vulnerabilities"); + + const result = await collection.deleteOne({ _id: new ObjectId(id) }); + + if (result.deletedCount === 1) { + return NextResponse.json({ message: "Log deleted successfully" }); + } else { + return NextResponse.json({ message: "Log not found" }, { status: 404 }); + } + } catch (error) { + return NextResponse.json( + { message: "Failed to delete log", error: error }, + { status: 500 } + ); + } +} diff --git a/app/api/delete/vulnerability/mods/route.ts b/app/api/delete/vulnerability/mods/route.ts new file mode 100644 index 0000000..bc0a681 --- /dev/null +++ b/app/api/delete/vulnerability/mods/route.ts @@ -0,0 +1,32 @@ +import clientPromise from "@/lib/db"; +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === "POST") { + try { + const client = await clientPromise; + const db = client.db("modsVuln"); + const { title, category, bullets } = req.body; + + if (!title || !category || !bullets || !Array.isArray(bullets)) { + return res.status(400).json({ error: "Invalid data" }); + } + + await db.collection("vulnerabilities").insertOne({ + title, + category, + bullets, + }); + + res.status(200).json({ message: "Vulnerability added successfully" }); + } catch (error) { + res.status(500).json({ error: error || "Failed to add vulnerability" }); + } + } else { + res.setHeader("Allow", ["POST"]); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/app/api/mdx/pages/[slug]/route.ts b/app/api/mdx/pages/[slug]/route.ts index 9192649..48cc77f 100644 --- a/app/api/mdx/pages/[slug]/route.ts +++ b/app/api/mdx/pages/[slug]/route.ts @@ -34,57 +34,35 @@ export const PUT = async ( return NextResponse.json({ message: "Slug is required" }, { status: 400 }); } - const { title, content } = await req.json(); + const { title, content, vulnerabilities } = await req.json(); - if (typeof title !== "string" || typeof content !== "string") { + if ( + typeof title !== "string" || + typeof content !== "string" || + typeof vulnerabilities !== "string" + ) { return NextResponse.json( - { message: "Invalid title or content" }, + { message: "Invalid title, content, or vulnerabilities" }, { status: 400 } ); } try { - // it works here ig - const result = await db - .collection("pages") - .findOneAndUpdate( - { slug }, - { $set: { title, content } }, - { returnDocument: "after" } - ); + const result = await db.collection("pages").findOneAndUpdate( + { slug }, + { $set: { title, content, vulnerabilities } }, + { returnDocument: "after" } // Updated option + ); - // i hate my life fr fr - console.log("Update Result:", result); - // result returns like - - // Update Result: { - // _id: new ObjectId('66a2946b2b91eef505eef943'), - // title: 'TEST PAGE', - // slug: 'test-page', - // content: 'asd]---\n' + - // '---\n' + - // '\n' + - // 'this is basic heading ?\n' + - // '\n' + - // '**HELLO**\n' + - // '\n' + - // 'erw\n' + - // '\n' + - // 'trying another time for test' - // } - - // ERRROR : TypeError: Cannot read properties of undefined (reading '_id') - // aposdjaoi sdio JUST WORK NIAWWWWWWWWW - - // if (result && result.value) { - const serializedResult = { - ...result?.value, - _id: result?.value._id.toString(), // Convert ObjectId to string - }; - return NextResponse.json(result?.value.content, { status: 200 }); - // } else { - // return NextResponse.json({ message: "Page not found" }, { status: 404 }); - // } + if (result?.value) { + const serializedResult = { + ...result.value, + _id: result.value._id.toString(), // Convert ObjectId to string + }; + return NextResponse.json(serializedResult, { status: 200 }); + } else { + return NextResponse.json({ message: "Page not found" }, { status: 404 }); + } } catch (error) { console.error("Error updating page:", error); return NextResponse.json( diff --git a/bun.lockb b/bun.lockb index 90dbf44..763ff07 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000..cbe5a36 --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,160 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/constants/guidelines.ts b/constants/guidelines.ts index 026ff39..e3f918a 100644 --- a/constants/guidelines.ts +++ b/constants/guidelines.ts @@ -357,7 +357,7 @@ By contributing to our project, you agree that your contributions will be licens Thank you for considering contributing to our project! `; -export const vulnerabilities = ` +export const VULNERABILITY = ` ### Fixed in SVR.JS 3.15.0 and in SVR.JS 3.14.16 LTS - An attacker could send a HTTP forward proxy request with malformed URL not using CONNECT method to possibly crash the server. @@ -419,46 +419,6 @@ export const vulnerabilities = ` ### Fixed in SVR.JS 2.1.1 - An attacker could use directory listing to access secret files (through path traversal). -## easy-waf integration - -### Fixed in easy-waf integration 1.2.4 -- An attacker could send a very small chunk of the POST request body (which will not trigger WAF) before the payload to bypass the WAF. - -### Fixed in easy-waf integration 1.2.1 -- An attacker could access the resource on the server with poorly written SVR.JS mod or server-side JavaScript (that normally would invoke 500 Internal Server Error) to crash the server. - -## RedBrick - -### Fixed in RedBrick 2.5.4 -- An attacker could add HTTP authentication header to the HTTP request when not required to enable web application functionality normally disabled on unauthenticated requests. View the security advisory - -### Fixed in RedBrick 2.3.3 -- An attacker could use “CGI-BIN” instead of “cgi-bin” to leak source code, while SVR.JS with RedBrick is running on Windows. View the security advisory -- An attacker could leak RedBrick interpreter settings, while SVR.JS with RedBrick is running on Windows. View the security advisory - -## reverse-proxy-mod - -### Fixed in reverse-proxy-mod 1.1.2 -- An attacker could hack the upstream server, replace the web server or application with one that sends an invalid HTTP response code, and make a request to the hacked server through the reverse proxy to crash the reverse proxy server. - -### Fixed in reverse-proxy-mod 1.0.4 -- An attacker could leak reverse proxy configuration file. View the security advisory - -## OrangeCircle - -### Fixed in OrangeCircle 1.1.2 -- An attacker could add HTTP authentication header to the HTTP request when not required to enable web application functionality normally disabled on unauthenticated requests. View the security advisory - -### Fixed in OrangeCircle 1.0.2 -- An attacker could leak OrangeCircle configuration, while SVR.JS with YellowSquare is running on Windows. View the security advisory - -## YellowSquare - -### Fixed in YellowSquare 1.1.2 -- An attacker could add HTTP authentication header to the HTTP request when not required to enable web application functionality normally disabled on unauthenticated requests. View the security advisory - -### Fixed in YellowSquare 1.0.1 -- An attacker could use “JSGI-BIN” instead of “jsgi-bin” to leak source code, while SVR.JS with YellowSquare is running on Windows. View the security advisory `; export const CHANGE_LOGS = ` diff --git a/lib/validations/validation.ts b/lib/validations/validation.ts index b719279..f7ba46f 100644 --- a/lib/validations/validation.ts +++ b/lib/validations/validation.ts @@ -24,7 +24,28 @@ export const logsSchema = z.object({ ), }); +export const vulnerabilitiesSchema = z.object({ + version: z.string(), + bullets: z.array( + z.object({ + point: z.string(), + }) + ), +}); + +export const ModsVulnerabilities = z.object({ + title: z.string(), + category: z.string(), + bullets: z.array( + z.object({ + point: z.string(), + }) + ), +}); + export type LogsFormValues = z.infer; +export type VulnerabiltiesForm = z.infer; +export type ModsVulnerability = z.infer; // Contact Page diff --git a/package.json b/package.json index e3f6da7..511578e 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,10 @@ "@hookform/resolvers": "^3.6.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.1.1", - "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.1",