2024-08-05 03:34:00 -04:00
|
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
const validateImageRequest = require('~/server/middleware/validateImageRequest');
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
jest.mock('~/server/services/Config/app', () => ({
|
|
|
|
getAppConfig: jest.fn(),
|
|
|
|
}));
|
|
|
|
|
2024-08-05 03:34:00 -04:00
|
|
|
describe('validateImageRequest middleware', () => {
|
|
|
|
let req, res, next;
|
2024-10-24 15:50:48 -04:00
|
|
|
const validObjectId = '65cfb246f7ecadb8b1e8036b';
|
2025-08-26 12:10:18 -04:00
|
|
|
const { getAppConfig } = require('~/server/services/Config/app');
|
2024-08-05 03:34:00 -04:00
|
|
|
|
|
|
|
beforeEach(() => {
|
2025-08-26 12:10:18 -04:00
|
|
|
jest.clearAllMocks();
|
2024-08-05 03:34:00 -04:00
|
|
|
req = {
|
|
|
|
headers: {},
|
|
|
|
originalUrl: '',
|
|
|
|
};
|
|
|
|
res = {
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
send: jest.fn(),
|
|
|
|
};
|
|
|
|
next = jest.fn();
|
|
|
|
process.env.JWT_REFRESH_SECRET = 'test-secret';
|
2025-08-26 12:10:18 -04:00
|
|
|
|
|
|
|
// Mock getAppConfig to return secureImageLinks: true by default
|
|
|
|
getAppConfig.mockResolvedValue({
|
|
|
|
secureImageLinks: true,
|
|
|
|
});
|
2024-08-05 03:34:00 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
jest.clearAllMocks();
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should call next() if secureImageLinks is false', async () => {
|
|
|
|
getAppConfig.mockResolvedValue({
|
|
|
|
secureImageLinks: false,
|
|
|
|
});
|
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(next).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should return 401 if refresh token is not provided', async () => {
|
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(res.status).toHaveBeenCalledWith(401);
|
|
|
|
expect(res.send).toHaveBeenCalledWith('Unauthorized');
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should return 403 if refresh token is invalid', async () => {
|
2024-08-05 03:34:00 -04:00
|
|
|
req.headers.cookie = 'refreshToken=invalid-token';
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
|
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should return 403 if refresh token is expired', async () => {
|
2024-08-05 03:34:00 -04:00
|
|
|
const expiredToken = jwt.sign(
|
2024-10-24 15:50:48 -04:00
|
|
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) - 3600 },
|
2024-08-05 03:34:00 -04:00
|
|
|
process.env.JWT_REFRESH_SECRET,
|
|
|
|
);
|
|
|
|
req.headers.cookie = `refreshToken=${expiredToken}`;
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
|
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should call next() for valid image path', async () => {
|
2024-08-05 03:34:00 -04:00
|
|
|
const validToken = jwt.sign(
|
2024-10-24 15:50:48 -04:00
|
|
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
2024-08-05 03:34:00 -04:00
|
|
|
process.env.JWT_REFRESH_SECRET,
|
|
|
|
);
|
|
|
|
req.headers.cookie = `refreshToken=${validToken}`;
|
2024-10-24 15:50:48 -04:00
|
|
|
req.originalUrl = `/images/${validObjectId}/example.jpg`;
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(next).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should return 403 for invalid image path', async () => {
|
2024-08-05 03:34:00 -04:00
|
|
|
const validToken = jwt.sign(
|
2024-10-24 15:50:48 -04:00
|
|
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
2024-08-05 03:34:00 -04:00
|
|
|
process.env.JWT_REFRESH_SECRET,
|
|
|
|
);
|
|
|
|
req.headers.cookie = `refreshToken=${validToken}`;
|
2024-10-24 15:50:48 -04:00
|
|
|
req.originalUrl = '/images/65cfb246f7ecadb8b1e8036c/example.jpg'; // Different ObjectId
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-10-24 15:50:48 -04:00
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
|
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should return 403 for invalid ObjectId format', async () => {
|
2024-10-24 15:50:48 -04:00
|
|
|
const validToken = jwt.sign(
|
|
|
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
|
|
process.env.JWT_REFRESH_SECRET,
|
|
|
|
);
|
|
|
|
req.headers.cookie = `refreshToken=${validToken}`;
|
|
|
|
req.originalUrl = '/images/123/example.jpg'; // Invalid ObjectId
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
|
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
|
|
|
});
|
|
|
|
|
|
|
|
// File traversal tests
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should prevent file traversal attempts', async () => {
|
2024-08-05 03:34:00 -04:00
|
|
|
const validToken = jwt.sign(
|
2024-10-24 15:50:48 -04:00
|
|
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
2024-08-05 03:34:00 -04:00
|
|
|
process.env.JWT_REFRESH_SECRET,
|
|
|
|
);
|
|
|
|
req.headers.cookie = `refreshToken=${validToken}`;
|
|
|
|
|
|
|
|
const traversalAttempts = [
|
2024-10-24 15:50:48 -04:00
|
|
|
`/images/${validObjectId}/../../../etc/passwd`,
|
|
|
|
`/images/${validObjectId}/..%2F..%2F..%2Fetc%2Fpasswd`,
|
|
|
|
`/images/${validObjectId}/image.jpg/../../../etc/passwd`,
|
|
|
|
`/images/${validObjectId}/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd`,
|
2024-08-05 03:34:00 -04:00
|
|
|
];
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
for (const attempt of traversalAttempts) {
|
2024-08-05 03:34:00 -04:00
|
|
|
req.originalUrl = attempt;
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
|
|
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
|
|
|
jest.clearAllMocks();
|
2025-08-26 12:10:18 -04:00
|
|
|
}
|
2024-08-05 03:34:00 -04:00
|
|
|
});
|
|
|
|
|
2025-08-26 12:10:18 -04:00
|
|
|
test('should handle URL encoded characters in valid paths', async () => {
|
2024-08-05 03:34:00 -04:00
|
|
|
const validToken = jwt.sign(
|
2024-10-24 15:50:48 -04:00
|
|
|
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
2024-08-05 03:34:00 -04:00
|
|
|
process.env.JWT_REFRESH_SECRET,
|
|
|
|
);
|
|
|
|
req.headers.cookie = `refreshToken=${validToken}`;
|
2024-10-24 15:50:48 -04:00
|
|
|
req.originalUrl = `/images/${validObjectId}/image%20with%20spaces.jpg`;
|
2025-08-26 12:10:18 -04:00
|
|
|
await validateImageRequest(req, res, next);
|
2024-08-05 03:34:00 -04:00
|
|
|
expect(next).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|