diff --git a/server/middleware/validate.js b/server/middleware/validate.js new file mode 100644 index 0000000..e69de29 diff --git a/server/middleware/verifyServerRole.js b/server/middleware/verifyServerRole.js new file mode 100644 index 0000000..6752852 --- /dev/null +++ b/server/middleware/verifyServerRole.js @@ -0,0 +1,41 @@ +import jwt from "jsonwebtoken"; +import { checkServerInUser } from "../services/serverService.js"; +//checking jwt +export const verifyServerRole = async (req, res, next) => { + let decoded; + try { + const token = req.headers["x-auth-token"]; + if (!token) { + return res + .status(401) + .json({ status: 401, message: "Authentication required" }); + } + decoded = jwt.verify(token, process.env.ACCESS_TOKEN); + } catch (err) { + return res + .status(401) + .json({ status: 401, message: "Invalid or expired token" }); + } + //checking server membership + const { server_id } = req.body; + let membership; + try { + const result = await checkServerInUser(decoded.id, server_id); + membership = result?.[0].servers?.[0]; + } catch (err) { + return res + .status(500) + .json({ status: 500, message: "server error during membership check" }); + } + //check role + if (!["owner", "admin"].includes(membership.server_role)) { + return res.status(403).json({ + status: 403, + message: "You don't have permission to perform this action", + }); + } + //if all pass then we come here. + req.user = decoded; + req.serverMembership = membership; + next(); +}; diff --git a/server/package.json b/server/package.json index f28c40c..1d4d7f0 100644 --- a/server/package.json +++ b/server/package.json @@ -14,6 +14,7 @@ "cors": "^2.8.5", "dotenv": "^16.0.2", "express": "^4.18.1", + "express-validator": "^7.3.2", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.1", "nanoid": "^5.1.6", diff --git a/server/routes/servers.js b/server/routes/servers.js index 56f5435..88034a1 100644 --- a/server/routes/servers.js +++ b/server/routes/servers.js @@ -1,6 +1,10 @@ import express from "express"; import jwt from "jsonwebtoken"; import mongoose from "mongoose"; +//new imports +import { body } from "express-validator"; +import { validate } from "../middleware/validate.js"; +import { verifyServerRole } from "../middleware/verifyServerRole.js"; import Server from "../models/Server.js"; import User from "../models/User.js"; @@ -15,21 +19,91 @@ import { getIO } from "../socket/runtime.js"; const router = express.Router(); -router.post("/create_server", async (req, res) => { +//rules for /create_server + +const createServerRules = [ + body("server_details").notEmpty().withMessage("server_details is required"), + body("server_details.server_name") + .notEmpty() + .withMessage("server_details.server_name is required") + .trim() + .isLength({ max: 100 }) + .withMessage("server_name must be 100 characters or fewer"), +]; + +//rules for /add_new_channel +const addChannelRules = [ + body("server_id") + .notEmpty() + .withMessage("server_id is required") + .isMongoId() + .withMessage("server_id must be a valid MongoDB ObjectId"), + body("category_id") + .notEmpty() + .withMessage("category_id is required") + .isMongoId() + .withMessage("category_id must be a valid MongoDB ObjectId"), + body("channel_name") + .notEmpty() + .withMessage("channel_name is required") + .trim() + .isLength({ max: 100 }) + .withMessage("channel_name must be 100 characters or fewer"), + body("channel_type") + .notEmpty() + .withMessage("channel_type is required") + .isIn(["text", "voice"]) + .withMessage("channel_type must be 'text' or 'voice'"), +]; + +//rules for /add_new_category + +const addCategoryRules = [ + body("server_id") + .notEmpty() + .withMessage("server_id is required") + .isMongoId() + .withMessage("server_id must be a valid MongoDB ObjectId"), + body("category_name") + .notEmpty() + .withMessage("category_name is required") + .trim() + .isLength({ max: 100 }) + .withMessage("category_name must be 100 characters or fewer"), +]; + +//rules for /delete_server and /leave_server + +const serverIdRules = [ + body("server_id") + .notEmpty() + .withMessage("server_id is required") + .isMongoId() + .withMessage("server_id must be a valid MongoDB ObjectId"), +]; + +//rules for server_info +const serverInfoRules = [ + body("server_id") + .notEmpty() + .withMessage("server_id is required") + .isMongoId() + .withMessage("server_id must be a valid MongoDB ObjectId"), +]; + +router.post("/create_server", validate(createServerRules), async (req, res) => { let user_id; try { - user_id = jwt.verify( - req.headers["x-auth-token"], - process.env.ACCESS_TOKEN - ); + user_id = jwt.verify(req.headers["x-auth-token"], process.env.ACCESS_TOKEN); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); } + //safe details are only sent const serverTemplate = await createServerFromTemplate( user_id, req.body.server_details, - req.body.server_image + req.body.server_image, ); const addNewChat = await createChat(serverTemplate.server_id); @@ -40,7 +114,7 @@ router.post("/create_server", async (req, res) => { const addServer = await addServerToUser( user_id.id, serverTemplate, - req.body.server_details.role + req.body.server_details.role, ); if (addServer) { @@ -56,116 +130,139 @@ router.post("/create_server", async (req, res) => { } }); -router.post("/server_info", async (req, res) => { +router.post("/server_info", validate(serverInfoRules), async (req, res) => { const { server_id } = req.body; let user_id; try { - user_id = jwt.verify( - req.headers["x-auth-token"], - process.env.ACCESS_TOKEN - ); + user_id = jwt.verify(req.headers["x-auth-token"], process.env.ACCESS_TOKEN); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); } const response = await checkServerInUser(user_id.id, server_id); - if (!response || !response[0] || !response[0].servers || response[0].servers.length === 0) { + if ( + !response || + !response[0] || + !response[0].servers || + response[0].servers.length === 0 + ) { return res.json({ status: 404, message: "you are not authorized" }); } - const serverInfo = await Server.find({ _id: new mongoose.Types.ObjectId(server_id), }); res.json(serverInfo); }); -router.post("/add_new_channel", async (req, res) => { - const { category_id, channel_name, channel_type, server_id } = req.body; - const newChannel = { - $push: { - "categories.$.channels": { - channel_name, - channel_type, - }, - }, - }; - try { - const data = await Server.updateOne( - { - _id: new mongoose.Types.ObjectId(server_id), - "categories._id": new mongoose.Types.ObjectId(category_id), +router.post( + "/add_new_channel", + validate(addChannelRules), + verifyServerRole, + async (req, res) => { + const { category_id, channel_name, channel_type, server_id } = req.body; + + // safety ensured + const newChannel = { + $push: { + "categories.$.channels": { + channel_name, + channel_type, + }, }, - newChannel - ); - if (data && data.modifiedCount > 0) { - const io = getIO(); - if (io) { - io.to(`server:${String(server_id)}`).emit("server_updated", { - server_id: String(server_id), - reason: "channel_created", - }); + }; + try { + const data = await Server.updateOne( + { + _id: new mongoose.Types.ObjectId(server_id), + "categories._id": new mongoose.Types.ObjectId(category_id), + }, + newChannel, + ); + if (data && data.modifiedCount > 0) { + const io = getIO(); + if (io) { + io.to(`server:${String(server_id)}`).emit("server_updated", { + server_id: String(server_id), + reason: "channel_created", + }); + } + return res.json({ status: 200 }); } - return res.json({ status: 200 }); + //404 added + return res + .status(404) + .json({ status: 404, message: "Server or category not found" }); + } catch (err) { + return res.status(500).json({ status: 500, message: "Server error" }); } - return res.status(500).json({ status: 500, message: "Update failed" }); - } catch (err) { - return res.status(500).json({ status: 500, message: "Server error" }); - } -}); + }, +); -router.post("/add_new_category", async (req, res) => { - const { category_name, server_id } = req.body; - const newCategory = { - $push: { categories: { category_name, channels: [] } }, - }; - try { - const data = await Server.updateOne( - { _id: new mongoose.Types.ObjectId(server_id) }, - newCategory - ); - if (data && data.modifiedCount > 0) { - const io = getIO(); - if (io) { - io.to(`server:${String(server_id)}`).emit("server_updated", { - server_id: String(server_id), - reason: "category_created", - }); +router.post( + "/add_new_category", + validate(addCategoryRules), + verifyServerRole, + async (req, res) => { + const { category_name, server_id } = req.body; + + // safety ensured + const newCategory = { + $push: { categories: { category_name, channels: [] } }, + }; + try { + const data = await Server.updateOne( + { _id: new mongoose.Types.ObjectId(server_id) }, + newCategory, + ); + if (data && data.modifiedCount > 0) { + const io = getIO(); + if (io) { + io.to(`server:${String(server_id)}`).emit("server_updated", { + server_id: String(server_id), + reason: "category_created", + }); + } + return res.json({ status: 200 }); } - return res.json({ status: 200 }); + //changed to 404 to be more specific + return res.status(404).json({ status: 404, message: "Server not found" }); + } catch (err) { + return res.status(500).json({ status: 500, message: "Server error" }); } - return res.status(500).json({ status: 500, message: "Update failed" }); - } catch (err) { - return res.status(500).json({ status: 500, message: "Server error" }); - } -}); + }, +); -router.post("/delete_server", async (req, res) => { - const { server_id } = req.body; - try { - const data = await Server.updateOne( - { _id: server_id }, - { $set: { active: false } } - ); - if (!data || data.modifiedCount <= 0) { - return res.status(404).json({ status: 404, message: "Not found" }); - } +// CHANGED: added validate(serverIdRules) before handler +router.post( + "/delete_server", + validate(serverIdRules), + verifyServerRole, + async (req, res) => { + const { server_id } = req.body; + try { + const data = await Server.updateOne( + { _id: server_id }, + { $set: { active: false } }, + ); + if (!data || data.modifiedCount <= 0) { + return res.status(404).json({ status: 404, message: "Not found" }); + } - const deleteFromUser = { $pull: { servers: { server_id } } }; - await User.updateMany({ "servers.server_id": server_id }, deleteFromUser); - return res.json({ status: 200 }); - } catch (err) { - return res.status(500).json({ status: 500, message: "Server error" }); - } -}); + const deleteFromUser = { $pull: { servers: { server_id } } }; + await User.updateMany({ "servers.server_id": server_id }, deleteFromUser); + return res.json({ status: 200 }); + } catch (err) { + return res.status(500).json({ status: 500, message: "Server error" }); + } + }, +); -router.post("/leave_server", async (req, res) => { +// CHANGED: added validate(serverIdRules) before handler +router.post("/leave_server", validate(serverIdRules), async (req, res) => { const { server_id } = req.body; let user_id; try { - user_id = jwt.verify( - req.headers["x-auth-token"], - process.env.ACCESS_TOKEN - ); + user_id = jwt.verify(req.headers["x-auth-token"], process.env.ACCESS_TOKEN); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); } @@ -174,7 +271,10 @@ router.post("/leave_server", async (req, res) => { try { await User.updateOne({ _id: user_id.id }, leaveServer); const deleteUserFromServer = { $pull: { users: { user_id: user_id.id } } }; - const data2 = await Server.updateOne({ _id: server_id }, deleteUserFromServer); + const data2 = await Server.updateOne( + { _id: server_id }, + deleteUserFromServer, + ); if (data2 && data2.modifiedCount > 0) { const io = getIO(); if (io) {