Add new user limit per 5 mins

This commit is contained in:
Ajay 2025-04-10 02:26:09 -04:00
parent 9b55dc5d4d
commit 74f6224091
3 changed files with 43 additions and 25 deletions

View file

@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Logger } from "../utils/logger";
import { isUserVIP } from "../utils/isUserVIP";
import { isUserTempVIP } from "../utils/isUserTempVIP";
import { getMaxResThumbnail, YouTubeAPI } from "../utils/youtubeApi";
import { getMaxResThumbnail } from "../utils/youtubeApi";
import { db, privateDB } from "../databases/databases";
import { dispatchEvent, getVoteAuthor, getVoteAuthorRaw } from "../utils/webhookUtils";
import { getFormattedTime } from "../utils/getFormattedTime";
@ -17,7 +17,6 @@ import { getVideoDetails, videoDetails } from "../utils/getVideoDetails";
import { deleteLockCategories } from "./deleteLockCategories";
import { acquireLock } from "../utils/redisLock";
import { checkBanStatus } from "../utils/checkBan";
import { canVote } from "../utils/permissions";
const voteTypes = {
normal: 0,
@ -343,14 +342,6 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
const nonAnonUserID = await getHashCache(paramUserID);
const userID = await getHashCache(paramUserID + UUID);
const permission = await canVote(nonAnonUserID);
if (!permission.canSubmit) {
return {
status: 403,
message: permission.reason
};
}
//hash the ip 5000 times so no one can get it from the database
const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress);

View file

@ -5,6 +5,7 @@ import { Feature, HashedUserID } from "../types/user.model";
import { hasFeature } from "./features";
import { isUserVIP } from "./isUserVIP";
import { oneOf } from "./promise";
import redis from "./redis";
import { getReputation } from "./reputation";
import { getServerConfig } from "./serverConfig";
@ -20,7 +21,8 @@ async function lowDownvotes(userID: HashedUserID): Promise<boolean> {
return result.submissionCount > 5 && result.downvotedSubmissions / result.submissionCount < 0.10;
}
async function oldSubmitter(userID: HashedUserID): Promise<boolean> {
const fiveMinutes = 5 * 60 * 1000;
async function oldSubmitterOrAllowed(userID: HashedUserID): Promise<boolean> {
const submitterThreshold = await getServerConfig("old-submitter-block-date");
if (!submitterThreshold) {
return true;
@ -29,10 +31,22 @@ async function oldSubmitter(userID: HashedUserID): Promise<boolean> {
const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND "timeSubmitted" < ?`
, [userID, parseInt(submitterThreshold)], { useReplica: true });
return result.submissionCount >= 1;
const isOldSubmitter = result.submissionCount >= 1;
if (!isOldSubmitter) {
await redis.zRemRangeByScore("submitters", "-inf", Date.now() - fiveMinutes);
const last5MinUsers = await redis.zCard("submitters");
const maxUsers = await getServerConfig("max-users-per-minute");
if (maxUsers && last5MinUsers < parseInt(maxUsers)) {
await redis.zAdd("submitters", { score: Date.now(), value: userID });
return true;
}
}
return isOldSubmitter;
}
async function oldDeArrowSubmitter(userID: HashedUserID): Promise<boolean> {
async function oldDeArrowSubmitterOrAllowed(userID: HashedUserID): Promise<boolean> {
const submitterThreshold = await getServerConfig("old-submitter-block-date");
if (!submitterThreshold) {
return true;
@ -41,7 +55,19 @@ async function oldDeArrowSubmitter(userID: HashedUserID): Promise<boolean> {
const result = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "titles" WHERE "userID" = ? AND "timeSubmitted" < 1743827196000`
, [userID, parseInt(submitterThreshold)], { useReplica: true });
return result.submissionCount >= 1;
const isOldSubmitter = result.submissionCount >= 1;
if (!isOldSubmitter) {
await redis.zRemRangeByScore("submittersDeArrow", "-inf", Date.now() - fiveMinutes);
const last5MinUsers = await redis.zCard("submittersDeArrow");
const maxUsers = await getServerConfig("max-users-per-minute-dearrow");
if (maxUsers && last5MinUsers < parseInt(maxUsers)) {
await redis.zAdd("submittersDeArrow", { score: Date.now(), value: userID });
return true;
}
}
return isOldSubmitter;
}
export async function canSubmit(userID: HashedUserID, category: Category): Promise<CanSubmitResult> {
@ -66,16 +92,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi
export async function canSubmitGlobal(userID: HashedUserID): Promise<CanSubmitResult> {
return {
canSubmit: await oneOf([isUserVIP(userID),
oldSubmitter(userID)
]),
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
};
}
export async function canVote(userID: HashedUserID): Promise<CanSubmitResult> {
return {
canSubmit: await oneOf([isUserVIP(userID),
oldSubmitter(userID)
oldSubmitterOrAllowed(userID)
]),
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
};
@ -84,7 +101,7 @@ export async function canVote(userID: HashedUserID): Promise<CanSubmitResult> {
export async function canSubmitDeArrow(userID: HashedUserID): Promise<CanSubmitResult> {
return {
canSubmit: await oneOf([isUserVIP(userID),
oldDeArrowSubmitter(userID)
oldDeArrowSubmitterOrAllowed(userID)
]),
reason: "We are currently experiencing a mass spam attack, we are restricting submissions for now"
};

View file

@ -9,6 +9,7 @@ import { Postgres } from "../databases/Postgres";
import { compress, uncompress } from "lz4-napi";
import { LRUCache } from "lru-cache";
import { shouldClientCacheKey } from "./redisKeys";
import { ZMember } from "@redis/client/dist/lib/commands/generic-transformers";
export interface RedisStats {
activeRequests: number;
@ -35,6 +36,9 @@ interface RedisSB {
sendCommand(args: RedisCommandArguments, options?: RedisClientOptions): Promise<RedisReply>;
ttl(key: RedisCommandArgument): Promise<number>;
quit(): Promise<void>;
zRemRangeByScore(key: string, min: number | RedisCommandArgument, max: number | RedisCommandArgument): Promise<number>;
zAdd(key: string, members: ZMember | ZMember[]): Promise<number>;
zCard(key: string): Promise<number>;
}
let exportClient: RedisSB = {
@ -49,6 +53,9 @@ let exportClient: RedisSB = {
sendCommand: () => Promise.resolve(null),
quit: () => Promise.resolve(null),
ttl: () => Promise.resolve(null),
zRemRangeByScore: () => Promise.resolve(null),
zAdd: () => Promise.resolve(null),
zCard: () => Promise.resolve(null)
};
let lastClientFail = 0;
@ -308,6 +315,9 @@ if (config.redis?.enabled) {
.then((reply) => resolve(reply))
.catch((err) => reject(err))
);
exportClient.zRemRangeByScore = client.zRemRangeByScore.bind(client);
exportClient.zAdd = client.zAdd.bind(client);
exportClient.zCard = client.zCard.bind(client);
/* istanbul ignore next */
client.on("error", function(error) {
lastClientFail = Date.now();