mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-27 02:01:02 -04:00
fix(api): handle invalid picture URLs for '/update-my-about' (#61769)
This commit is contained in:
@@ -871,15 +871,74 @@ Happy coding!
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
|
||||
test('PUT updates the values in about settings without image', async () => {
|
||||
test('PUT returns 400 if the URL is invalid', async () => {
|
||||
const response = await superPut('/update-my-about').send({
|
||||
about: 'Teacher at freeCodeCamp',
|
||||
name: 'Quincy Larson',
|
||||
location: 'USA',
|
||||
// `new URL` throws if the image isn't a URL, this checks if it doesn't throw.
|
||||
picture: 'invalid'
|
||||
});
|
||||
|
||||
expect(response.body).toEqual({
|
||||
message: 'flash.wrong-updating',
|
||||
type: 'danger'
|
||||
});
|
||||
expect(response.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT returns 400 if the URL has no image extension', async () => {
|
||||
const response = await superPut('/update-my-about').send({
|
||||
about: 'Teacher at freeCodeCamp',
|
||||
name: 'Quincy Larson',
|
||||
location: 'USA',
|
||||
picture: 'https://example.com/avatar'
|
||||
});
|
||||
|
||||
expect(response.body).toEqual({
|
||||
message: 'flash.wrong-updating',
|
||||
type: 'danger'
|
||||
});
|
||||
expect(response.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT returns 400 if the URL has a non-image extension', async () => {
|
||||
const response = await superPut('/update-my-about').send({
|
||||
about: 'Teacher at freeCodeCamp',
|
||||
name: 'Quincy Larson',
|
||||
location: 'USA',
|
||||
picture: 'https://example.com/file.txt'
|
||||
});
|
||||
|
||||
expect(response.body).toEqual({
|
||||
message: 'flash.wrong-updating',
|
||||
type: 'danger'
|
||||
});
|
||||
expect(response.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT accepts an image URL with query string', async () => {
|
||||
const response = await superPut('/update-my-about').send({
|
||||
about: 'Teacher at freeCodeCamp',
|
||||
name: 'Quincy Larson',
|
||||
location: 'USA',
|
||||
picture: 'https://example.com/photo.png?size=200&cache=bust'
|
||||
});
|
||||
|
||||
expect(response.body).toEqual({
|
||||
message: 'flash.updated-about-me',
|
||||
type: 'success'
|
||||
});
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
|
||||
test('PUT accepts an image URL with a different valid extension (.webp)', async () => {
|
||||
const response = await superPut('/update-my-about').send({
|
||||
about: 'Teacher at freeCodeCamp',
|
||||
name: 'Quincy Larson',
|
||||
location: 'USA',
|
||||
picture: 'https://example.com/avatar.webp'
|
||||
});
|
||||
|
||||
expect(response.body).toEqual({
|
||||
message: 'flash.updated-about-me',
|
||||
type: 'success'
|
||||
|
||||
@@ -53,6 +53,45 @@ export const isPictureWithProtocol = (picture?: string): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
const commonImageExtensions = [
|
||||
'apng',
|
||||
'avif',
|
||||
'gif',
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'jfif',
|
||||
'pjpeg',
|
||||
'pjp',
|
||||
'png',
|
||||
'svg',
|
||||
'webp'
|
||||
];
|
||||
|
||||
/**
|
||||
* Validate that a picture URL has a common image extension.
|
||||
*
|
||||
* @param picture The URL to check.
|
||||
* @returns Whether the URL has a common image extension.
|
||||
*/
|
||||
|
||||
const validateImageExtension = (picture?: string): boolean => {
|
||||
if (!picture) return true;
|
||||
return commonImageExtensions.some(ext => picture.includes(`.${ext}`));
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate that a picture URL is valid. A valid picture URL either:
|
||||
* - is empty/undefined (no update), or
|
||||
* - has a valid http/https protocol AND has a common image extension.
|
||||
*
|
||||
* @param picture The URL to validate.
|
||||
* @returns Whether the picture URL is considered valid.
|
||||
*/
|
||||
const isValidPictureUrl = (picture?: string): boolean => {
|
||||
if (!picture) return true;
|
||||
return isPictureWithProtocol(picture) && validateImageExtension(picture);
|
||||
};
|
||||
|
||||
const ALLOWED_DOMAINS_MAP = {
|
||||
githubProfile: ['github.com'],
|
||||
linkedin: ['linkedin.com'],
|
||||
@@ -484,7 +523,12 @@ ${isLinkSentWithinLimitTTL}`
|
||||
},
|
||||
async (req, reply) => {
|
||||
const logger = fastify.log.child({ req, res: reply });
|
||||
const hasProtocol = isPictureWithProtocol(req.body.picture);
|
||||
const pictureIsValid = isValidPictureUrl(req.body.picture);
|
||||
if (!pictureIsValid) {
|
||||
logger.warn(`Invalid picture URL: ${req.body.picture}`);
|
||||
void reply.code(400);
|
||||
return { message: 'flash.wrong-updating', type: 'danger' } as const;
|
||||
}
|
||||
|
||||
try {
|
||||
await fastify.prisma.user.update({
|
||||
@@ -493,7 +537,7 @@ ${isLinkSentWithinLimitTTL}`
|
||||
about: req.body.about,
|
||||
name: req.body.name,
|
||||
location: req.body.location,
|
||||
picture: hasProtocol ? req.body.picture : ''
|
||||
picture: req.body.picture
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user