From 36f654f41c549d71eb32f44e894beab879d9efae Mon Sep 17 00:00:00 2001 From: Nanobyte Date: Wed, 16 Sep 2020 22:40:11 +0200 Subject: [PATCH] Blocking users with too many active warnings from submitting votes and submissions --- config.json.example | 4 ++- src/config.js | 4 ++- src/routes/postSkipSegments.js | 10 ++++++ src/routes/voteOnSponsorTime.js | 10 ++++++ test.json | 4 ++- test/cases/postSkipSegments.js | 64 +++++++++++++++++++++++++++++++++ test/cases/voteOnSponsorTime.js | 32 +++++++++++++++++ 7 files changed, 125 insertions(+), 3 deletions(-) diff --git a/config.json.example b/config.json.example index a2eee23..f3a2413 100644 --- a/config.json.example +++ b/config.json.example @@ -22,5 +22,7 @@ "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 + "maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire + "hoursAfterWarningExpire": 24 } diff --git a/src/config.js b/src/config.js index d85b930..b3f30d3 100644 --- a/src/config.js +++ b/src/config.js @@ -20,7 +20,9 @@ addDefaults(config, { "privateDBSchema": "./databases/_private.db.sql", "readOnly": false, "webhooks": [], - "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"] + "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], + "maxNumberOfActiveWarnings": 3, + "hoursAfterWarningExpires": 24 }) module.exports = config; diff --git a/src/routes/postSkipSegments.js b/src/routes/postSkipSegments.js index 820e0cf..4961e92 100644 --- a/src/routes/postSkipSegments.js +++ b/src/routes/postSkipSegments.js @@ -269,6 +269,16 @@ module.exports = async function postSkipSegments(req, res) { //hash the ip 5000 times so no one can get it from the database let hashedIP = getHash(getIP(req) + config.globalSalt); + + const MILLISECONDS_IN_HOUR = 3600000; + const now = Date.now(); + let warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?", + [userID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))] + ).count; + + if (warningsCount >= config.maxNumberOfActiveWarnings) { + return res.status(403).send('Submission blocked. Too many active warnings!'); + } let noSegmentList = db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID]).map((list) => { return list.category }); diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.js index e8c4edc..0e6de00 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.js @@ -235,6 +235,16 @@ async function voteOnSponsorTime(req, res) { return; } } + + const MILLISECONDS_IN_HOUR = 3600000; + const now = Date.now(); + let warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?", + [nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))] + ).count; + + if (warningsCount >= config.maxNumberOfActiveWarnings) { + return res.status(403).send('Vote blocked. Too many active warnings!'); + } let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect; diff --git a/test.json b/test.json index 8905d83..c620be4 100644 --- a/test.json +++ b/test.json @@ -49,5 +49,7 @@ ] } ], - "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"] + "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], + "maxNumberOfActiveWarnings": 3, + "hoursAfterWarningExpires": 24 } diff --git a/test/cases/postSkipSegments.js b/test/cases/postSkipSegments.js index a2e2c97..b8966b1 100644 --- a/test/cases/postSkipSegments.js +++ b/test/cases/postSkipSegments.js @@ -1,5 +1,7 @@ var assert = require('assert'); var request = require('request'); +var config = require('../../src/config.js'); +var getHash = require('../../src/utils/getHash.js'); var utils = require('../utils.js'); @@ -7,6 +9,24 @@ var databases = require('../../src/databases/databases.js'); var db = databases.db; describe('postSkipSegments', () => { + before(() => { + const now = Date.now(); + const warnVip01Hash = getHash("warn-vip01"); + const warnUser01Hash = getHash("warn-user01"); + const warnUser02Hash = getHash("warn-user02"); + const MILLISECONDS_IN_HOUR = 3600000; + const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; + const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES'; + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-1000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-2000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-3601000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')"); + }); + it('Should be able to submit a single time (Params method)', (done) => { request.post(utils.getbaseURL() + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null, @@ -130,6 +150,50 @@ describe('postSkipSegments', () => { }); }); + it('Should be rejected if user has to many active warnings', (done) => { + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "warn-user01", + videoID: "dQw4w9WgXcF", + segments: [{ + segment: [0, 10], + category: "sponsor" + }] + } + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); // success + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be accepted if user has some active warnings', (done) => { + request.post(utils.getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "warn-user02", + videoID: "dQw4w9WgXcF", + segments: [{ + segment: [50, 60], + category: "sponsor" + }] + } + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); // success + } else { + done("Status code was " + res.statusCode + " " + body); + } + }); + }); + it('Should be allowed if youtube thinks duration is 0', (done) => { request.get(utils.getbaseURL() + "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", null, diff --git a/test/cases/voteOnSponsorTime.js b/test/cases/voteOnSponsorTime.js index 2e94a0b..084d0f9 100644 --- a/test/cases/voteOnSponsorTime.js +++ b/test/cases/voteOnSponsorTime.js @@ -1,11 +1,19 @@ const request = require('request'); +const config = require('../../src/config.js'); const { db, privateDB } = require('../../src/databases/databases.js'); const utils = require('../utils.js'); const getHash = require('../../src/utils/getHash.js'); describe('voteOnSponsorTime', () => { before(() => { + const now = Date.now(); + const warnVip01Hash = getHash("warn-vip01"); + const warnUser01Hash = getHash("warn-voteuser01"); + const warnUser02Hash = getHash("warn-voteuser02"); + const MILLISECONDS_IN_HOUR = 3600000; + const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES'; db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 2, 'vote-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest2', 1) + "')"); @@ -25,6 +33,17 @@ describe('voteOnSponsorTime', () => { db.exec(startOfQuery + "('not-own-submission-video', 1, 11, 500, 'not-own-submission-uuid', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('not-own-submission-video', 1) + "')"); db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category', 1) + "')"); db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category-change', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'warnvote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); + + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-1000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-2000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-3601000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')"); + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')"); privateDB.exec("INSERT INTO shadowBannedUsers (userID) VALUES ('" + getHash("randomID4") + "')"); @@ -332,4 +351,17 @@ describe('voteOnSponsorTime', () => { }); }); + it('Should not be able to upvote a segment (Too many warning)', (done) => { + request.get(utils.getbaseURL() + + "/api/voteOnSponsorTime?userID=warn-voteuser01&UUID=warnvote-uuid-0&type=1", null, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); // success + } else { + done("Status code was " + res.statusCode); + } + }); + }); + }); \ No newline at end of file