diff --git a/api/index.js b/api/index.js index 4a3f9f4..f5f4fad 100644 --- a/api/index.js +++ b/api/index.js @@ -10,6 +10,8 @@ const bcrypt = require('bcrypt'); const bodyParser = require("body-parser"); const cookieParser = require("cookie-parser"); const session = require("express-session"); +const helmet = require('helmet'); +const csurf = require('csurf'); // then express and cors app.use(express.json()); @@ -19,6 +21,10 @@ app.use(cors({ credentials: true })); +// Security enhancements +app.use(helmet()); // Use Helmet to secure HTTP headers +app.use(csurf()); // Enable CSRF protection + // this for the cookies app.use(cookieParser()); app.use(bodyParser.urlencoded({extended:true })); @@ -31,6 +37,7 @@ app.use(session({ cookie: { // cookie expires in 8 hours expires: 60 * 60 * 8, + secure: true, // Ensure cookies are sent over HTTPS }, })); diff --git a/api/middlewares/Authent.js b/api/middlewares/Authent.js index 39b672a..6865d9b 100644 --- a/api/middlewares/Authent.js +++ b/api/middlewares/Authent.js @@ -12,7 +12,7 @@ const validateTheToken = (req, res, next) =>{ else{ try { - const validTokenito = verify(accessToken, "hereputyoursecret"); + const validTokenito = verify(accessToken, process.env.JWT_SECRET); if(validTokenito){ req.user = validTokenito; return next(); diff --git a/api/routes/Items.js b/api/routes/Items.js index 2ae2771..fd88aaf 100644 --- a/api/routes/Items.js +++ b/api/routes/Items.js @@ -6,6 +6,13 @@ const { Op } = require("sequelize"); const fs = require('fs'); const js2xml = require('js2xml').Js2Xml; const xml2js = require('xml2js'); +const rateLimit = require('express-rate-limit'); + +// Apply rate limiting to the importxmls route +const importXmlsLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 10 // limit each IP to 10 requests per windowMs +}); router.get('/', async (req, res) => { @@ -1042,7 +1049,7 @@ router.get('/top/:id', async (req, res) => { // this to import the xmls in the database // comment out so it doesn't run when the site is "online" -router.post('/importxmls', async (req, res) => { +router.post('/importxmls', importXmlsLimiter, async (req, res) => { const parser = new xml2js.Parser(); var path = require("path"); diff --git a/api/routes/Photos.js b/api/routes/Photos.js index 960510c..bd0af91 100644 --- a/api/routes/Photos.js +++ b/api/routes/Photos.js @@ -5,6 +5,13 @@ const { upload } = require('../middlewares/Upload'); const { Photo, Item } = require('../models'); const fs = require('fs'); const path = require("path"); +const rateLimit = require('express-rate-limit'); + +// Rate limiting middleware +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100 // limit each IP to 100 requests per windowMs +}); // No token cause this needs to work for guests as well router.get('/:id', async (req, res) => { @@ -82,11 +89,11 @@ router.post('/:id', validateTheToken, upload.single('image'), async (req, res) = // validate the user too before deleting -router.delete('/:id', validateTheToken, async (req, res)=>{ +router.delete('/:id', validateTheToken, limiter, async (req, res)=>{ const myId = req.params.id; const photograph = await Photo.findOne({ where: {id: myId}}); const itemId = photograph.ItemId; - const localpath = path.join(__dirname, '..', 'images', photograph.url.split('/').pop()) + const localpath = path.join(__dirname, '..', 'images', path.basename(photograph.url.split('/').pop())); await Photo.destroy({where : { id: myId, diff --git a/api/routes/Users.js b/api/routes/Users.js index 5aedece..3f57042 100644 --- a/api/routes/Users.js +++ b/api/routes/Users.js @@ -5,6 +5,14 @@ const bcrypt = require('bcrypt'); const {sign} =require('jsonwebtoken'); const { validateTheToken } = require('../middlewares/Authent'); const { Op } = require("sequelize"); +const rateLimit = require('express-rate-limit'); + +// Rate limiting middleware for login route +const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, // limit each IP to 5 requests per windowMs + message: "Too many login attempts from this IP, please try again after 15 minutes" +}); router.post('/', async (req, res) => { @@ -35,7 +43,7 @@ router.post('/', async (req, res) => { }); -router.post('/login', async (req, res) => { +router.post('/login', loginLimiter, async (req, res) => { const { username , password } = req.body; @@ -57,7 +65,7 @@ router.post('/login', async (req, res) => { res.json({error: "Wrong User Credentials!"}); } else{ - const accessToken = sign({username: user.username, id: user.id}, "hereputyoursecret"); + const accessToken = sign({username: user.username, id: user.id}, process.env.JWT_SECRET); res.json({token: accessToken, username: user.username, id: user.id }); } }); @@ -159,18 +167,22 @@ router.put('/approve', validateTheToken, async (req, res) => { const username = req.user.username; if (username==='admin'){ - for (var i = 0; i < userList.length; i++) { - var userId = userList[i]; - console.log(userId); - await User.update({ - approved: true - }, - {where : { - id: userId - } - }); + if (Array.isArray(userList) && userList.length <= 100) { // Validate userList + for (var i = 0; i < userList.length; i++) { + var userId = userList[i]; + console.log(userId); + await User.update({ + approved: true + }, + {where : { + id: userId + } + }); + } + res.json("Succesfully approved users!"); + } else { + res.json("Invalid user list!"); } - res.json("Succesfully approved users!"); } else{ res.json("This is forbidden!") diff --git a/front/src/components/Admin/Download.js b/front/src/components/Admin/Download.js index 15e3d9e..009ba88 100644 --- a/front/src/components/Admin/Download.js +++ b/front/src/components/Admin/Download.js @@ -15,7 +15,8 @@ export function Download(props){ }, }).then((response) => { - const create_url = window.URL.createObjectURL(new Blob([JSON.stringify(response.data, null, "\t")])); + const sanitizedData = JSON.stringify(response.data, null, "\t").replace(//g, "\\u003e"); + const create_url = window.URL.createObjectURL(new Blob([sanitizedData])); const generate_link = document.createElement('a'); generate_link.href = create_url; diff --git a/front/src/components/Admin/DownloadXML.js b/front/src/components/Admin/DownloadXML.js index e7d09bf..965a9ea 100644 --- a/front/src/components/Admin/DownloadXML.js +++ b/front/src/components/Admin/DownloadXML.js @@ -23,6 +23,7 @@ export function DownloadXML(props){ generate_link.setAttribute('download', 'auctions.xml'); document.body.appendChild(generate_link); generate_link.click(); + document.body.removeChild(generate_link); // Ensure the link is removed after use }); } diff --git a/front/src/components/Modals/Recommendations.js b/front/src/components/Modals/Recommendations.js index 7496462..184fe64 100644 --- a/front/src/components/Modals/Recommendations.js +++ b/front/src/components/Modals/Recommendations.js @@ -33,10 +33,11 @@ function Recommendations() { // Displaying the items of this particular page const displayItems = itemList.slice( visitedPages, visitedPages + itemsPerPage ).map((value, key)=>{ + const sanitizedSrc = value.coverPhoto.startsWith('http') ? value.coverPhoto : ''; return