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

@@ -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();