diff --git a/api/src/routes/protected/settings.test.ts b/api/src/routes/protected/settings.test.ts index 66df23ca5d7..e760ce72e14 100644 --- a/api/src/routes/protected/settings.test.ts +++ b/api/src/routes/protected/settings.test.ts @@ -991,6 +991,75 @@ Happy coding! expect(response.body).toEqual(updateErrorResponse); expect(response.statusCode).toEqual(400); }); + + test('PUT allows updating location/about when picture is unchanged (even without extension)', async () => { + // Simulate a user who already has a GitHub avatar URL saved (e.g., from before strict validation) + const githubAvatarUrl = + 'https://avatars0.githubusercontent.com/u/34585031?v=4'; + await fastifyTestInstance.prisma.user.update({ + where: { id: defaultUserId }, + data: { + picture: githubAvatarUrl, + about: 'Initial about', + name: 'Test User', + location: 'Initial Location' + } + }); + + // Now update only location and about, keeping the same picture (no extension) + const updateResponse = await superPut('/update-my-about').send({ + about: 'Updated about text', + name: 'Test User', + location: 'New Location', + picture: githubAvatarUrl // Same URL, no extension - should skip validation + }); + + expect(updateResponse.body).toEqual({ + message: 'flash.updated-about-me', + type: 'success' + }); + expect(updateResponse.statusCode).toEqual(200); + + const user = await fastifyTestInstance?.prisma.user.findFirst({ + where: { email: 'foo@bar.com' } + }); + + expect(user?.about).toEqual('Updated about text'); + expect(user?.location).toEqual('New Location'); + expect(user?.picture).toEqual(githubAvatarUrl); + }); + + test('PUT still validates picture when it is actually changed', async () => { + // Set initial valid picture + const validPictureUrl = 'https://example.com/avatar.png'; + await superPut('/update-my-about').send({ + about: 'Initial', + name: 'Test', + location: 'Location', + picture: validPictureUrl + }); + + // Try to change picture to invalid URL (no extension) + const updateResponse = await superPut('/update-my-about').send({ + about: 'Initial', + name: 'Test', + location: 'Location', + picture: 'https://example.com/new-avatar' // Changed but invalid + }); + + expect(updateResponse.statusCode).toEqual(400); + expect(updateResponse.body).toEqual({ + message: 'flash.wrong-updating', + type: 'danger' + }); + + // Verify picture wasn't updated + const user = await fastifyTestInstance?.prisma.user.findFirst({ + where: { email: 'foo@bar.com' } + }); + + expect(user?.picture).toEqual(validPictureUrl); + }); }); describe('/update-my-honesty', () => { diff --git a/api/src/routes/protected/settings.ts b/api/src/routes/protected/settings.ts index b94d39c2b85..76407ae60ea 100644 --- a/api/src/routes/protected/settings.ts +++ b/api/src/routes/protected/settings.ts @@ -523,11 +523,16 @@ ${isLinkSentWithinLimitTTL}` }, async (req, reply) => { const logger = fastify.log.child({ req, res: reply }); - 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; + + // No need to validate if picture is being deleted. + if (req.body.picture) { + if (req.body.picture !== req.user!.picture) { + if (!isValidPictureUrl(req.body.picture)) { + logger.warn(`Invalid picture URL: ${req.body.picture}`); + void reply.code(400); + return { message: 'flash.wrong-updating', type: 'danger' } as const; + } + } } try {