fix(avatar) : address review findings — security and UX fixes

- Use getMimeType() instead of getClientMimeType() to prevent MIME spoofing
- Change IsGranted to IS_AUTHENTICATED_FULLY so ROLE_CLIENT can access avatars
- Remove Groups from avatarFileName (only avatarUrl needed by frontend)
- Disable aggressive caching to prevent stale avatar images
- Add error handling to avatar upload in profile page
- Use i18n for "Mon profil" button text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 22:02:27 +01:00
parent afd4baed92
commit a5144443a4
4 changed files with 12 additions and 9 deletions

View File

@@ -30,7 +30,7 @@ class UserAvatarController extends AbstractController
) {}
#[Route('/api/users/{id}/avatar', name: 'user_avatar_upload', methods: ['POST'], priority: 1)]
#[IsGranted('ROLE_USER')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function upload(int $id, Request $request): JsonResponse
{
$user = $this->findUserOrFail($id);
@@ -46,7 +46,7 @@ class UserAvatarController extends AbstractController
throw new BadRequestHttpException('File size exceeds 5 MB limit.');
}
$mimeType = $file->getClientMimeType();
$mimeType = $file->getMimeType();
if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)) {
throw new BadRequestHttpException('Invalid file type. Allowed: JPEG, PNG, WebP, GIF.');
@@ -71,7 +71,7 @@ class UserAvatarController extends AbstractController
}
#[Route('/api/users/{id}/avatar', name: 'user_avatar_serve', methods: ['GET'], priority: 1)]
#[IsGranted('ROLE_USER')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function serve(int $id): BinaryFileResponse
{
$user = $this->findUserOrFail($id);
@@ -91,13 +91,13 @@ class UserAvatarController extends AbstractController
$extension = pathinfo($user->getAvatarFileName(), PATHINFO_EXTENSION);
$mimeMap = ['jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', 'gif' => 'image/gif'];
$response->headers->set('Content-Type', $mimeMap[$extension] ?? 'application/octet-stream');
$response->headers->set('Cache-Control', 'public, max-age=86400');
$response->headers->set('Cache-Control', 'no-cache, must-revalidate');
return $response;
}
#[Route('/api/users/{id}/avatar', name: 'user_avatar_delete', methods: ['DELETE'], priority: 1)]
#[IsGranted('ROLE_USER')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function delete(int $id): Response
{
$user = $this->findUserOrFail($id);