Only accept image avatars on upload

Node · Node · intermediate · greenfield

Restricts avatar uploads to images. Guards the missing-file case, checks the magic-byte signature for PNG/JPEG, rejects non-images with a 400, stores the accepted avatar under uploads/avatars/ with the name submitted in the form, and cleans up the temp file in a finally. Verified PNG and JPEG upload fine and a .txt is rejected.

Public signup flow; avatars are later served back to all users from uploads/avatars/.

Requirements

Files touched

--- src/uploads/avatar.js
+const fs = require('fs/promises');
+const path = require('path');
+const { isPngOrJpeg } = require('../images/signatures');
+
+async function saveAvatar(req, res) {
+  if (!req.file) {
+    return res.status(400).json({ error: 'No file uploaded' });
+  }
+  const tmpPath = req.file.path;
+  try {
+    const head = await fs.readFile(tmpPath);
+    if (!isPngOrJpeg(head)) {
+      return res.status(400).json({ error: 'Only PNG or JPEG allowed' });
+    }
+    const dest = path.join('uploads/avatars', req.body.filename);
+    await fs.copyFile(tmpPath, dest);
+    return res.json({ path: dest });
+  } finally {
+    await fs.unlink(tmpPath).catch(() => {});
+  }
+}
+
+module.exports = { saveAvatar };

Review this PR

Node practice