Feat. Added rate limiting
This commit is contained in:
@@ -7,3 +7,12 @@ MINIO_USE_SSL=false
|
|||||||
MINIO_ACCESS_KEY=minioadmin
|
MINIO_ACCESS_KEY=minioadmin
|
||||||
MINIO_SECRET_KEY=minioadmin
|
MINIO_SECRET_KEY=minioadmin
|
||||||
MINIO_BUCKET=pastes
|
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",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "minipaste",
|
"name": "binit.app",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
"express-rate-limit": "^7.5.0",
|
||||||
|
"helmet": "^8.0.0",
|
||||||
"minio": "^8.0.0"
|
"minio": "^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -295,6 +297,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
@@ -336,6 +339,21 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"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": {
|
"node_modules/fast-xml-builder": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.0.tgz",
|
||||||
@@ -498,6 +516,15 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
|
"express-rate-limit": "^7.5.0",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
"helmet": "^8.0.0",
|
||||||
"minio": "^8.0.0"
|
"minio": "^8.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/index.js
53
src/index.js
@@ -1,15 +1,68 @@
|
|||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const helmet = require('helmet');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
const { ensureBucket } = require('./lib/minio');
|
const { ensureBucket } = require('./lib/minio');
|
||||||
const pastesRouter = require('./routes/pastes');
|
const pastesRouter = require('./routes/pastes');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
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.json({ limit: '110kb' }));
|
||||||
|
app.use(express.urlencoded({ extended: false, limit: '110kb' }));
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
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);
|
app.use('/api/pastes', pastesRouter);
|
||||||
|
|
||||||
// Serve the view page for any paste URL
|
// Serve the view page for any paste URL
|
||||||
|
|||||||
Reference in New Issue
Block a user