diff --git a/config.json.example b/config.json.example index a2eee23..82d30fe 100644 --- a/config.json.example +++ b/config.json.example @@ -22,5 +22,12 @@ "mode": "development", "readOnly": false, "webhooks": [], - "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"] // List of supported categories any other category will be rejected + "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], // List of supported categories any other category will be rejected + "rateLimit": { + "vote": { + "windowMs": 900000, // 15 minutes + "max": 20, // 20 requests in 15min time window + "message": "Too many votes, please try again later" + } + } } diff --git a/package-lock.json b/package-lock.json index 0878451..c608133 100644 --- a/package-lock.json +++ b/package-lock.json @@ -785,6 +785,11 @@ } } }, + "express-rate-limit": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", + "integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1866,6 +1871,11 @@ "semver": "^5.7.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-forge": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", diff --git a/package.json b/package.json index 4a44856..d3112d2 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,14 @@ "dependencies": { "better-sqlite3": "^5.4.3", "express": "^4.17.1", + "express-rate-limit": "^5.1.3", "http": "0.0.0", "iso8601-duration": "^1.2.0", + "node-fetch": "^2.6.0", "redis": "^3.0.2", "sync-mysql": "^3.0.1", "uuid": "^3.3.2", - "youtube-api": "^2.0.10", - "node-fetch": "^2.6.0" + "youtube-api": "^2.0.10" }, "devDependencies": { "mocha": "^7.1.1", diff --git a/src/app.js b/src/app.js index a102a76..0f12117 100644 --- a/src/app.js +++ b/src/app.js @@ -3,8 +3,11 @@ var express = require('express'); var app = express(); var config = require('./config.js'); var redis = require('./utils/redis.js'); +const getIP = require('./utils/getIP.js'); +const getHash = require('./utils/getHash.js'); // Middleware +const voteRateLimitMiddleware = require('./middleware/voteRateLimit.js'); var corsMiddleware = require('./middleware/cors.js'); var loggerMiddleware = require('./middleware/logger.js'); const userCounter = require('./middleware/userCounter.js'); @@ -59,8 +62,8 @@ app.post('/api/skipSegments', postSkipSegments); app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); //voting endpoint -app.get('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint); -app.post('/api/voteOnSponsorTime', voteOnSponsorTime.endpoint); +app.get('/api/voteOnSponsorTime', voteRateLimitMiddleware, voteOnSponsorTime.endpoint); +app.post('/api/voteOnSponsorTime', voteRateLimitMiddleware, voteOnSponsorTime.endpoint); //Endpoint when a sponsorTime is used up app.get('/api/viewedVideoSponsorTime', viewedVideoSponsorTime); diff --git a/src/middleware/voteRateLimit.js b/src/middleware/voteRateLimit.js new file mode 100644 index 0000000..50120ff --- /dev/null +++ b/src/middleware/voteRateLimit.js @@ -0,0 +1,18 @@ +const config = require('../config.js'); +const getIP = require('../utils/getIP.js'); +const getHash = require('../utils/getHash.js'); +const rateLimit = require('express-rate-limit'); + +module.exports = rateLimit({ + windowMs: config.rateLimit.vote.windowMs, + max: config.rateLimit.vote.max, + message: config.rateLimit.vote.message, + headers: false, + keyGenerator: (req /*, res*/) => { + return getHash(req.ip, 1); + }, + skip: (/*req, res*/) => { + // skip rate limit if running in test mode + return process.env.npm_lifecycle_script === 'node test.js'; + } +}); diff --git a/test.json b/test.json index 8905d83..23ac900 100644 --- a/test.json +++ b/test.json @@ -49,5 +49,12 @@ ] } ], - "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"] + "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], + "rateLimit": { + "vote": { + "windowMs": 900000, + "max": 20, + "message": "Too many votes, please try again later" + } + } }