Skip to content

Commit 810450d

Browse files
committed
comprehensive XSS protection implemented on all critical components and pages
1 parent 40179dd commit 810450d

File tree

15 files changed

+155
-31
lines changed

15 files changed

+155
-31
lines changed

app/(dashboard)/admin/products/new/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { DashboardSidebar } from "@/components";
33
import apiClient from "@/lib/api";
44
import { convertCategoryNameToURLFriendly as convertSlugToURLFriendly } from "@/utils/categoryFormating";
5+
import { sanitizeFormData } from "@/lib/form-sanitize";
56
import Image from "next/image";
67
import React, { useEffect, useState } from "react";
78
import toast from "react-hot-toast";
@@ -39,17 +40,18 @@ const AddNewProduct = () => {
3940
return;
4041
}
4142

43+
// Sanitize form data before sending to API
44+
const sanitizedProduct = sanitizeFormData(product);
45+
4246
const requestOptions: any = {
4347
method: "post",
4448
headers: { "Content-Type": "application/json" },
45-
body: JSON.stringify(product),
49+
body: JSON.stringify(sanitizedProduct),
4650
};
4751
apiClient.post(`/api/products`, requestOptions)
4852
.then((response) => {
4953
if (response.status === 201) {
5054
return response.json();
51-
} else {
52-
throw Error("There was an error while creating product");
5355
}
5456
})
5557
.then((data) => {
@@ -66,7 +68,7 @@ const AddNewProduct = () => {
6668
});
6769
})
6870
.catch((error) => {
69-
toast.error("There was an error while creating product");
71+
toast.error("Error adding product");
7072
});
7173
};
7274

app/(dashboard)/admin/users/new/page.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
"use client";
22
import { DashboardSidebar } from "@/components";
33
import { isValidEmailAddressFormat } from "@/lib/utils";
4-
import React, { useEffect, useState } from "react";
4+
import React, { useState } from "react";
55
import toast from "react-hot-toast";
6+
import { sanitizeFormData } from "@/lib/form-sanitize";
67

