From e29bf3465de9beb9d3e8c71fdca7b5ac85a8180f Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Fri, 3 Jul 2026 23:55:44 +0530 Subject: [PATCH 01/10] Integrate Zod for request validation in user authentication routes --- backend/Input_validators/ValidateAuth.js | 55 ++++++++++++++++++++++++ backend/Input_validators/registerZod.js | 28 ++++++++++++ backend/config/db.js | 3 ++ backend/package-lock.json | 12 +++++- backend/package.json | 3 +- backend/routes/authRoutes.js | 7 ++- 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 backend/Input_validators/ValidateAuth.js create mode 100644 backend/Input_validators/registerZod.js diff --git a/backend/Input_validators/ValidateAuth.js b/backend/Input_validators/ValidateAuth.js new file mode 100644 index 0000000..927cd83 --- /dev/null +++ b/backend/Input_validators/ValidateAuth.js @@ -0,0 +1,55 @@ +const { z } = require("zod"); + +const registerUserZod = z.object({ + name: z.string().min(4, "Length of the name should be minimum 4").trim(), + email: z.string().email("Enter a valid email").trim(), + password: z.string().trim(), + profileImageUrl: z.string().url("Enter a valid URL").trim().optional() +}); + + +const validateUserSignup = (req, res, next) => { + try { + registerUserZod.parse(req.body); + next(); + } catch (err) { + if (err instanceof z.ZodError) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.issues.map(issue => ({ + field: issue.path.join("."), + message: issue.message, + })), + }); + } + // fallback for other errors + return res.status(500).json({ success: false, message: "Internal server error" }); +} +}; + +const loginUserZod = z.object({ + email : z.string().email("Enter a valid email"), + password : z.string().min(8) +}) + +const validateUserLogin = (req, res, next) => { + try { + loginUserZod.parse(req.body); + next(); + } catch (err) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); + } +}; + +module.exports = { + validateUserLogin, + validateUserSignup +} diff --git a/backend/Input_validators/registerZod.js b/backend/Input_validators/registerZod.js new file mode 100644 index 0000000..869df41 --- /dev/null +++ b/backend/Input_validators/registerZod.js @@ -0,0 +1,28 @@ +const { z } = require("zod"); + +const registerUserZod = z.object({ + name: z.string().min(4, "Length of the name should be minimum 4").trim(), + email: z.string().email("Enter a valid email").trim(), + password: z.string().trim(), + profileImageUrl: z.string().url("Enter a valid URL").optional().trim() +}); + +const validateUserSignup = (req, res, next) => { + try { + registerUserZod.parse(req.body); + next(); + } catch (err) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); + } +}; + +module.exports = { + validateUserSignup +} diff --git a/backend/config/db.js b/backend/config/db.js index 77e3c22..7c78f8c 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,4 +1,7 @@ const mongoose = require("mongoose"); +const dns = require('dns'); + +dns.setServers(['8.8.8.8']) const connectDB = async () => { try { diff --git a/backend/package-lock.json b/backend/package-lock.json index 496a27b..8bd76ba 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -25,7 +25,8 @@ "node-cache": "^5.1.2", "nodemailer": "^8.0.10", "pdf-parse": "^2.4.5", - "resend": "^6.12.4" + "resend": "^6.12.4", + "zod": "^4.4.3" }, "devDependencies": { "nodemon": "^3.1.10", @@ -3793,6 +3794,15 @@ "optional": true } } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/backend/package.json b/backend/package.json index edea614..10fd0c4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,7 +28,8 @@ "node-cache": "^5.1.2", "nodemailer": "^8.0.10", "pdf-parse": "^2.4.5", - "resend": "^6.12.4" + "resend": "^6.12.4", + "zod": "^4.4.3" }, "devDependencies": { "nodemon": "^3.1.10", diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js index 56360ec..d7fb72a 100644 --- a/backend/routes/authRoutes.js +++ b/backend/routes/authRoutes.js @@ -2,16 +2,19 @@ const express = require("express"); const { registerUser, loginUser, verifyEmail, resendVerificationEmail, getUserProfile, updateUserProfile, changePassword, deleteUserAccount, refreshToken, logoutUser } = require("../controllers/authController"); const { protect } = require("../middlewares/authMiddleware"); const { upload } = require("../middlewares/uploadMiddleware"); +const { validateUserLogin, validateUserSignup } = require("../Input_validators/ValidateAuth"); const router = express.Router(); + const { authLimiter, generalLimiter, sensitiveAuthLimiter, } = require("../middlewares/rateLimiter"); + // Auth Routes -router.post("/register", authLimiter, registerUser); -router.post("/login", authLimiter, loginUser); +router.post("/register", authLimiter, validateUserSignup, registerUser); +router.post("/login", authLimiter, validateUserLogin, loginUser); router.post("/refresh", authLimiter, refreshToken); router.post("/logout", authLimiter, logoutUser); router.get("/profile", protect, generalLimiter, getUserProfile); From 7dfb7b7fc46a051dff51d96becd72a516eb25cc9 Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Fri, 3 Jul 2026 23:57:18 +0530 Subject: [PATCH 02/10] Remove unused DNS server configuration from database connection file --- backend/config/db.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/config/db.js b/backend/config/db.js index 7c78f8c..ee5dfc5 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,7 +1,5 @@ const mongoose = require("mongoose"); -const dns = require('dns'); -dns.setServers(['8.8.8.8']) const connectDB = async () => { try { From aee45fcf4173ba6dbcd1a87833849d671dbca09c Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Fri, 3 Jul 2026 23:57:29 +0530 Subject: [PATCH 03/10] Remove Zod validation for user registration --- backend/Input_validators/registerZod.js | 28 ------------------------- 1 file changed, 28 deletions(-) delete mode 100644 backend/Input_validators/registerZod.js diff --git a/backend/Input_validators/registerZod.js b/backend/Input_validators/registerZod.js deleted file mode 100644 index 869df41..0000000 --- a/backend/Input_validators/registerZod.js +++ /dev/null @@ -1,28 +0,0 @@ -const { z } = require("zod"); - -const registerUserZod = z.object({ - name: z.string().min(4, "Length of the name should be minimum 4").trim(), - email: z.string().email("Enter a valid email").trim(), - password: z.string().trim(), - profileImageUrl: z.string().url("Enter a valid URL").optional().trim() -}); - -const validateUserSignup = (req, res, next) => { - try { - registerUserZod.parse(req.body); - next(); - } catch (err) { - return res.status(400).json({ - success: false, - message: "Validation failed", - errors: err.errors.map(e => ({ - field: e.path.join("."), - message: e.message, - })), - }); - } -}; - -module.exports = { - validateUserSignup -} From 6282d61c31659a13ff251ac097ccc1a843d6e3e8 Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 00:08:41 +0530 Subject: [PATCH 04/10] Integrate Zod validation for refresh token and resend verification email routes --- backend/Input_validators/ValidateAuth.js | 51 +++++++++++++++++++++++- backend/routes/authRoutes.js | 8 ++-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/backend/Input_validators/ValidateAuth.js b/backend/Input_validators/ValidateAuth.js index 927cd83..962c31a 100644 --- a/backend/Input_validators/ValidateAuth.js +++ b/backend/Input_validators/ValidateAuth.js @@ -1,4 +1,5 @@ const { z } = require("zod"); +const { refreshToken } = require("../controllers/authController"); const registerUserZod = z.object({ name: z.string().min(4, "Length of the name should be minimum 4").trim(), @@ -49,7 +50,55 @@ const validateUserLogin = (req, res, next) => { } }; +const refreshTokenZod = z.object({ + refreshToken : z.string("Must be an string") +}) + +const validateRefreshToken = (req, res, next) => { + try { + refreshToken.parse(req.body); + next(); + } catch (err) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); + } +}; + +const resendVerificationZod = z.object({ + email : z.string().email() +}) + +const validateResendEmail = (req,res,next)=>{ + + try{ + + resendVerificationZod.parse(req.body); + next(); + + } + catch(err){ + + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); + + } +} + module.exports = { validateUserLogin, - validateUserSignup + validateUserSignup, + validateRefreshToken, + validateResendEmail } diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js index d7fb72a..5b99865 100644 --- a/backend/routes/authRoutes.js +++ b/backend/routes/authRoutes.js @@ -2,7 +2,7 @@ const express = require("express"); const { registerUser, loginUser, verifyEmail, resendVerificationEmail, getUserProfile, updateUserProfile, changePassword, deleteUserAccount, refreshToken, logoutUser } = require("../controllers/authController"); const { protect } = require("../middlewares/authMiddleware"); const { upload } = require("../middlewares/uploadMiddleware"); -const { validateUserLogin, validateUserSignup } = require("../Input_validators/ValidateAuth"); +const { validateUserLogin, validateUserSignup, validateRefreshToken, validateResendEmail } = require("../Input_validators/ValidateAuth"); const router = express.Router(); const { @@ -15,13 +15,13 @@ const { // Auth Routes router.post("/register", authLimiter, validateUserSignup, registerUser); router.post("/login", authLimiter, validateUserLogin, loginUser); -router.post("/refresh", authLimiter, refreshToken); -router.post("/logout", authLimiter, logoutUser); +router.post("/refresh", authLimiter,validateRefreshToken, refreshToken); +router.post("/logout", authLimiter, validateRefreshToken, logoutUser); router.get("/profile", protect, generalLimiter, getUserProfile); router.put("/profile", protect, generalLimiter, updateUserProfile); router.put("/change-password", protect, sensitiveAuthLimiter, changePassword); router.delete("/delete-account", protect, sensitiveAuthLimiter, deleteUserAccount); -router.post("/resend-verification", authLimiter, resendVerificationEmail); +router.post("/resend-verification", authLimiter, validateResendEmail, resendVerificationEmail); router.get("/verify-email", verifyEmail); /** From 8ad089ddd0b6638398caa85d2404c864f0eacac7 Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 15:41:55 +0530 Subject: [PATCH 05/10] Integrate Zod validation for saved achievements in saveAchievements route --- .../Input_validators/ValidateAchievement.js | 24 +++++++++++++++++++ backend/controllers/achievementController.js | 8 ------- backend/routes/achievementRoutes.js | 7 ++++-- 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 backend/Input_validators/ValidateAchievement.js diff --git a/backend/Input_validators/ValidateAchievement.js b/backend/Input_validators/ValidateAchievement.js new file mode 100644 index 0000000..d0610c8 --- /dev/null +++ b/backend/Input_validators/ValidateAchievement.js @@ -0,0 +1,24 @@ +const { z } = require('zod'); + +const savedAchievementsSchema = z.object({ + unlockedAchievements: z.array(z.string(), { + required_error: "unlockedAchievements must be an array", + invalid_type_error: "unlockedAchievements must be an array of strings", + }), +}); + +const validateSavedAchievements = (req, res, next) => { + try { + savedAchievementsSchema.parse(req.body); + next(); + } catch (e) { + return res.status(400).json({ + success: false, + error: e.errors?.[0]?.message || 'Invalid input', + }); + } +}; + +module.exports = { + validateSavedAchievements, +}; diff --git a/backend/controllers/achievementController.js b/backend/controllers/achievementController.js index ba18526..dfc4c99 100644 --- a/backend/controllers/achievementController.js +++ b/backend/controllers/achievementController.js @@ -14,14 +14,6 @@ exports.getAchievements = async (req, res) => { exports.saveAchievements = async (req, res) => { const { unlockedAchievements } = req.body; - // Must be a non-empty array - if (!Array.isArray(unlockedAchievements)) { - return res.status(400).json({ - success: false, - error: 'unlockedAchievements must be an array', - }); - } - // Reject any ID not in the server-side allowlist const unknown = unlockedAchievements.filter((id) => !VALID_ACHIEVEMENTS.has(id)); if (unknown.length > 0) { diff --git a/backend/routes/achievementRoutes.js b/backend/routes/achievementRoutes.js index 04181b1..eab7cd6 100644 --- a/backend/routes/achievementRoutes.js +++ b/backend/routes/achievementRoutes.js @@ -1,9 +1,12 @@ const express = require('express'); const { getAchievements, saveAchievements } = require('../controllers/achievementController'); const { protect } = require('../middlewares/authMiddleware'); +const { validateSavedAchievements } = require('../Input_validators/ValidateAchievement'); const router = express.Router(); -router.get('/achievements', protect, getAchievements); -router.post('/achievements', protect, saveAchievements); +router.use(protect); + +router.get('/achievements',getAchievements); +router.post('/achievements', validateSavedAchievements, saveAchievements); module.exports = router; \ No newline at end of file From 758289a7196e7c49666c2b2283714e2d724a8001 Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 16:17:08 +0530 Subject: [PATCH 06/10] Integrate Zod validation for AI request handling in interview questions, concept explanations, and interview tips --- backend/Input_validators/ValidateAi.js | 76 ++++++++++++++++++++++++++ backend/controllers/aiController.js | 11 ---- backend/server.js | 14 ++++- 3 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 backend/Input_validators/ValidateAi.js diff --git a/backend/Input_validators/ValidateAi.js b/backend/Input_validators/ValidateAi.js new file mode 100644 index 0000000..076bd0a --- /dev/null +++ b/backend/Input_validators/ValidateAi.js @@ -0,0 +1,76 @@ +const { z } = require("zod"); + +// Schema for interview questions request +const generateInterviewQuestionsSchema = z.object({ + role: z.string().min(1, "Role is required"), + experience: z.string().min(1, "Experience is required"), + topicsToFocus: z.array(z.string()).min(1, "At least one topic is required"), + numberOfQuestions: z.number().int().positive("Number of questions must be positive"), +}); + +// Schema for concept explanation request +const generateConceptExplanationSchema = z.object({ + question: z.string().min(1, "Question is required"), +}); + +// Schema for interview tips request +const generateInterviewTipsSchema = z.object({ + role: z.string().min(1, "Role is required"), + experience: z.string().min(1, "Experience is required"), +}); + + +const validateGenerateInterviewQuestions = (req,res,next)=>{ + + try { + generateInterviewQuestionsSchema.parse(req.body); + next(); + } catch (error) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); + } +} + +const validateGenerateConceptExplanation = (req,res,next)=>{ + + try { + generateConceptExplanationSchema.parse(req.body); + next(); + } catch (error) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: err.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }) +}} + +const validateGenerateInterviewTips = (req, res, next) => { + try { + generateInterviewTipsSchema.parse(req.body); + next(); + } catch (error) { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: error.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); + } +}; + +module.exports = { + validateGenerateInterviewQuestions, + validateGenerateConceptExplanation, + validateGenerateInterviewTips, +}; \ No newline at end of file diff --git a/backend/controllers/aiController.js b/backend/controllers/aiController.js index 9807743..69ac27a 100644 --- a/backend/controllers/aiController.js +++ b/backend/controllers/aiController.js @@ -39,10 +39,6 @@ const generateInterviewQuestions = async (req, res) => { try { const { role, experience, topicsToFocus, numberOfQuestions } = req.body; - if (!role || !experience || !topicsToFocus || !numberOfQuestions) { - return res.status(400).json({ message: "Missing required fields" }); - } - // Fetch questions the user has already seen for this role + topic const pastSessions = await Session.find({ user: req.user._id, @@ -161,9 +157,6 @@ const generateInterviewQuestions = async (req, res) => { const generateConceptExplanation = async (req, res) => { try { const { question } = req.body; - if (!question) { - return res.status(400).json({ message: "Missing question" }); - } const prompt = conceptExplainPrompt(question); @@ -233,10 +226,6 @@ const generateInterviewTips = async (req, res) => { try { const { role, experience } = req.body; - if (!role || !experience) { - return res.status(400).json({ message: "Missing required fields" }); - } - const prompt = interviewTipsPrompt({ role, experience }); const candidateModels = [ diff --git a/backend/server.js b/backend/server.js index 2c287c5..1397eb0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -104,20 +104,26 @@ const achievementRoutes = require("./routes/achievementRoutes"); app.use("/api/user", generalLimiter, achievementRoutes); const booksRoutes = require("./routes/booksRoutes"); const { required } = require("joi"); +const { validateGenerateInterviewQuestions, validateGenerateConceptExplanation, validateGenerateInterviewTips } = require("./Input_validators/ValidateAi.js"); app.use("/api/resume", generalLimiter, resumeRoutes); + +// AI routes with Zod validation app.use( "/api/ai/generate-questions", sensitiveRouteHeaders, aiLimiter, protect, - generateInterviewQuestions, + validateGenerateInterviewQuestions, // Zod validator + generateInterviewQuestions // Controller ); + app.use( "/api/ai/generate-explanation", sensitiveRouteHeaders, aiLimiter, protect, - generateConceptExplanation, + validateGenerateConceptExplanation, // Zod validator + generateConceptExplanation // Controller ); app.use( @@ -125,9 +131,11 @@ app.use( sensitiveRouteHeaders, aiLimiter, protect, - generateInterviewTips, + validateGenerateInterviewTips, // Zod validator + generateInterviewTips // Controller ); + app.use("/api/books", generalLimiter, booksRoutes); //Serve uploads folder From c0b69284fc93ca26fe95d4ba9d56311277c65da2 Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 16:32:10 +0530 Subject: [PATCH 07/10] Integrate Zod validation for question management routes and add validation middleware --- .../Input_validators/ValidateAchievement.js | 6 +- backend/Input_validators/ValidateAi.js | 28 ++------ backend/Input_validators/ValidateAuth.js | 13 +--- backend/Input_validators/ValidateQuestions.js | 72 +++++++++++++++++++ backend/controllers/questionController.js | 9 +-- backend/routes/questionRoutes.js | 9 +-- 6 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 backend/Input_validators/ValidateQuestions.js diff --git a/backend/Input_validators/ValidateAchievement.js b/backend/Input_validators/ValidateAchievement.js index d0610c8..062217b 100644 --- a/backend/Input_validators/ValidateAchievement.js +++ b/backend/Input_validators/ValidateAchievement.js @@ -1,4 +1,5 @@ const { z } = require('zod'); +const { handleValidationError } = require('./ValidateQuestions'); const savedAchievementsSchema = z.object({ unlockedAchievements: z.array(z.string(), { @@ -12,10 +13,7 @@ const validateSavedAchievements = (req, res, next) => { savedAchievementsSchema.parse(req.body); next(); } catch (e) { - return res.status(400).json({ - success: false, - error: e.errors?.[0]?.message || 'Invalid input', - }); + return handleValidationError(res, e); } }; diff --git a/backend/Input_validators/ValidateAi.js b/backend/Input_validators/ValidateAi.js index 076bd0a..6b36d79 100644 --- a/backend/Input_validators/ValidateAi.js +++ b/backend/Input_validators/ValidateAi.js @@ -1,4 +1,5 @@ const { z } = require("zod"); +const { handleValidationError } = require("./ValidateQuestions"); // Schema for interview questions request const generateInterviewQuestionsSchema = z.object({ @@ -26,14 +27,7 @@ const validateGenerateInterviewQuestions = (req,res,next)=>{ generateInterviewQuestionsSchema.parse(req.body); next(); } catch (error) { - return res.status(400).json({ - success: false, - message: "Validation failed", - errors: err.errors.map(e => ({ - field: e.path.join("."), - message: e.message, - })), - }); + return handleValidationError(res, error); } } @@ -43,14 +37,7 @@ const validateGenerateConceptExplanation = (req,res,next)=>{ generateConceptExplanationSchema.parse(req.body); next(); } catch (error) { - return res.status(400).json({ - success: false, - message: "Validation failed", - errors: err.errors.map(e => ({ - field: e.path.join("."), - message: e.message, - })), - }) + return handleValidationError(res, error); }} const validateGenerateInterviewTips = (req, res, next) => { @@ -58,14 +45,7 @@ const validateGenerateInterviewTips = (req, res, next) => { generateInterviewTipsSchema.parse(req.body); next(); } catch (error) { - return res.status(400).json({ - success: false, - message: "Validation failed", - errors: error.errors.map(e => ({ - field: e.path.join("."), - message: e.message, - })), - }); + return handleValidationError(res, error); } }; diff --git a/backend/Input_validators/ValidateAuth.js b/backend/Input_validators/ValidateAuth.js index 962c31a..659b5af 100644 --- a/backend/Input_validators/ValidateAuth.js +++ b/backend/Input_validators/ValidateAuth.js @@ -1,5 +1,5 @@ const { z } = require("zod"); -const { refreshToken } = require("../controllers/authController"); +const { handleValidationError } = require("./ValidateQuestions"); const registerUserZod = z.object({ name: z.string().min(4, "Length of the name should be minimum 4").trim(), @@ -39,14 +39,7 @@ const validateUserLogin = (req, res, next) => { loginUserZod.parse(req.body); next(); } catch (err) { - return res.status(400).json({ - success: false, - message: "Validation failed", - errors: err.errors.map(e => ({ - field: e.path.join("."), - message: e.message, - })), - }); + return handleValidationError(res, error); } }; @@ -56,7 +49,7 @@ const refreshTokenZod = z.object({ const validateRefreshToken = (req, res, next) => { try { - refreshToken.parse(req.body); + refreshTokenZod.parse(req.body); next(); } catch (err) { return res.status(400).json({ diff --git a/backend/Input_validators/ValidateQuestions.js b/backend/Input_validators/ValidateQuestions.js new file mode 100644 index 0000000..ce15de6 --- /dev/null +++ b/backend/Input_validators/ValidateQuestions.js @@ -0,0 +1,72 @@ +const { z } = require("zod"); + +// Schema for adding questions to a session +const addQuestionToSessionSchema = z.object({ + sessionId: z.string().min(1, "Session ID is required"), + questions: z.array( + z.object({ + question: z.string().min(1, "Question text is required"), + answer: z.string().min(1, "Answer text is required"), + }) + ).min(1, "At least one question is required"), +}); + +// Schema for toggling pin (params only) +const togglePinQuestionSchema = z.object({ + id: z.string().min(1, "Question ID is required"), +}); + +// Schema for updating note +const updateQuestionNoteSchema = z.object({ + note: z.string().min(1, "Note cannot be empty"), +}); + + +// Helper for consistent error responses +const handleValidationError = (res, error) => { + return res.status(400).json({ + success: false, + message: "Validation failed", + errors: error.errors.map(e => ({ + field: e.path.join("."), + message: e.message, + })), + }); +}; + +// Middleware for addQuestionToSession +const validateAddQuestionToSession = (req, res, next) => { + try { + addQuestionToSessionSchema.parse(req.body); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for togglePinQuestion (params) +const validateTogglePinQuestion = (req, res, next) => { + try { + togglePinQuestionSchema.parse(req.params); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for updateQuestionNote +const validateUpdateQuestionNote = (req, res, next) => { + try { + updateQuestionNoteSchema.parse(req.body); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +module.exports = { + validateAddQuestionToSession, + validateTogglePinQuestion, + validateUpdateQuestionNote, + handleValidationError +}; diff --git a/backend/controllers/questionController.js b/backend/controllers/questionController.js index 88822fa..3ecc182 100644 --- a/backend/controllers/questionController.js +++ b/backend/controllers/questionController.js @@ -23,9 +23,7 @@ const Session = require("../models/Session"); const addQuestionToSession = async (req, res) => { try { const { sessionId, questions } = req.body; - if (!sessionId || !questions || !Array.isArray(questions)) { - return res.status(400).json({ success: false, message: "Invalid or missing input data provided" }); - } + const session = await Session.findById(sessionId); if (!session) { @@ -67,11 +65,6 @@ const addQuestionToSession = async (req, res) => { const togglePinQuestion = async (req, res) => { try { const question = await Question.findById(req.params.id); - if (!question) { - return res - .status(404) - .json({ success: false, message: "Question not found" }); - } const session = await Session.findById(question.session); if (!session) { diff --git a/backend/routes/questionRoutes.js b/backend/routes/questionRoutes.js index bf1da6d..dad0cc2 100644 --- a/backend/routes/questionRoutes.js +++ b/backend/routes/questionRoutes.js @@ -3,30 +3,31 @@ const {togglePinQuestion, updateQuestionNote,addQuestionToSession} = require(".. const {protect} = require("../middlewares/authMiddleware"); const { generalLimiter } = require("../middlewares/rateLimiter"); +const { validateAddQuestionToSession, validateTogglePinQuestion, validateUpdateQuestionNote } = require('../Input_validators/ValidateQuestions'); const router = express.Router(); /** * Apply rate limiter to all question routes. */ -router.use(generalLimiter); +router.use(generalLimiter, protect); /** * Add new questions to an existing session. * @route POST /api/question/add */ -router.post('/add',protect,addQuestionToSession); +router.post('/add',validateAddQuestionToSession, addQuestionToSession); /** * Toggle pin state for a specific question. * @route POST /api/question/:id/pin */ -router.post('/:id/pin',protect,togglePinQuestion); +router.post('/:id/pin',validateTogglePinQuestion,togglePinQuestion); /** * Update the note field for a specific question. * @route POST /api/question/:id/note */ -router.post('/:id/note', protect, updateQuestionNote); +router.post('/:id/note', validateUpdateQuestionNote, updateQuestionNote); module.exports = router; \ No newline at end of file From f0d1843bf7231bc20b626bc95561fdb95ef4b726 Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 16:46:40 +0530 Subject: [PATCH 08/10] Integrate Zod validation for resume-related routes and add validation middleware --- backend/Input_validators/ValidateResume.js | 60 ++++++++++++++++++++++ backend/controllers/resumeController.js | 11 +--- backend/routes/resumeRoutes.js | 11 ++-- 3 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 backend/Input_validators/ValidateResume.js diff --git a/backend/Input_validators/ValidateResume.js b/backend/Input_validators/ValidateResume.js new file mode 100644 index 0000000..761d9f0 --- /dev/null +++ b/backend/Input_validators/ValidateResume.js @@ -0,0 +1,60 @@ +const { z } = require("zod"); +const { handleValidationError } = require('./ValidateQuestions') + +// Schema for compileResume request +const compileResumeSchema = z.object({ + code: z.string().min(1, "LaTeX code is required"), +}); + +// Schema for analyzeResume request +const analyzeResumeSchema = z.object({ + targetRole: z.string().min(1, "Target role is required").optional(), +}); + +// Schema for saveResume request +const saveResumeSchema = z.object({ + title: z.string().min(1, "Title is required"), + latexCode: z.string().min(1, "LaTeX code is required"), + resumeId: z.string().optional(), +}); + + +// Middleware for compileResume +const validateCompileResume = (req, res, next) => { + try { + compileResumeSchema.parse(req.body); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for analyzeResume +const validateAnalyzeResume = (req, res, next) => { + try { + analyzeResumeSchema.parse(req.body); + // also ensure file is uploaded + if (!req.file) { + return res.status(400).json({ success: false, message: "No resume file uploaded" }); + } + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for saveResume +const validateSaveResume = (req, res, next) => { + try { + saveResumeSchema.parse(req.body); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +module.exports = { + validateCompileResume, + validateAnalyzeResume, + validateSaveResume, +}; \ No newline at end of file diff --git a/backend/controllers/resumeController.js b/backend/controllers/resumeController.js index 7bb9ceb..115444c 100644 --- a/backend/controllers/resumeController.js +++ b/backend/controllers/resumeController.js @@ -20,10 +20,7 @@ const { GoogleGenerativeAI } = require('@google/generative-ai'); const compileResume = async (req, res) => { try { const { code } = req.body; - if (!code) { - return res.status(400).json({ message: "No LaTeX code provided" }); - } - + // Normalize line endings to UNIX format, as texlive.net is highly sensitive to Windows \r\n const cleanCode = code.replace(/\r\n/g, '\n'); @@ -205,13 +202,9 @@ const Resume = require("../models/Resume"); */ const saveResume = async (req, res) => { try { - const { title, latexCode, resumeId } = req.body; + const userId = req.user._id || req.user.id; - if (!title || !latexCode) { - return res.status(400).json({ success: false, message: "Title and LaTeX code are required." }); - } - let resume; if (resumeId) { resume = await Resume.findOneAndUpdate( diff --git a/backend/routes/resumeRoutes.js b/backend/routes/resumeRoutes.js index f3fdf08..67cb05d 100644 --- a/backend/routes/resumeRoutes.js +++ b/backend/routes/resumeRoutes.js @@ -4,25 +4,28 @@ const { compileResume, analyzeResume, saveResume, getMyResumes } = require('../c const { protect } = require('../middlewares/authMiddleware'); const { upload, uploadResume } = require('../middlewares/uploadMiddleware'); const { aiLimiter } = require('../middlewares/rateLimiter'); +const { validateCompileResume, validateAnalyzeResume, validateSaveResume } = require('../Input_validators/ValidateResume'); + +router.use(protect); // @route POST /api/resume/compile // @desc Compile LaTeX code to PDF // @access Private — requires auth; aiLimiter caps external texlive.net calls to 20/hr per IP -router.post('/compile', protect, aiLimiter, compileResume); +router.post('/compile', aiLimiter,validateCompileResume, compileResume); // @route POST /api/resume/analyze // @desc Analyze resume using Gemini API // @access Private — requires auth; aiLimiter caps Gemini API calls to 20/hr per IP -router.post('/analyze', protect, aiLimiter, uploadResume.single("resume"), analyzeResume); +router.post('/analyze', aiLimiter, uploadResume.single("resume"), validateAnalyzeResume, analyzeResume); // @route POST /api/resume/save // @desc Save or update a resume // @access Private -router.post('/save', protect, saveResume); +router.post('/save', validateSaveResume, saveResume); // @route GET /api/resume/my-resumes // @desc Get all saved resumes for logged-in user // @access Private -router.get('/my-resumes', protect, getMyResumes); +router.get('/my-resumes', getMyResumes); module.exports = router; \ No newline at end of file From ee87601b3ff232a3b4852d6791b0b30cabc142af Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 16:51:46 +0530 Subject: [PATCH 09/10] Integrate Zod validation for session management routes and add validation middleware --- backend/Input_validators/ValidateSession.js | 63 +++++++++++++++++++++ backend/routes/sessionRoutes.js | 11 ++-- 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 backend/Input_validators/ValidateSession.js diff --git a/backend/Input_validators/ValidateSession.js b/backend/Input_validators/ValidateSession.js new file mode 100644 index 0000000..a0c2335 --- /dev/null +++ b/backend/Input_validators/ValidateSession.js @@ -0,0 +1,63 @@ +const { z } = require("zod"); +const { handleValidationError } = require("./ValidateQuestions"); + +// Schema for creating a session +const createSessionSchema = z.object({ + role: z.string().min(1, "Role is required"), + experience: z.string().min(1, "Experience is required"), + topicsToFocus: z.array(z.string()).min(1, "At least one topic is required"), + description: z.string().optional(), + question: z.array( + z.object({ + question: z.string().min(1, "Question text is required"), + answer: z.string().min(1, "Answer text is required"), + }) + ).optional(), +}); + +// Schema for getting a session by ID (params) +const getSessionByIdSchema = z.object({ + id: z.string().min(1, "Session ID is required"), +}); + +// Schema for deleting a session (params) +const deleteSessionSchema = z.object({ + id: z.string().min(1, "Session ID is required"), +}); + + +// Middleware for createSession +const validateCreateSession = (req, res, next) => { + try { + createSessionSchema.parse(req.body); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for getSessionById +const validateGetSessionById = (req, res, next) => { + try { + getSessionByIdSchema.parse(req.params); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for deleteSession +const validateDeleteSession = (req, res, next) => { + try { + deleteSessionSchema.parse(req.params); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +module.exports = { + validateCreateSession, + validateGetSessionById, + validateDeleteSession, +}; diff --git a/backend/routes/sessionRoutes.js b/backend/routes/sessionRoutes.js index ff47953..c0f131d 100644 --- a/backend/routes/sessionRoutes.js +++ b/backend/routes/sessionRoutes.js @@ -6,6 +6,7 @@ const { deleteSession, } = require("../controllers/sessionController"); const { protect } = require("../middlewares/authMiddleware"); +const { validateCreateSession, validateGetSessionById, validateDeleteSession } = require("../Input_validators/ValidateSession"); const router = express.Router(); @@ -13,24 +14,26 @@ const router = express.Router(); * Create a new user session. * @route POST /api/sessions/create */ -router.post("/create", protect, createSession); + +router.use(protect); +router.post("/create", validateCreateSession, createSession); /** * Get all sessions for the authenticated user. * @route GET /api/sessions/my-sessions */ -router.get("/my-sessions", protect, getMySessions); +router.get("/my-sessions", getMySessions); /** * Get a session by its ID. * @route GET /api/sessions/:id */ -router.get("/:id", protect, getSessionById); +router.get("/:id", validateGetSessionById, getSessionById); /** * Delete a session by its ID. * @route DELETE /api/sessions/:id */ -router.delete("/:id", protect, deleteSession); +router.delete("/:id", validateDeleteSession, deleteSession); module.exports = router; From e6e64bd76862eb2053e3122ae197510e30d8e0da Mon Sep 17 00:00:00 2001 From: anandayush005 Date: Sat, 4 Jul 2026 16:56:51 +0530 Subject: [PATCH 10/10] Integrate Zod validation for user sheet progress routes and add validation middleware --- .../ValidateUserSheetProgress.js | 42 +++++++++++++++++++ backend/routes/userSheetProgressRoutes.js | 9 ++-- 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 backend/Input_validators/ValidateUserSheetProgress.js diff --git a/backend/Input_validators/ValidateUserSheetProgress.js b/backend/Input_validators/ValidateUserSheetProgress.js new file mode 100644 index 0000000..83642a0 --- /dev/null +++ b/backend/Input_validators/ValidateUserSheetProgress.js @@ -0,0 +1,42 @@ +const { z } = require("zod"); +const { handleValidationError } = require("./ValidateQuestions"); + +// Schema for saving/updating sheet progress +const saveProgressSchema = z.object({ + sheetId: z.string().min(1, "Sheet ID is required"), + followed: z.boolean().optional(), + completedTopics: z.array(z.string()).optional(), + percentage: z.number().int().min(0, "Percentage must be at least 0").max(100, "Percentage cannot exceed 100"), +}); + +// Schema for getting progress by sheetId (params) +const getProgressSchema = z.object({ + sheetId: z.string().min(1, "Sheet ID is required"), +}); + + + +// Middleware for saveProgress +const validateSaveProgress = (req, res, next) => { + try { + saveProgressSchema.parse(req.body); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +// Middleware for getProgress (params) +const validateGetProgress = (req, res, next) => { + try { + getProgressSchema.parse(req.params); + next(); + } catch (error) { + return handleValidationError(res, error); + } +}; + +module.exports = { + validateSaveProgress, + validateGetProgress +} \ No newline at end of file diff --git a/backend/routes/userSheetProgressRoutes.js b/backend/routes/userSheetProgressRoutes.js index c542c02..c202302 100644 --- a/backend/routes/userSheetProgressRoutes.js +++ b/backend/routes/userSheetProgressRoutes.js @@ -1,6 +1,7 @@ const express = require('express'); const { saveProgress, getProgress } = require('../controllers/userSheetProgressController'); const { protect } = require('../middlewares/authMiddleware'); +const { validateSaveProgress, validateGetProgress } = require('../Input_validators/ValidateUserSheetProgress'); const router = express.Router(); @@ -8,18 +9,20 @@ const router = express.Router(); * Save or update progress for a user sheet. * @route POST /api/user/sheet-progress */ -router.post('/sheet-progress', protect, saveProgress); + +router.use(protect); +router.post('/sheet-progress', validateSaveProgress, saveProgress); /** * Get progress for a specific user sheet. * @route GET /api/user/sheet-progress/:sheetId */ -router.get('/sheet-progress/:sheetId', protect, getProgress); +router.get('/sheet-progress/:sheetId', validateGetProgress, getProgress); /** * Get all sheet progress records for the authenticated user. * @route GET /api/user/sheet-progress */ -router.get('/sheet-progress', protect, require('../controllers/userSheetProgressController').getAllProgress); +router.get('/sheet-progress', require('../controllers/userSheetProgressController').getAllProgress); module.exports = router;