Phase 3 + 6
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
const express = require('express');
|
||||
import express from 'express';
|
||||
import { Client } from 'ssh2';
|
||||
import mysql from 'mysql2/promise';
|
||||
import multer from 'multer';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import sharp from 'sharp';
|
||||
import axios from 'axios';
|
||||
import net from 'node:net';
|
||||
import { requirePermission } from '../../shared/auth/middleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
const { Client } = require('ssh2');
|
||||
const mysql = require('mysql2/promise');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fsp = fs.promises;
|
||||
const sharp = require('sharp');
|
||||
const axios = require('axios');
|
||||
const net = require('net');
|
||||
|
||||
// Phase 6.2: imports, uploads, generate-upc and deletions all require product_import.
|
||||
// Reads (list-uploads, status checks) remain authenticated-only.
|
||||
router.use((req, res, next) => {
|
||||
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') return next();
|
||||
return requirePermission('product_import')(req, res, next);
|
||||
});
|
||||
|
||||
// Create uploads directory if it doesn't exist
|
||||
const uploadsDir = path.join('/var/www/inventory/uploads/products');
|
||||
@@ -515,21 +524,30 @@ const storage = multer.diskStorage({
|
||||
|
||||
const MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
||||
|
||||
// Phase 6.7: exact-match MIME + extension allowlist. Substring-based regex
|
||||
// matchers (the previous /jpeg|png|.../ approach) accepted MIMEs like
|
||||
// `application/jpeg-payload` because of partial matches; this rejects them.
|
||||
const ALLOWED_MIME_TYPES = new Set([
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'image/tiff',
|
||||
]);
|
||||
const ALLOWED_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.tif', '.tiff']);
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: MAX_UPLOAD_BYTES,
|
||||
files: 1,
|
||||
},
|
||||
fileFilter: function (req, file, cb) {
|
||||
// Accept only image files
|
||||
const filetypes = /jpeg|jpg|png|gif|webp|tiff?/;
|
||||
const mimetype = filetypes.test(file.mimetype);
|
||||
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
|
||||
|
||||
if (mimetype && extname) {
|
||||
return cb(null, true);
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
if (!ALLOWED_MIME_TYPES.has(file.mimetype) || !ALLOWED_EXTENSIONS.has(ext)) {
|
||||
return cb(new Error('Only image files are allowed (jpg, png, gif, webp, tiff)'));
|
||||
}
|
||||
cb(new Error('Only image files are allowed'));
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -647,7 +665,7 @@ async function setupSshTunnel() {
|
||||
port: process.env.PROD_SSH_PORT || 22,
|
||||
username: process.env.PROD_SSH_USER,
|
||||
privateKey: process.env.PROD_SSH_KEY_PATH
|
||||
? require('fs').readFileSync(process.env.PROD_SSH_KEY_PATH)
|
||||
? fs.readFileSync(process.env.PROD_SSH_KEY_PATH)
|
||||
: undefined,
|
||||
compress: true
|
||||
};
|
||||
@@ -2872,4 +2890,4 @@ router.get('/query-products', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user