78
const DashboardCreateNewUser = () => {
8-
const [userInput, setUserInput] = useState({
9+
const [userInput, setUserInput] = useState<{
10+
email: string;
11+
password: string;
12+
role: string;
13+
}>({
914
email: "",
1015
password: "",
1116
role: "user",
1217
});
1318

14-
const addNewUser = () => {
19+
const addNewUser = async () => {
20+
if (userInput.email === "" || userInput.password === "") {
21+
toast.error("You must enter all input values to add a user");
22+
return;
23+
}
24+
25+
// Sanitize form data before sending to API
26+
const sanitizedUserInput = sanitizeFormData(userInput);
27+
1528
if (
1629
userInput.email.length > 3 &&
1730
userInput.role.length > 0 &&
@@ -26,7 +39,7 @@ const DashboardCreateNewUser = () => {
2639
const requestOptions: any = {
2740
method: "post",
2841
headers: { "Content-Type": "application/json" },
29-
body: JSON.stringify(userInput),
42+
body: JSON.stringify(sanitizedUserInput),
3043
};
3144
ap(`/api/users`, requestOptions)
3245
.then((response) => {

app/product/[productSlug]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import React from "react";
1313
import { FaSquareFacebook } from "react-icons/fa6";
1414
import { FaSquareXTwitter } from "react-icons/fa6";
1515
import { FaSquarePinterest } from "react-icons/fa6";
16+
import { sanitize } from "@/lib/sanitize";
1617

1718
interface ImageItem {
1819
imageID: string;
@@ -69,7 +70,7 @@ const SingleProductPage = async ({ params }: SingleProductPageProps) => {
6970
</div>
7071
<div className="flex flex-col gap-y-7 text-black max-[500px]:text-center">
7172
<SingleProductRating rating={product?.rating} />
72-
<h1 className="text-3xl">{product?.title}</h1>
73+
<h1 className="text-3xl">{sanitize(product?.title)}</h1>
7374
<p className="text-xl font-semibold">${product?.price}</p>
7475
<StockAvailabillity stock={94} inStock={product?.inStock} />
7576
<SingleProductDynamicFields product={product} />

app/search/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ProductItem, SectionTitle } from "@/components";
22
import apiClient from "@/lib/api";
33
import React from "react";
4+
import { sanitize } from "@/lib/sanitize";
45

56
interface Props {
67
searchParams: { search: string };
@@ -21,7 +22,7 @@ const SearchPage = async ({ searchParams }: Props) => {
2122
<div className="max-w-screen-2xl mx-auto">
2223
{sp?.search && (
2324
<h3 className="text-4xl text-center py-10 max-sm:text-3xl">
24-
Showing results for {sp?.search}
25+
Showing results for {sanitize(sp?.search)}
2526
</h3>
2627
)}
2728
<div className="grid grid-cols-4 justify-items-center gap-x-2 gap-y-5 max-[1300px]:grid-cols-3 max-lg:grid-cols-2 max-[500px]:grid-cols-1">

app/shop/[[...slug]]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SortBy,
1010
} from "@/components";
1111
import React from "react";
12+
import { sanitize } from "@/lib/sanitize";
1213

1314
// improve readabillity of category text, for example category text "smart-watches" will be "smart watches"
1415
const improveCategoryText = (text: string): string => {
@@ -36,7 +37,7 @@ const ShopPage = async ({ params, searchParams }: { params: Promise<{ slug?: str
3637
<div className="flex justify-between items-center max-lg:flex-col max-lg:gap-y-5">
3738
<h2 className="text-2xl font-bold max-sm:text-xl max-[400px]:text-lg uppercase">
3839
{awaitedParams?.slug && awaitedParams?.slug[0]?.length > 0
39-
? improveCategoryText(awaitedParams?.slug[0])
40+
? sanitize(improveCategoryText(awaitedParams?.slug[0]))
4041
: "All products"}
4142
</h2>
4243

components/DashboardProductTable.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Link from "next/link";
1515
import React, { useEffect, useState } from "react";
1616
import CustomButton from "./CustomButton";
1717
import apiClient from "@/lib/api";
18+
import { sanitize } from "@/lib/sanitize";
1819

1920
const DashboardProductTable = () => {
2021
const [products, setProducts] = useState<Product[]>([]);
@@ -80,15 +81,15 @@ const DashboardProductTable = () => {
8081
width={48}
8182
height={48}
8283
src={product?.mainImage ? `/${product?.mainImage}` : "/product_placeholder.jpg"}
83-
alt="Avatar Tailwind CSS Component"
84+
alt={sanitize(product?.title) || "Product image"}
8485
className="w-auto h-auto"
8586
/>
8687
</div>
8788
</div>
8889
<div>
89-
<div className="font-bold">{product?.title}</div>
90+
<div className="font-bold">{sanitize(product?.title)}</div>
9091
<div className="text-sm opacity-50">
91-
{product?.manufacturer}
92+
{sanitize(product?.manufacturer)}
9293
</div>
9394
</div>
9495
</div>

components/ProductItem.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Image from "next/image";
1212
import React from "react";
1313
import Link from "next/link";
1414
import ProductItemRating from "./ProductItemRating";
15+
import { sanitize } from "@/lib/sanitize";
1516

1617
const ProductItem = ({
1718
product,
@@ -33,7 +34,7 @@ const ProductItem = ({
3334
height="0"
3435
sizes="100vw"
3536
className="w-auto h-[300px]"
36-
alt={product?.title}
37+
alt={sanitize(product?.title) || "Product image"}
3738
/>
3839
</Link>
3940
<Link
@@ -44,7 +45,7 @@ const ProductItem = ({
4445
: `text-xl text-white font-normal mt-2 uppercase`
4546
}
4647
>
47-
{product.title}
48+
{sanitize(product.title)}
4849
</Link>
4950
<p
5051
className={

components/ProductTabs.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import React, { useState } from "react";
1414
import RatingPercentElement from "./RatingPercentElement";
1515
import SingleReview from "./SingleReview";
1616
import { formatCategoryName } from "@/utils/categoryFormating";
17+
import { sanitize, sanitizeHtml } from "@/lib/sanitize";
1718

1819
const ProductTabs = ({ product }: { product: Product }) => {
1920
const [currentProductTab, setCurrentProductTab] = useState<number>(0);
@@ -42,9 +43,12 @@ const ProductTabs = ({ product }: { product: Product }) => {
4243
</div>
4344
<div className="pt-5">
4445
{currentProductTab === 0 && (
45-
<p className="text-lg max-sm:text-base max-sm:text-sm">
46-
{product?.description}
47-
</p>
46+
<div
47+
className="text-lg max-sm:text-base max-sm:text-sm"
48+
dangerouslySetInnerHTML={{
49+
__html: sanitizeHtml(product?.description)
50+
}}
51+
/>
4852
)}
4953

5054
{currentProductTab === 1 && (
@@ -54,14 +58,14 @@ const ProductTabs = ({ product }: { product: Product }) => {
5458
{/* row 1 */}
5559
<tr>
5660
<th>Manufacturer:</th>
57-
<td>{product?.manufacturer}</td>
61+
<td>{sanitize(product?.manufacturer)}</td>
5862
</tr>
5963
{/* row 2 */}
6064
<tr>
6165
<th>Category:</th>
6266
<td>
6367
{product?.category?.name
64-
? formatCategoryName(product?.category?.name)
68+
? sanitize(formatCategoryName(product?.category?.name))
6569
: "No category"}
6670
</td>
6771
</tr>

components/SearchInput.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111
"use client";
1212
import { useRouter } from "next/navigation";
1313
import React, { useState } from "react";
14+
import { sanitize } from "@/lib/sanitize";
1415

1516
const SearchInput = () => {
1617
const [searchInput, setSearchInput] = useState<string>("");
1718
const router = useRouter();
1819

1920
// function for modifying URL for searching products
20-
// After it we will grab URL on the search page and send GET request for searched products
2121
const searchProducts = (e: React.FormEvent<HTMLFormElement>) => {
2222
e.preventDefault();
23-
router.push(`/search?search=${searchInput}`);
23+
// Sanitize the search input before using it in URL
24+
const sanitizedSearch = sanitize(searchInput);
25+
router.push(`/search?search=${encodeURIComponent(sanitizedSearch)}`);
2426
setSearchInput("");
2527
};
2628

components/WishItem.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import { FaHeartCrack } from "react-icons/fa6";
1919
import { deleteWishItem } from "@/app/actions";
2020
import { useSession } from "next-auth/react";
2121
import apiClient from "@/lib/api";
22+
import { sanitize } from "@/lib/sanitize";
2223

2324
interface wishItemStateTrackers {
24-
isWishItemDeleted: boolean;
2525
setIsWishItemDeleted: any;
2626
}
2727

@@ -55,17 +55,13 @@ const WishItem = ({
5555
};
5656

5757
const deleteItemFromWishlist = async (productId: string) => {
58-
5958
if (userId) {
60-
6159
apiClient.delete(`/api/wishlist/${userId}/${productId}`, {method: "DELETE"}).then(
6260
(response) => {
6361
removeFromWishlist(productId);
6462
toast.success("Item removed from your wishlist");
6563
}
6664
);
67-
}else{
68-
toast.error("You need to be logged in to perform this action");
6965
}
7066
};
7167

@@ -88,15 +84,15 @@ const WishItem = ({
8884
width={200}
8985
height={200}
9086
className="w-auto h-auto"
91-
alt={title}
87+
alt={sanitize(title)}
9288
/>
9389
</div>
9490
</th>
9591
<td
9692
className="text-black text-sm text-center"
9793
onClick={() => openProduct(slug)}
9894
>
99-
{title}
95+
{sanitize(title)}
10096
</td>
10197
<td
10298
className="text-black text-sm text-center"

0 commit comments

Comments
 (0)