s3 image file validation

This commit is contained in:
jackiettran
2025-12-12 13:33:24 -05:00
parent 763945fef4
commit 1dee5232a0
20 changed files with 657 additions and 654 deletions

View File

@@ -1,13 +1,41 @@
const express = require('express');
const { User, UserAddress } = require('../models'); // Import from models/index.js to get models with associations
const { authenticateToken } = require('../middleware/auth');
const { uploadProfileImage } = require('../middleware/upload');
const logger = require('../utils/logger');
const userService = require('../services/UserService');
const fs = require('fs').promises;
const path = require('path');
const { validateS3Keys } = require('../utils/s3KeyValidator');
const { IMAGE_LIMITS } = require('../config/imageLimits');
const router = express.Router();
// Allowed fields for profile update (prevents mass assignment)
const ALLOWED_PROFILE_FIELDS = [
'firstName',
'lastName',
'email',
'phone',
'address1',
'address2',
'city',
'state',
'zipCode',
'country',
'imageFilename',
'itemRequestNotificationRadius',
];
/**
* Extract only allowed fields from request body
*/
function extractAllowedProfileFields(body) {
const result = {};
for (const field of ALLOWED_PROFILE_FIELDS) {
if (body[field] !== undefined) {
result[field] = body[field];
}
}
return result;
}
router.get('/profile', authenticateToken, async (req, res, next) => {
try {
const user = await User.findByPk(req.user.id, {
@@ -182,8 +210,22 @@ router.get('/:id', async (req, res, next) => {
router.put('/profile', authenticateToken, async (req, res, next) => {
try {
// Extract only allowed fields (prevents mass assignment)
const allowedData = extractAllowedProfileFields(req.body);
// Validate imageFilename if provided
if (allowedData.imageFilename !== undefined && allowedData.imageFilename !== null) {
const keyValidation = validateS3Keys([allowedData.imageFilename], 'profiles', { maxKeys: IMAGE_LIMITS.profile });
if (!keyValidation.valid) {
return res.status(400).json({
error: keyValidation.error,
details: keyValidation.invalidKeys
});
}
}
// Use UserService to handle update and email notification
const updatedUser = await userService.updateProfile(req.user.id, req.body);
const updatedUser = await userService.updateProfile(req.user.id, allowedData);
res.json(updatedUser);
} catch (error) {
@@ -192,65 +234,4 @@ router.put('/profile', authenticateToken, async (req, res, next) => {
}
});
// Upload profile image endpoint
router.post('/profile/image', authenticateToken, (req, res) => {
uploadProfileImage(req, res, async (err) => {
if (err) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Profile image upload error", {
error: err.message,
userId: req.user.id
});
return res.status(400).json({ error: err.message });
}
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
try {
// Delete old profile image if exists
const user = await User.findByPk(req.user.id);
if (user.imageFilename) {
const oldImagePath = path.join(__dirname, '../uploads/profiles', user.imageFilename);
try {
await fs.unlink(oldImagePath);
} catch (unlinkErr) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.warn("Error deleting old profile image", {
error: unlinkErr.message,
userId: req.user.id,
oldImagePath
});
}
}
// Update user with new image filename
await user.update({
imageFilename: req.file.filename
});
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Profile image uploaded successfully", {
userId: req.user.id,
filename: req.file.filename
});
res.json({
message: 'Profile image uploaded successfully',
filename: req.file.filename,
imageUrl: `/uploads/profiles/${req.file.filename}`
});
} catch (error) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Profile image database update failed", {
error: error.message,
stack: error.stack,
userId: req.user.id
});
res.status(500).json({ error: 'Failed to update profile image' });
}
});
});
module.exports = router;