Feat. Added rate limiting
This commit is contained in:
@@ -7,3 +7,12 @@ MINIO_USE_SSL=false
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
MINIO_BUCKET=pastes
|
||||
|
||||
# Set true when running behind a reverse proxy (Nginx, Cloudflare, etc.)
|
||||
TRUST_PROXY=false
|
||||
|
||||
# API rate limiting
|
||||
RATE_LIMIT_READ_WINDOW_MS=60000
|
||||
RATE_LIMIT_READ_MAX=240
|
||||
RATE_LIMIT_CREATE_WINDOW_MS=600000
|
||||
RATE_LIMIT_CREATE_MAX=40
|
||||
|
||||
31
package-lock.json
generated
31
package-lock.json
generated
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "minipaste",
|
||||
"name": "binit.app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "minipaste",
|
||||
"name": "binit.app",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.21.0",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"helmet": "^8.0.0",
|
||||
"minio": "^8.0.0"
|
||||
}
|
||||
},
|
||||
@@ -295,6 +297,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -336,6 +339,21 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
|
||||
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/express-rate-limit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": ">= 4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.0.tgz",
|
||||
@@ -498,6 +516,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/helmet": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
|
||||
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"express": "^4.21.0",
|
||||
"helmet": "^8.0.0",
|
||||
"minio": "^8.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
53
src/index.js
53
src/index.js
@@ -1,15 +1,68 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { ensureBucket } = require('./lib/minio');
|
||||
const pastesRouter = require('./routes/pastes');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
function envInt(name, fallback) {
|
||||
const raw = process.env[name];
|
||||
if (!raw) {
|
||||
return fallback;
|
||||
}
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
const READ_LIMIT_WINDOW_MS = envInt('RATE_LIMIT_READ_WINDOW_MS', 60 * 1000);
|
||||
const READ_LIMIT_MAX = envInt('RATE_LIMIT_READ_MAX', 240);
|
||||
const CREATE_LIMIT_WINDOW_MS = envInt('RATE_LIMIT_CREATE_WINDOW_MS', 10 * 60 * 1000);
|
||||
const CREATE_LIMIT_MAX = envInt('RATE_LIMIT_CREATE_MAX', 40);
|
||||
|
||||
app.disable('x-powered-by');
|
||||
|
||||
// If deployed behind a reverse proxy/load balancer, set TRUST_PROXY=true.
|
||||
if (process.env.TRUST_PROXY === 'true') {
|
||||
app.set('trust proxy', 1);
|
||||
}
|
||||
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: false,
|
||||
crossOriginEmbedderPolicy: false,
|
||||
}));
|
||||
|
||||
const createPasteLimiter = rateLimit({
|
||||
windowMs: CREATE_LIMIT_WINDOW_MS,
|
||||
max: CREATE_LIMIT_MAX,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: 'Too many paste creations. Try again later.' },
|
||||
});
|
||||
|
||||
const readPasteLimiter = rateLimit({
|
||||
windowMs: READ_LIMIT_WINDOW_MS,
|
||||
max: READ_LIMIT_MAX,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: 'Too many requests. Slow down.' },
|
||||
});
|
||||
|
||||
app.use(express.json({ limit: '110kb' }));
|
||||
app.use(express.urlencoded({ extended: false, limit: '110kb' }));
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/api/pastes', readPasteLimiter);
|
||||
app.use('/api/pastes', (req, res, next) => {
|
||||
if (req.method === 'POST') {
|
||||
return createPasteLimiter(req, res, next);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use('/api/pastes', pastesRouter);
|
||||
|
||||
// Serve the view page for any paste URL
|
||||
|
||||
Reference in New Issue
Block a user