fix(api): handle invalid picture URLs for '/update-my-about' (#61769)

This commit is contained in:
Sem Bauke
2025-10-13 12:45:08 +02:00
committed by GitHub
parent 799713c879
commit c005816748
2 changed files with 107 additions and 4 deletions

View File

@@ -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'

View File

@@ -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
}
});