Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion agency/server/controllers/blogController.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ const updateBlog = async (req, res) => {
const blog = await Blog.findById(req.params.id);

if (blog) {
Object.assign(blog, req.body);
const allowedFields = ['title', 'author', 'date', 'category', 'image', 'excerpt', 'content', 'readTime', 'tags', 'slug', 'status'];
allowedFields.forEach(field => {
if (req.body[field] !== undefined) {
blog[field] = req.body[field];
}
});
const updatedBlog = await blog.save();
res.json(updatedBlog);
} else {
Expand Down
7 changes: 6 additions & 1 deletion agency/server/controllers/jobController.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ const updateJob = async (req, res) => {
const job = await Job.findById(req.params.id);

if (job) {
Object.assign(job, req.body);
const allowedFields = ['role', 'type', 'location', 'description', 'requirements', 'isActive'];
allowedFields.forEach(field => {
if (req.body[field] !== undefined) {
job[field] = req.body[field];
}
});
const updatedJob = await job.save();
res.json(updatedJob);
} else {
Expand Down
6 changes: 4 additions & 2 deletions agency/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
"dev": "nodemon index.js",
"test": "jest"
},
"dependencies": {
"bcryptjs": "^3.0.3",
Expand All @@ -23,6 +24,7 @@
"nodemailer": "^8.0.2"
},
"devDependencies": {
"jest": "^30.4.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the server lockfile for Jest

When this package is installed from a clean checkout using the tracked agency/server/package-lock.json, the new test script depends on jest but the lockfile still has only nodemon in the server devDependencies and no Jest entries. That leaves the new tests unreproducible and can make lockfile-based installs or dependency verification for agency/server fail or omit the test runner; update the corresponding lockfile alongside this added dependency.

Useful? React with 👍 / 👎.

"nodemon": "^3.1.0"
}
}
}
91 changes: 91 additions & 0 deletions agency/server/tests/massAssignment.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const { updateBlog } = require('../controllers/blogController');
const { updateJob } = require('../controllers/jobController');
const Blog = require('../models/Blog');
const Job = require('../models/Job');

jest.mock('../models/Blog');
jest.mock('../models/Job');

describe('Mass Assignment Vulnerability Fixes', () => {
let req, res;

beforeEach(() => {
req = {
params: { id: 'test-id' },
body: {}
};
res = {
json: jest.fn(),
status: jest.fn().mockReturnThis()
};
jest.clearAllMocks();
});

describe('blogController.updateBlog', () => {
it('should only update allowed fields and ignore unauthorized fields', async () => {
const mockBlogDoc = {
title: 'Old Title',
author: 'Old Author',
save: jest.fn().mockResolvedValue(true)
};
Blog.findById.mockResolvedValue(mockBlogDoc);

req.body = {
title: 'New Title',
isAdmin: true, // Vulnerable field attempt
status: 'published',
_id: 'injected-id' // Vulnerable field attempt
};

await updateBlog(req, res);

// Expect the explicitly allowed fields to be updated
expect(mockBlogDoc.title).toBe('New Title');
expect(mockBlogDoc.status).toBe('published');

// Expect unauthorized fields to NOT be assigned
expect(mockBlogDoc.isAdmin).toBeUndefined();
expect(mockBlogDoc._id).toBeUndefined();

// Original un-updated field should remain untouched
expect(mockBlogDoc.author).toBe('Old Author');

expect(mockBlogDoc.save).toHaveBeenCalled();
expect(res.json).toHaveBeenCalledWith(true); // Since save() resolves to true
});
});

describe('jobController.updateJob', () => {
it('should only update allowed fields and ignore unauthorized fields', async () => {
const mockJobDoc = {
role: 'Old Role',
type: 'Full-time',
save: jest.fn().mockResolvedValue(true)
};
Job.findById.mockResolvedValue(mockJobDoc);

req.body = {
role: 'New Role',
isActive: false,
isAdmin: true, // Vulnerable field attempt
_id: 'injected-id' // Vulnerable field attempt
};

await updateJob(req, res);

// Expect explicitly allowed fields to be updated
expect(mockJobDoc.role).toBe('New Role');
expect(mockJobDoc.isActive).toBe(false);

// Expect unauthorized fields to NOT be assigned
expect(mockJobDoc.isAdmin).toBeUndefined();
expect(mockJobDoc._id).toBeUndefined();

// Original un-updated field should remain untouched
expect(mockJobDoc.type).toBe('Full-time');

expect(mockJobDoc.save).toHaveBeenCalled();
expect(res.json).toHaveBeenCalledWith(true); // Since save() resolves to true
});
});
});
Loading