Spaces:
Sleeping
Sleeping
Commit ·
c8aab3a
1
Parent(s): 2ec5005
- dist/app.js +2 -0
- dist/middleware/rateLimit.middleware.js +36 -0
- dist/routes/api.routes.js +5 -3
- package-lock.json +52 -0
- package.json +1 -0
- src/app.ts +2 -0
- src/middleware/rateLimit.middleware.ts +32 -0
- src/routes/api.routes.ts +7 -3
dist/app.js
CHANGED
|
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
|
|
| 9 |
const app_config_1 = require("./config/app.config");
|
| 10 |
const logger_middleware_1 = require("./middleware/logger.middleware");
|
| 11 |
const error_middleware_1 = require("./middleware/error.middleware");
|
|
|
|
| 12 |
const api_routes_1 = require("./routes/api.routes");
|
| 13 |
// Repositories
|
| 14 |
const fileTaskRepository_1 = require("./repositories/fileStorage/fileTaskRepository");
|
|
@@ -29,6 +30,7 @@ const admin_controller_1 = require("./controllers/admin.controller");
|
|
| 29 |
function createApp() {
|
| 30 |
const app = (0, express_1.default)();
|
| 31 |
// Middleware
|
|
|
|
| 32 |
app.use(express_1.default.json());
|
| 33 |
app.use(express_1.default.urlencoded({ extended: true }));
|
| 34 |
app.use(logger_middleware_1.requestLogger);
|
|
|
|
| 9 |
const app_config_1 = require("./config/app.config");
|
| 10 |
const logger_middleware_1 = require("./middleware/logger.middleware");
|
| 11 |
const error_middleware_1 = require("./middleware/error.middleware");
|
| 12 |
+
const rateLimit_middleware_1 = require("./middleware/rateLimit.middleware");
|
| 13 |
const api_routes_1 = require("./routes/api.routes");
|
| 14 |
// Repositories
|
| 15 |
const fileTaskRepository_1 = require("./repositories/fileStorage/fileTaskRepository");
|
|
|
|
| 30 |
function createApp() {
|
| 31 |
const app = (0, express_1.default)();
|
| 32 |
// Middleware
|
| 33 |
+
app.use(rateLimit_middleware_1.globalRateLimiter);
|
| 34 |
app.use(express_1.default.json());
|
| 35 |
app.use(express_1.default.urlencoded({ extended: true }));
|
| 36 |
app.use(logger_middleware_1.requestLogger);
|
dist/middleware/rateLimit.middleware.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
| 2 |
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
| 3 |
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
| 4 |
+
};
|
| 5 |
+
Object.defineProperty(exports, "__esModule", { value: true });
|
| 6 |
+
exports.strictActionRateLimiter = exports.globalRateLimiter = void 0;
|
| 7 |
+
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
| 8 |
+
/**
|
| 9 |
+
* General application rate limiter to suppress bot scanning volume and protect endpoints.
|
| 10 |
+
* Allows legitimate dashboard background polling (every ~3s = 60 req/min) while blocking aggressive scanners.
|
| 11 |
+
*/
|
| 12 |
+
exports.globalRateLimiter = (0, express_rate_limit_1.default)({
|
| 13 |
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
| 14 |
+
limit: 500, // Limit each IP to 500 requests per 15-minute window
|
| 15 |
+
standardHeaders: 'draft-7',
|
| 16 |
+
legacyHeaders: false,
|
| 17 |
+
message: {
|
| 18 |
+
status: 429,
|
| 19 |
+
error: 'Too Many Requests',
|
| 20 |
+
message: 'Rate limit exceeded. Please slow down your requests and try again later.',
|
| 21 |
+
},
|
| 22 |
+
});
|
| 23 |
+
/**
|
| 24 |
+
* Strict rate limiter for sensitive state-modifying endpoints (e.g., worker shutdown, task reset/generation).
|
| 25 |
+
*/
|
| 26 |
+
exports.strictActionRateLimiter = (0, express_rate_limit_1.default)({
|
| 27 |
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
| 28 |
+
limit: 30, // Limit each IP to 30 administrative actions per 15-minute window
|
| 29 |
+
standardHeaders: 'draft-7',
|
| 30 |
+
legacyHeaders: false,
|
| 31 |
+
message: {
|
| 32 |
+
status: 429,
|
| 33 |
+
error: 'Too Many Requests',
|
| 34 |
+
message: 'Rate limit exceeded for administrative actions. Please try again later.',
|
| 35 |
+
},
|
| 36 |
+
});
|
dist/routes/api.routes.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
Object.defineProperty(exports, "__esModule", { value: true });
|
| 3 |
exports.createApiRouter = createApiRouter;
|
| 4 |
const express_1 = require("express");
|
|
|
|
| 5 |
function createApiRouter(healthController, adminController) {
|
| 6 |
const router = (0, express_1.Router)();
|
| 7 |
// Health check
|
|
@@ -11,8 +12,9 @@ function createApiRouter(healthController, adminController) {
|
|
| 11 |
router.get('/workers', adminController.getWorkers);
|
| 12 |
router.get('/tasks', adminController.getTasks);
|
| 13 |
router.get('/dataset/rows', adminController.getDatasetRows);
|
| 14 |
-
|
| 15 |
-
router.post('/tasks/
|
| 16 |
-
router.post('/
|
|
|
|
| 17 |
return router;
|
| 18 |
}
|
|
|
|
| 2 |
Object.defineProperty(exports, "__esModule", { value: true });
|
| 3 |
exports.createApiRouter = createApiRouter;
|
| 4 |
const express_1 = require("express");
|
| 5 |
+
const rateLimit_middleware_1 = require("../middleware/rateLimit.middleware");
|
| 6 |
function createApiRouter(healthController, adminController) {
|
| 7 |
const router = (0, express_1.Router)();
|
| 8 |
// Health check
|
|
|
|
| 12 |
router.get('/workers', adminController.getWorkers);
|
| 13 |
router.get('/tasks', adminController.getTasks);
|
| 14 |
router.get('/dataset/rows', adminController.getDatasetRows);
|
| 15 |
+
// Sensitive administrative POST actions protected by strict rate limit
|
| 16 |
+
router.post('/tasks/create', rateLimit_middleware_1.strictActionRateLimiter, adminController.createTask);
|
| 17 |
+
router.post('/tasks/reset', rateLimit_middleware_1.strictActionRateLimiter, adminController.resetTasks);
|
| 18 |
+
router.post('/shutdown-worker', rateLimit_middleware_1.strictActionRateLimiter, adminController.shutdownWorker);
|
| 19 |
return router;
|
| 20 |
}
|
package-lock.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
"dependencies": {
|
| 12 |
"ejs": "^3.1.10",
|
| 13 |
"express": "^4.21.2",
|
|
|
|
| 14 |
"mime-types": "^2.1.35",
|
| 15 |
"multer": "^1.4.5-lts.1",
|
| 16 |
"ws": "^8.18.0"
|
|
@@ -719,6 +720,48 @@
|
|
| 719 |
"url": "https://opencollective.com/express"
|
| 720 |
}
|
| 721 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
"node_modules/filelist": {
|
| 723 |
"version": "1.0.6",
|
| 724 |
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
|
@@ -990,6 +1033,15 @@
|
|
| 990 |
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
| 991 |
"license": "ISC"
|
| 992 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 993 |
"node_modules/ipaddr.js": {
|
| 994 |
"version": "1.9.1",
|
| 995 |
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
|
|
|
| 11 |
"dependencies": {
|
| 12 |
"ejs": "^3.1.10",
|
| 13 |
"express": "^4.21.2",
|
| 14 |
+
"express-rate-limit": "^8.6.0",
|
| 15 |
"mime-types": "^2.1.35",
|
| 16 |
"multer": "^1.4.5-lts.1",
|
| 17 |
"ws": "^8.18.0"
|
|
|
|
| 720 |
"url": "https://opencollective.com/express"
|
| 721 |
}
|
| 722 |
},
|
| 723 |
+
"node_modules/express-rate-limit": {
|
| 724 |
+
"version": "8.6.0",
|
| 725 |
+
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.0.tgz",
|
| 726 |
+
"integrity": "sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==",
|
| 727 |
+
"license": "MIT",
|
| 728 |
+
"dependencies": {
|
| 729 |
+
"debug": "^4.4.3",
|
| 730 |
+
"ip-address": "^10.2.0"
|
| 731 |
+
},
|
| 732 |
+
"engines": {
|
| 733 |
+
"node": ">= 16"
|
| 734 |
+
},
|
| 735 |
+
"funding": {
|
| 736 |
+
"url": "https://github.com/sponsors/express-rate-limit"
|
| 737 |
+
},
|
| 738 |
+
"peerDependencies": {
|
| 739 |
+
"express": ">= 4.11"
|
| 740 |
+
}
|
| 741 |
+
},
|
| 742 |
+
"node_modules/express-rate-limit/node_modules/debug": {
|
| 743 |
+
"version": "4.4.3",
|
| 744 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 745 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 746 |
+
"license": "MIT",
|
| 747 |
+
"dependencies": {
|
| 748 |
+
"ms": "^2.1.3"
|
| 749 |
+
},
|
| 750 |
+
"engines": {
|
| 751 |
+
"node": ">=6.0"
|
| 752 |
+
},
|
| 753 |
+
"peerDependenciesMeta": {
|
| 754 |
+
"supports-color": {
|
| 755 |
+
"optional": true
|
| 756 |
+
}
|
| 757 |
+
}
|
| 758 |
+
},
|
| 759 |
+
"node_modules/express-rate-limit/node_modules/ms": {
|
| 760 |
+
"version": "2.1.3",
|
| 761 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 762 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 763 |
+
"license": "MIT"
|
| 764 |
+
},
|
| 765 |
"node_modules/filelist": {
|
| 766 |
"version": "1.0.6",
|
| 767 |
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
|
|
|
| 1033 |
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
| 1034 |
"license": "ISC"
|
| 1035 |
},
|
| 1036 |
+
"node_modules/ip-address": {
|
| 1037 |
+
"version": "10.2.0",
|
| 1038 |
+
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
| 1039 |
+
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
|
| 1040 |
+
"license": "MIT",
|
| 1041 |
+
"engines": {
|
| 1042 |
+
"node": ">= 12"
|
| 1043 |
+
}
|
| 1044 |
+
},
|
| 1045 |
"node_modules/ipaddr.js": {
|
| 1046 |
"version": "1.9.1",
|
| 1047 |
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
package.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
| 20 |
"dependencies": {
|
| 21 |
"ejs": "^3.1.10",
|
| 22 |
"express": "^4.21.2",
|
|
|
|
| 23 |
"mime-types": "^2.1.35",
|
| 24 |
"multer": "^1.4.5-lts.1",
|
| 25 |
"ws": "^8.18.0"
|
|
|
|
| 20 |
"dependencies": {
|
| 21 |
"ejs": "^3.1.10",
|
| 22 |
"express": "^4.21.2",
|
| 23 |
+
"express-rate-limit": "^8.6.0",
|
| 24 |
"mime-types": "^2.1.35",
|
| 25 |
"multer": "^1.4.5-lts.1",
|
| 26 |
"ws": "^8.18.0"
|
src/app.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
| 3 |
import { CONFIG } from './config/app.config';
|
| 4 |
import { requestLogger } from './middleware/logger.middleware';
|
| 5 |
import { errorHandler } from './middleware/error.middleware';
|
|
|
|
| 6 |
import { createApiRouter } from './routes/api.routes';
|
| 7 |
|
| 8 |
// Repositories
|
|
@@ -45,6 +46,7 @@ export function createApp(): AppContext {
|
|
| 45 |
const app = express();
|
| 46 |
|
| 47 |
// Middleware
|
|
|
|
| 48 |
app.use(express.json());
|
| 49 |
app.use(express.urlencoded({ extended: true }));
|
| 50 |
app.use(requestLogger);
|
|
|
|
| 3 |
import { CONFIG } from './config/app.config';
|
| 4 |
import { requestLogger } from './middleware/logger.middleware';
|
| 5 |
import { errorHandler } from './middleware/error.middleware';
|
| 6 |
+
import { globalRateLimiter } from './middleware/rateLimit.middleware';
|
| 7 |
import { createApiRouter } from './routes/api.routes';
|
| 8 |
|
| 9 |
// Repositories
|
|
|
|
| 46 |
const app = express();
|
| 47 |
|
| 48 |
// Middleware
|
| 49 |
+
app.use(globalRateLimiter);
|
| 50 |
app.use(express.json());
|
| 51 |
app.use(express.urlencoded({ extended: true }));
|
| 52 |
app.use(requestLogger);
|
src/middleware/rateLimit.middleware.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import rateLimit from 'express-rate-limit';
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* General application rate limiter to suppress bot scanning volume and protect endpoints.
|
| 5 |
+
* Allows legitimate dashboard background polling (every ~3s = 60 req/min) while blocking aggressive scanners.
|
| 6 |
+
*/
|
| 7 |
+
export const globalRateLimiter = rateLimit({
|
| 8 |
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
| 9 |
+
limit: 500, // Limit each IP to 500 requests per 15-minute window
|
| 10 |
+
standardHeaders: 'draft-7',
|
| 11 |
+
legacyHeaders: false,
|
| 12 |
+
message: {
|
| 13 |
+
status: 429,
|
| 14 |
+
error: 'Too Many Requests',
|
| 15 |
+
message: 'Rate limit exceeded. Please slow down your requests and try again later.',
|
| 16 |
+
},
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Strict rate limiter for sensitive state-modifying endpoints (e.g., worker shutdown, task reset/generation).
|
| 21 |
+
*/
|
| 22 |
+
export const strictActionRateLimiter = rateLimit({
|
| 23 |
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
| 24 |
+
limit: 30, // Limit each IP to 30 administrative actions per 15-minute window
|
| 25 |
+
standardHeaders: 'draft-7',
|
| 26 |
+
legacyHeaders: false,
|
| 27 |
+
message: {
|
| 28 |
+
status: 429,
|
| 29 |
+
error: 'Too Many Requests',
|
| 30 |
+
message: 'Rate limit exceeded for administrative actions. Please try again later.',
|
| 31 |
+
},
|
| 32 |
+
});
|
src/routes/api.routes.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { Router } from 'express';
|
| 2 |
import { HealthController } from '../controllers/health.controller';
|
| 3 |
import { AdminController } from '../controllers/admin.controller';
|
|
|
|
| 4 |
|
| 5 |
export function createApiRouter(
|
| 6 |
healthController: HealthController,
|
|
@@ -16,9 +17,12 @@ export function createApiRouter(
|
|
| 16 |
router.get('/workers', adminController.getWorkers);
|
| 17 |
router.get('/tasks', adminController.getTasks);
|
| 18 |
router.get('/dataset/rows', adminController.getDatasetRows);
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
router.post('/
|
|
|
|
|
|
|
| 22 |
|
| 23 |
return router;
|
| 24 |
}
|
|
|
|
|
|
| 1 |
import { Router } from 'express';
|
| 2 |
import { HealthController } from '../controllers/health.controller';
|
| 3 |
import { AdminController } from '../controllers/admin.controller';
|
| 4 |
+
import { strictActionRateLimiter } from '../middleware/rateLimit.middleware';
|
| 5 |
|
| 6 |
export function createApiRouter(
|
| 7 |
healthController: HealthController,
|
|
|
|
| 17 |
router.get('/workers', adminController.getWorkers);
|
| 18 |
router.get('/tasks', adminController.getTasks);
|
| 19 |
router.get('/dataset/rows', adminController.getDatasetRows);
|
| 20 |
+
|
| 21 |
+
// Sensitive administrative POST actions protected by strict rate limit
|
| 22 |
+
router.post('/tasks/create', strictActionRateLimiter, adminController.createTask);
|
| 23 |
+
router.post('/tasks/reset', strictActionRateLimiter, adminController.resetTasks);
|
| 24 |
+
router.post('/shutdown-worker', strictActionRateLimiter, adminController.shutdownWorker);
|
| 25 |
|
| 26 |
return router;
|
| 27 |
}
|
| 28 |
+
|