From b982ac4f4f5b9b224a5607daecdd5f4dd4c9ec9d Mon Sep 17 00:00:00 2001 From: ksantoso2 <130810746+ksantoso2@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:19:41 -0800 Subject: [PATCH 1/3] protect routes --- backend/src/routes/HousingRoutes.ts | 16 +- backend/src/routes/ReviewsRoutes.ts | 278 ++++++++++++------------ backend/src/routes/admin/PagesRoutes.ts | 36 ++- backend/src/routes/admin/StaffRoutes.ts | 91 +++++--- 4 files changed, 244 insertions(+), 177 deletions(-) diff --git a/backend/src/routes/HousingRoutes.ts b/backend/src/routes/HousingRoutes.ts index 8661896d..7c19fbd3 100644 --- a/backend/src/routes/HousingRoutes.ts +++ b/backend/src/routes/HousingRoutes.ts @@ -8,9 +8,7 @@ import { import { housingReviewPictures } from '../server'; import { ObjectId } from 'mongodb'; import { - isAdmin, isAuthenticated, - isCourseReviewOwner, isHousingReviewOwner, } from '../middleware/authMiddleware'; @@ -22,7 +20,7 @@ const upload = multer({ storage: storage }); /** * @route GET /api/campus/housing * @desc Get all housing buildings - * @access Public + * @access isAuthenticated */ router.get('/', isAuthenticated, async (req: Request, res: Response) => { try { @@ -36,7 +34,7 @@ router.get('/', isAuthenticated, async (req: Request, res: Response) => { /** * @route GET /api/campus/housing/:building * @desc Get housing building by id - * @access Public + * @access isAuthenticated */ router.get( '/:building', @@ -72,7 +70,7 @@ router.get( /** * @route GET /campus/housing/:building/rooms * @desc Get all roms in a building (by building id) - * @access Public + * @access isAuthenticated */ router.get( '/:building/rooms', @@ -108,7 +106,7 @@ router.get( /** * @route GET /api/campus/:room/reviews * @desc Get housing reviews for a room - * @access Public + * @access isAuthenticated */ router.get( '/:room/reviews', @@ -195,7 +193,7 @@ router.get( /** * @route GET /api/campus/housing/:buildingId/:roomNumber/reviews * @desc Get reviews for a room by building id and room number - * @access Public + * @access isAuthenticated */ router.get( '/:buildingId/:roomNumber/reviews', @@ -285,7 +283,7 @@ router.get( /** * @route POST /api/campus/housing/:buildingId/:roomNumber/reviews * @desc Add new housing room review - * @access Public + * @access isAuthenticated */ router.post( '/:buildingId/:roomNumber/reviews', @@ -509,7 +507,7 @@ router.delete( /** * @route GET /api/campus/housing/review_pictures/:id * @desc Get review picture by id - * @access Public + * @access isAuthenticated */ router.get( '/review_pictures/:id', diff --git a/backend/src/routes/ReviewsRoutes.ts b/backend/src/routes/ReviewsRoutes.ts index 236a73cc..8a4f4035 100644 --- a/backend/src/routes/ReviewsRoutes.ts +++ b/backend/src/routes/ReviewsRoutes.ts @@ -1,167 +1,179 @@ import express, { Request, Response, Router } from 'express'; import { CourseReviews } from '../models/Courses'; +import { + isAuthenticated, + isCourseReviewOwner, +} from '../middleware/authMiddleware'; const router: Router = express.Router(); /** * @route GET /api/reviews/:id * @desc Get review by ID - * @access Public + * @access isAuthenticated */ -router.get('/reviews/:id', async (req: Request, res: Response) => { - try { - const reviewId = parseInt(req.params.id); - - // Check if conversion is valid - if (isNaN(reviewId)) { - res.status(400).json({ message: 'Invalid review ID format' }); - return; +router.get( + '/reviews/:id', + isAuthenticated, + async (req: Request, res: Response) => { + try { + const reviewId = parseInt(req.params.id); + + // Check if conversion is valid + if (isNaN(reviewId)) { + res.status(400).json({ message: 'Invalid review ID format' }); + return; + } + + const review = await CourseReviews.findOne({ + id: reviewId, + }); + + if (!review) { + res.status(404).json({ message: 'Review not found' }); + return; + } + + res.json(review); + } catch (err) { + console.error(err); + res.status(500).json({ message: 'Server error' }); } - - const review = await CourseReviews.findOne({ - id: reviewId, - }); - - if (!review) { - res.status(404).json({ message: 'Review not found' }); - return; - } - - res.json(review); - } catch (err) { - console.error(err); - res.status(500).json({ message: 'Server error' }); } -}); +); /** * @route POST /api/reviews * @desc Create new review - * @access Private + * @access isAuthenticated */ -router.post('/reviews', async (req: Request, res: Response) => { - try { - const { - id, - overall_rating, - challenge_rating, - inclusivity_rating, - work_per_week, - total_cost, - comments, - course_id, - instructor_id, - user_id, - } = req.body; - - // Check if review already exists - const reviewExists = await CourseReviews.findOne({ - id, - }); - if (reviewExists) { - res.status(400).json({ message: 'Review already exists' }); - return; +router.post( + '/reviews', + isAuthenticated, + async (req: Request, res: Response) => { + try { + const { + id, + overall_rating, + challenge_rating, + inclusivity_rating, + work_per_week, + total_cost, + comments, + course_id, + instructor_id, + user_id, + } = req.body; + + // Check if review already exists + const reviewExists = await CourseReviews.findOne({ + id, + }); + if (reviewExists) { + res.status(400).json({ message: 'Review already exists' }); + return; + } + + // Create new review + const newReview = new CourseReviews({ + id, + overall_rating, + challenge_rating, + inclusivity_rating, + work_per_week, + total_cost, + comments, + course_id, + instructor_id, + user_id, + }); + + const savedReview = await newReview.save(); + res.status(201).json(savedReview); + } catch (err) { + console.error(err); + res.status(500).json({ message: 'Server error' }); } - - // Create new review - const newReview = new CourseReviews({ - id, - overall_rating, - challenge_rating, - inclusivity_rating, - work_per_week, - total_cost, - comments, - course_id, - instructor_id, - user_id, - }); - - const savedReview = await newReview.save(); - res.status(201).json(savedReview); - } catch (err) { - console.error(err); - res.status(500).json({ message: 'Server error' }); } -}); +); /** * @route PUT /api/reviews/:id * @desc Update review - * @access Private + * @access isCourseReviewOwner */ -router.put('/reviews/:id', async (req: Request, res: Response) => { - try { - const reviewId = parseInt(req.params.id); - - // Check if conversion is valid - if (isNaN(reviewId)) { - res.status(400).json({ message: 'Invalid review ID format' }); - return; - } - - // Check if review exists - const review = await CourseReviews.findOne({ - id: reviewId, - }); - - if (!review) { - res.status(404).json({ message: 'Review not found' }); - return; +router.put( + '/reviews/:id', + isCourseReviewOwner, + async (req: Request, res: Response) => { + try { + const reviewId = parseInt(req.params.id); + + // Check if conversion is valid + if (isNaN(reviewId)) { + res.status(400).json({ message: 'Invalid review ID format' }); + return; + } + + // Check if review exists + const review = await CourseReviews.findOne({ + id: reviewId, + }); + + if (!review) { + res.status(404).json({ message: 'Review not found' }); + return; + } + + const updatedReview = await CourseReviews.findOneAndUpdate( + { id: reviewId }, + { $set: req.body }, + { new: true } + ); + + res.json(updatedReview); + } catch (err) { + console.error(err); + res.status(500).json({ message: 'Server error' }); } - - // TODO: Check if user is authorized to update this review - // For example, checking if req.user.id === review.user_id - // This can also be done somewhere else - - const updatedReview = await CourseReviews.findOneAndUpdate( - { id: reviewId }, - { $set: req.body }, - { new: true } - ); - - res.json(updatedReview); - } catch (err) { - console.error(err); - res.status(500).json({ message: 'Server error' }); } -}); +); /** * @route DELETE /api/reviews/:id * @desc Delete review - * @access Private + * @access isCourseReviewOwner */ -router.delete('/reviews/:id', async (req: Request, res: Response) => { - try { - const reviewId: number = parseInt(req.params.id); - - // Check if conversion is valid - if (isNaN(reviewId)) { - res.status(400).json({ message: 'Invalid review ID format' }); - return; - } - - // Check if review exists - const review = await CourseReviews.findOne({ - id: reviewId, - }); - - if (!review) { - res.status(404).json({ message: 'Review not found' }); - return; +router.delete( + '/reviews/:id', + isCourseReviewOwner, + async (req: Request, res: Response) => { + try { + const reviewId: number = parseInt(req.params.id); + + // Check if conversion is valid + if (isNaN(reviewId)) { + res.status(400).json({ message: 'Invalid review ID format' }); + return; + } + + // Check if review exists + const review = await CourseReviews.findOne({ + id: reviewId, + }); + + if (!review) { + res.status(404).json({ message: 'Review not found' }); + return; + } + + await CourseReviews.findOneAndDelete({ id: reviewId }); + res.json({ message: 'Review removed' }); + } catch (err) { + console.error(err); + res.status(500).json({ message: 'Server error' }); } - - // TODO: Check if user is authorized to delete this review - // For example, checking if req.user.id === review.user_id or if user is admin - // This can also be done somewhere else - - await CourseReviews.findOneAndDelete({ id: reviewId }); - res.json({ message: 'Review removed' }); - } catch (err) { - console.error(err); - res.status(500).json({ message: 'Server error' }); } -}); +); export default router; diff --git a/backend/src/routes/admin/PagesRoutes.ts b/backend/src/routes/admin/PagesRoutes.ts index 95e284cb..a38ce6a2 100644 --- a/backend/src/routes/admin/PagesRoutes.ts +++ b/backend/src/routes/admin/PagesRoutes.ts @@ -4,7 +4,11 @@ import { isAdmin, isAuthenticated } from '../../middleware/authMiddleware'; const router = express.Router(); -// Get all pages +/** + * @route GET /api/admin/pages + * @desc Get all pages + * @access isAuthenticated + */ router.get('/', async (req: Request, res: Response) => { try { const pages = await PageContent.find({}); @@ -14,7 +18,11 @@ router.get('/', async (req: Request, res: Response) => { } }); -// Get the page by id +/** + * @route GET /api/admin/pages/:id + * @desc Get page by id + * @access isAuthenticated + */ router.get('/:id', isAuthenticated, async (req: Request, res: Response) => { try { const { id } = req.params; @@ -31,7 +39,11 @@ router.get('/:id', isAuthenticated, async (req: Request, res: Response) => { } }); -// Get all pages by header +/** + * @route GET /api/admin/pages/:header + * @desc Get pages by header + * @access isAuthenticated + */ router.get('/header/:header', async (req: Request, res: Response) => { try { const { header } = req.params; @@ -42,7 +54,11 @@ router.get('/header/:header', async (req: Request, res: Response) => { } }); -// Create a new page +/** + * @route POST /api/admin/pages + * @desc Create a new page + * @access isAdmin + */ router.post('/', isAdmin, async (req: Request, res: Response) => { try { const { id, name, content, header, link } = req.body; @@ -79,7 +95,11 @@ router.post('/', isAdmin, async (req: Request, res: Response) => { } }); -// Update an existing page +/** + * @route PUT /api/admin/pages/:id + * @desc Update an existing page + * @access isAdmin + */ router.put('/:id', isAdmin, async (req: Request, res: Response) => { try { const { id } = req.params; @@ -116,7 +136,11 @@ router.put('/:id', isAdmin, async (req: Request, res: Response) => { } }); -// Delete a page by id +/** + * @route DELETE /api/admin/pages/:id + * @desc Delete a page by id + * @access isAdmin + */ router.delete('/:id', isAdmin, async (req: Request, res: Response) => { try { const { id } = req.params; diff --git a/backend/src/routes/admin/StaffRoutes.ts b/backend/src/routes/admin/StaffRoutes.ts index c10f409b..16713e9c 100644 --- a/backend/src/routes/admin/StaffRoutes.ts +++ b/backend/src/routes/admin/StaffRoutes.ts @@ -10,8 +10,12 @@ const router = express.Router(); const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); -// Get all ids and names and positions of staff members -router.get('/', async (req: Request, res: Response) => { +/** + * @route GET /api/members + * @desc Get all ids and names and positions of staff members + * @access isAuthenticated + */ +router.get('/', isAuthenticated, async (req: Request, res: Response) => { try { const staff = await Staff.find({}, { id: 1, name: 1, position: 1 }); res.json(staff); @@ -20,7 +24,11 @@ router.get('/', async (req: Request, res: Response) => { } }); -// Get staff info by id +/** + * @route GET /api/members/:id + * @desc Get staff member info by id + * @access isAuthenticated + */ router.get('/:id', isAuthenticated, async (req: Request, res: Response) => { try { const { id } = req.params; @@ -36,7 +44,11 @@ router.get('/:id', isAuthenticated, async (req: Request, res: Response) => { } }); -// Get staff info by group +/** + * @route GET /api/members/:group + * @desc Get staff members by group + * @access isAuthenticated + */ router.get( '/group/:group', isAuthenticated, @@ -57,7 +69,11 @@ router.get( } ); -// Create staff member +/** + * @route POST /api/members + * @desc Create a new staff member + * @access isAdmin + */ router.post( '/', isAdmin, @@ -105,7 +121,11 @@ router.post( } ); -// Update staff info +/** + * @route PATCH /api/members/:id + * @desc Update staff member info + * @access isAdmin + */ router.patch( '/:id', isAdmin, @@ -177,37 +197,50 @@ router.patch( } ); -// Get profile picture by id -router.get('/profile-pic/:id', async (req: Request, res: Response) => { - try { - const fileId = new ObjectId(req.params.id); +/** + * @route GET /api/members/profile-pic/:id + * @desc Get profile picture of staff by id + * @access isAuthenticated + */ +router.get( + '/profile-pic/:id', + isAuthenticated, + async (req: Request, res: Response) => { + try { + const fileId = new ObjectId(req.params.id); - // Check if file exists - const files = await bucket.find({ _id: fileId }).toArray(); - if (!files.length) { - res.status(404).json({ message: 'Profile picture not found' }); - return; - } + // Check if file exists + const files = await bucket.find({ _id: fileId }).toArray(); + if (!files.length) { + res.status(404).json({ message: 'Profile picture not found' }); + return; + } - // Set appropriate headers - res.set('Content-Type', files[0].contentType); + // Set appropriate headers + res.set('Content-Type', files[0].contentType); - // Create download stream - const downloadStream = bucket.openDownloadStream(fileId); + // Create download stream + const downloadStream = bucket.openDownloadStream(fileId); - // Pipe the file to the response - downloadStream.pipe(res); + // Pipe the file to the response + downloadStream.pipe(res); - downloadStream.on('error', () => { - res.status(404).json({ - message: 'Error retrieving profile picture', + downloadStream.on('error', () => { + res.status(404).json({ + message: 'Error retrieving profile picture', + }); }); - }); - } catch (error) { - res.status(400).json({ message: 'Invalid profile picture ID' }); + } catch (error) { + res.status(400).json({ message: 'Invalid profile picture ID' }); + } } -}); +); +/** + * @route DELETE /api/members/:id + * @desc Delete member by id + * @access isAdmin + */ router.delete('/:id', isAdmin, async (req: Request, res: Response) => { try { const staff = await Staff.findOneAndDelete({ id: req.params.id }); From 0fb7534333dec61bbf4add95561dac4ee2af77bc Mon Sep 17 00:00:00 2001 From: ksantoso2 <130810746+ksantoso2@users.noreply.github.com> Date: Tue, 18 Nov 2025 00:46:36 -0800 Subject: [PATCH 2/3] fix comments --- backend/src/routes/admin/PagesRoutes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/admin/PagesRoutes.ts b/backend/src/routes/admin/PagesRoutes.ts index a38ce6a2..93bc419c 100644 --- a/backend/src/routes/admin/PagesRoutes.ts +++ b/backend/src/routes/admin/PagesRoutes.ts @@ -7,7 +7,7 @@ const router = express.Router(); /** * @route GET /api/admin/pages * @desc Get all pages - * @access isAuthenticated + * @access */ router.get('/', async (req: Request, res: Response) => { try { @@ -42,7 +42,7 @@ router.get('/:id', isAuthenticated, async (req: Request, res: Response) => { /** * @route GET /api/admin/pages/:header * @desc Get pages by header - * @access isAuthenticated + * @access */ router.get('/header/:header', async (req: Request, res: Response) => { try { From 751b03dedad2d468ee9d53879dec156f36990680 Mon Sep 17 00:00:00 2001 From: ksantoso2 <130810746+ksantoso2@users.noreply.github.com> Date: Tue, 18 Nov 2025 00:49:41 -0800 Subject: [PATCH 3/3] fix staff --- backend/src/routes/admin/StaffRoutes.ts | 54 ++++++++++++------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/backend/src/routes/admin/StaffRoutes.ts b/backend/src/routes/admin/StaffRoutes.ts index 16713e9c..a10f6bf7 100644 --- a/backend/src/routes/admin/StaffRoutes.ts +++ b/backend/src/routes/admin/StaffRoutes.ts @@ -13,9 +13,9 @@ const upload = multer({ storage: storage }); /** * @route GET /api/members * @desc Get all ids and names and positions of staff members - * @access isAuthenticated + * @access */ -router.get('/', isAuthenticated, async (req: Request, res: Response) => { +router.get('/', async (req: Request, res: Response) => { try { const staff = await Staff.find({}, { id: 1, name: 1, position: 1 }); res.json(staff); @@ -200,41 +200,37 @@ router.patch( /** * @route GET /api/members/profile-pic/:id * @desc Get profile picture of staff by id - * @access isAuthenticated + * @access */ -router.get( - '/profile-pic/:id', - isAuthenticated, - async (req: Request, res: Response) => { - try { - const fileId = new ObjectId(req.params.id); +router.get('/profile-pic/:id', async (req: Request, res: Response) => { + try { + const fileId = new ObjectId(req.params.id); - // Check if file exists - const files = await bucket.find({ _id: fileId }).toArray(); - if (!files.length) { - res.status(404).json({ message: 'Profile picture not found' }); - return; - } + // Check if file exists + const files = await bucket.find({ _id: fileId }).toArray(); + if (!files.length) { + res.status(404).json({ message: 'Profile picture not found' }); + return; + } - // Set appropriate headers - res.set('Content-Type', files[0].contentType); + // Set appropriate headers + res.set('Content-Type', files[0].contentType); - // Create download stream - const downloadStream = bucket.openDownloadStream(fileId); + // Create download stream + const downloadStream = bucket.openDownloadStream(fileId); - // Pipe the file to the response - downloadStream.pipe(res); + // Pipe the file to the response + downloadStream.pipe(res); - downloadStream.on('error', () => { - res.status(404).json({ - message: 'Error retrieving profile picture', - }); + downloadStream.on('error', () => { + res.status(404).json({ + message: 'Error retrieving profile picture', }); - } catch (error) { - res.status(400).json({ message: 'Invalid profile picture ID' }); - } + }); + } catch (error) { + res.status(400).json({ message: 'Invalid profile picture ID' }); } -); +}); /** * @route DELETE /api/members/:id