create an isUserBanned utility function

This commit is contained in:
mini-bomba
2023-08-29 13:28:08 +02:00
parent c77e71e66a
commit c2a3630d49
6 changed files with 88 additions and 63 deletions

View File

@@ -8,12 +8,12 @@ import { getReputation } from "../utils/reputation";
import { Category, SegmentUUID } from "../types/segments.model"; import { Category, SegmentUUID } from "../types/segments.model";
import { config } from "../config"; import { config } from "../config";
import { canSubmit } from "../utils/permissions"; import { canSubmit } from "../utils/permissions";
import { isUserBanned } from "../utils/checkBan";
const maxRewardTime = config.maxRewardTimePerSegmentInSeconds; const maxRewardTime = config.maxRewardTimePerSegmentInSeconds;
async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> {
try { try {
const userBanCount = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount; const countShadowHidden = await isUserBanned(userID) ? 2 : 1; // if shadowbanned, count shadowhidden as well
const countShadowHidden = userBanCount > 0 ? 2 : 1; // if shadowbanned, count shadowhidden as well
const row = await db.prepare("get", const row = await db.prepare("get",
`SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved", `SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved",
count(*) as "segmentCount" FROM "sponsorTimes" count(*) as "segmentCount" FROM "sponsorTimes"
@@ -111,8 +111,7 @@ async function dbGetActiveWarningReasonForUser(userID: HashedUserID): Promise<st
async function dbGetBanned(userID: HashedUserID): Promise<boolean> { async function dbGetBanned(userID: HashedUserID): Promise<boolean> {
try { try {
const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID], { useReplica: true }); return await isUserBanned(userID);
return row?.userCount > 0 ?? false;
} catch (err) /* istanbul ignore next */ { } catch (err) /* istanbul ignore next */ {
return false; return false;
} }

View File

@@ -4,6 +4,7 @@ import { Request, Response } from "express";
import { HashedUserID, UserID } from "../types/user.model"; import { HashedUserID, UserID } from "../types/user.model";
import { config } from "../config"; import { config } from "../config";
import { Logger } from "../utils/logger"; import { Logger } from "../utils/logger";
import { isUserBanned } from "../utils/checkBan";
type nestedObj = Record<string, Record<string, number>>; type nestedObj = Record<string, Record<string, number>>;
const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400; const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400;
@@ -34,8 +35,7 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea
`; `;
} }
try { try {
const userBanCount = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount; const countShadowHidden = await isUserBanned(userID) ? 2 : 1; // if shadowbanned, count shadowhidden as well
const countShadowHidden = userBanCount > 0 ? 2 : 1; // if shadowbanned, count shadowhidden as well
const row = await db.prepare("get", ` const row = await db.prepare("get", `
SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved", SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved",
${additionalQuery} ${additionalQuery}

View File

@@ -9,7 +9,7 @@ import { getIP } from "../utils/getIP";
import { getFormattedTime } from "../utils/getFormattedTime"; import { getFormattedTime } from "../utils/getFormattedTime";
import { dispatchEvent } from "../utils/webhookUtils"; import { dispatchEvent } from "../utils/webhookUtils";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { ActionType, Category, IncomingSegment, IPAddress, SegmentUUID, Service, VideoDuration, VideoID } from "../types/segments.model"; import { ActionType, Category, HashedIP, IncomingSegment, IPAddress, SegmentUUID, Service, VideoDuration, VideoID } from "../types/segments.model";
import { deleteLockCategories } from "./deleteLockCategories"; import { deleteLockCategories } from "./deleteLockCategories";
import { QueryCacher } from "../utils/queryCacher"; import { QueryCacher } from "../utils/queryCacher";
import { getReputation } from "../utils/reputation"; import { getReputation } from "../utils/reputation";
@@ -23,8 +23,8 @@ import { vote } from "./voteOnSponsorTime";
import { canSubmit } from "../utils/permissions"; import { canSubmit } from "../utils/permissions";
import { getVideoDetails, videoDetails } from "../utils/getVideoDetails"; import { getVideoDetails, videoDetails } from "../utils/getVideoDetails";
import * as youtubeID from "../utils/youtubeID"; import * as youtubeID from "../utils/youtubeID";
import { banUser } from "./shadowBanUser";
import { acquireLock } from "../utils/redisLock"; import { acquireLock } from "../utils/redisLock";
import { checkBanStatus } from "../utils/checkBan";
type CheckResult = { type CheckResult = {
pass: boolean, pass: boolean,
@@ -544,7 +544,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
const newSegments = []; const newSegments = [];
//hash the ip 5000 times so no one can get it from the database //hash the ip 5000 times so no one can get it from the database
const hashedIP = await getHashCache(rawIP + config.globalSalt); const hashedIP = await getHashCache(rawIP + config.globalSalt) as HashedIP;
const timeSubmitted = Date.now(); const timeSubmitted = Date.now();
@@ -554,22 +554,14 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
// } // }
//check to see if this user is shadowbanned //check to see if this user is shadowbanned
const userBanCount = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount; const isBanned = await checkBanStatus(userID, hashedIP);
const ipBanCount = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedIPs" WHERE "hashedIP" = ? LIMIT 1`, [hashedIP]))?.userCount;
const shadowBanCount = userBanCount || ipBanCount;
const startingVotes = 0; const startingVotes = 0;
const reputation = await getReputation(userID); const reputation = await getReputation(userID);
if (!userBanCount && ipBanCount) {
// Make sure the whole user is banned
banUser(userID, true, true, 1, config.categoryList as Category[], config.deArrowTypes)
.catch((e) => Logger.error(`Error banning user after submitting from a banned IP: ${e}`));
}
for (const segmentInfo of segments) { for (const segmentInfo of segments) {
// Full segments are always rejected since there can only be one, so shadow hide wouldn't work // Full segments are always rejected since there can only be one, so shadow hide wouldn't work
if (segmentInfo.ignoreSegment if (segmentInfo.ignoreSegment
|| (shadowBanCount && segmentInfo.actionType === ActionType.Full)) { || (isBanned && segmentInfo.actionType === ActionType.Full)) {
continue; continue;
} }
@@ -586,7 +578,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "userAgent", "description") ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "userAgent", "description")
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0 videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0
, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, shadowBanCount, hashedVideoID, userAgent, segmentInfo.description , segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, isBanned ? 1 : 0, hashedVideoID, userAgent, segmentInfo.description
], ],
); );

View File

@@ -3,6 +3,8 @@ import { Logger } from "../utils/logger";
import { db, privateDB } from "../databases/databases"; import { db, privateDB } from "../databases/databases";
import { getHashCache } from "../utils/getHashCache"; import { getHashCache } from "../utils/getHashCache";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { isUserBanned } from "../utils/checkBan";
import { HashedUserID } from "../types/user.model";
function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> { function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> {
return privateDB.prepare("run", return privateDB.prepare("run",
@@ -12,12 +14,12 @@ function logUserNameChange(userID: string, newUserName: string, oldUserName: str
} }
export async function setUsername(req: Request, res: Response): Promise<Response> { export async function setUsername(req: Request, res: Response): Promise<Response> {
let userID = req.query.userID as string; const userIDInput = req.query.userID as string;
const adminUserIDInput = req.query.adminUserID as string;
let userName = req.query.username as string; let userName = req.query.username as string;
let hashedUserID: HashedUserID;
let adminUserIDInput = req.query.adminUserID as string; if (userIDInput == undefined || userName == undefined || userIDInput === "undefined" || userName.length > 64) {
if (userID == undefined || userName == undefined || userID === "undefined" || userName.length > 64) {
//invalid request //invalid request
return res.sendStatus(400); return res.sendStatus(400);
} }
@@ -35,41 +37,37 @@ export async function setUsername(req: Request, res: Response): Promise<Response
userName = userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, ""); userName = userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
try { try {
// check privateID against publicID
if (!await checkPrivateUsername(userName, userID)) {
return res.sendStatus(400);
}
timings.push(Date.now()); timings.push(Date.now());
if (adminUserIDInput != undefined) { if (adminUserIDInput != undefined) {
//this is the admin controlling the other users account, don't hash the controling account's ID //this is the admin controlling the other users account, don't hash the controling account's ID
adminUserIDInput = await getHashCache(adminUserIDInput); hashedUserID = userIDInput as HashedUserID;
if (adminUserIDInput != config.adminUserID) { if (await getHashCache(adminUserIDInput) != config.adminUserID) {
//they aren't the admin //they aren't the admin
return res.sendStatus(403); return res.sendStatus(403);
} }
} else { } else {
// check privateID against publicID
if (!await checkPrivateUsername(userName, userIDInput)) {
return res.sendStatus(400);
}
//hash the userID //hash the userID
userID = await getHashCache(userID); hashedUserID = await getHashCache(userIDInput) as HashedUserID;
}
timings.push(Date.now()); timings.push(Date.now());
const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "userNames" WHERE "userID" = ? AND "locked" = 1`, [userID]); const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "userNames" WHERE "userID" = ? AND "locked" = 1`, [hashedUserID]);
if (adminUserIDInput === undefined && row.userCount > 0) { if (row.userCount > 0) {
return res.sendStatus(200); return res.sendStatus(200);
} }
timings.push(Date.now()); timings.push(Date.now());
const shadowBanRow = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]); if (await isUserBanned(hashedUserID)) {
if (adminUserIDInput === undefined && shadowBanRow.userCount > 0) {
return res.sendStatus(200); return res.sendStatus(200);
} }
}
timings.push(Date.now());
} }
catch (error) /* istanbul ignore next */ { catch (error) /* istanbul ignore next */ {
Logger.error(error as string); Logger.error(error as string);
@@ -78,7 +76,7 @@ export async function setUsername(req: Request, res: Response): Promise<Response
try { try {
//check if username is already set //check if username is already set
const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ? LIMIT 1`, [userID]); const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ? LIMIT 1`, [hashedUserID]);
const locked = adminUserIDInput === undefined ? 0 : 1; const locked = adminUserIDInput === undefined ? 0 : 1;
let oldUserName = ""; let oldUserName = "";
@@ -87,19 +85,19 @@ export async function setUsername(req: Request, res: Response): Promise<Response
if (row?.userName !== undefined) { if (row?.userName !== undefined) {
//already exists, update this row //already exists, update this row
oldUserName = row.userName; oldUserName = row.userName;
if (userName == userID && !locked) { if (userName == hashedUserID && !locked) {
await db.prepare("run", `DELETE FROM "userNames" WHERE "userID" = ?`, [userID]); await db.prepare("run", `DELETE FROM "userNames" WHERE "userID" = ?`, [hashedUserID]);
} else { } else {
await db.prepare("run", `UPDATE "userNames" SET "userName" = ?, "locked" = ? WHERE "userID" = ?`, [userName, locked, userID]); await db.prepare("run", `UPDATE "userNames" SET "userName" = ?, "locked" = ? WHERE "userID" = ?`, [userName, locked, hashedUserID]);
} }
} else { } else {
//add to the db //add to the db
await db.prepare("run", `INSERT INTO "userNames"("userID", "userName", "locked") VALUES(?, ?, ?)`, [userID, userName, locked]); await db.prepare("run", `INSERT INTO "userNames"("userID", "userName", "locked") VALUES(?, ?, ?)`, [hashedUserID, userName, locked]);
} }
timings.push(Date.now()); timings.push(Date.now());
await logUserNameChange(userID, userName, oldUserName, adminUserIDInput !== undefined); await logUserNameChange(hashedUserID, userName, oldUserName, adminUserIDInput !== undefined);
timings.push(Date.now()); timings.push(Date.now());

View File

@@ -9,13 +9,14 @@ import { getFormattedTime } from "../utils/getFormattedTime";
import { getIP } from "../utils/getIP"; import { getIP } from "../utils/getIP";
import { getHashCache } from "../utils/getHashCache"; import { getHashCache } from "../utils/getHashCache";
import { config } from "../config"; import { config } from "../config";
import { UserID } from "../types/user.model"; import { HashedUserID, UserID } from "../types/user.model";
import { DBSegment, Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, VideoDuration, ActionType, VoteType } from "../types/segments.model"; import { DBSegment, Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, VideoDuration, ActionType, VoteType } from "../types/segments.model";
import { QueryCacher } from "../utils/queryCacher"; import { QueryCacher } from "../utils/queryCacher";
import axios from "axios"; import axios from "axios";
import { getVideoDetails, videoDetails } from "../utils/getVideoDetails"; import { getVideoDetails, videoDetails } from "../utils/getVideoDetails";
import { deleteLockCategories } from "./deleteLockCategories"; import { deleteLockCategories } from "./deleteLockCategories";
import { acquireLock } from "../utils/redisLock"; import { acquireLock } from "../utils/redisLock";
import { checkBanStatus } from "../utils/checkBan";
const voteTypes = { const voteTypes = {
normal: 0, normal: 0,
@@ -208,7 +209,7 @@ async function sendWebhooks(voteData: VoteData) {
} }
} }
async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, isTempVIP: boolean, isOwnSubmission: boolean, category: Category async function categoryVote(UUID: SegmentUUID, userID: HashedUserID, isVIP: boolean, isTempVIP: boolean, isOwnSubmission: boolean, category: Category
, hashedIP: HashedIP, finalResponse: FinalResponse): Promise<{ status: number, message?: string }> { , hashedIP: HashedIP, finalResponse: FinalResponse): Promise<{ status: number, message?: string }> {
// Check if they've already made a vote // Check if they've already made a vote
const usersLastVoteInfo = await privateDB.prepare("get", `select count(*) as votes, category from "categoryVotes" where "UUID" = ? and "userID" = ? group by category`, [UUID, userID], { useReplica: true }); const usersLastVoteInfo = await privateDB.prepare("get", `select count(*) as votes, category from "categoryVotes" where "UUID" = ? and "userID" = ? group by category`, [UUID, userID], { useReplica: true });
@@ -244,8 +245,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i
const timeSubmitted = Date.now(); const timeSubmitted = Date.now();
const voteAmount = (isVIP || isTempVIP) ? 500 : 1; const voteAmount = (isVIP || isTempVIP) ? 500 : 1;
const ableToVote = finalResponse.finalStatus === 200 const ableToVote = finalResponse.finalStatus === 200; // ban status checks handled by vote() (caller function)
&& (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID], { useReplica: true })) === undefined;
if (ableToVote) { if (ableToVote) {
// Add the vote // Add the vote
@@ -336,6 +336,9 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
const nonAnonUserID = await getHashCache(paramUserID); const nonAnonUserID = await getHashCache(paramUserID);
const userID = await getHashCache(paramUserID + UUID); const userID = await getHashCache(paramUserID + UUID);
//hash the ip 5000 times so no one can get it from the database
const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress);
const lock = await acquireLock(`voteOnSponsorTime:${UUID}.${paramUserID}`); const lock = await acquireLock(`voteOnSponsorTime:${UUID}.${paramUserID}`);
if (!lock.status) { if (!lock.status) {
return { status: 429, message: "Vote already in progress" }; return { status: 429, message: "Vote already in progress" };
@@ -350,9 +353,6 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
webhookMessage: null webhookMessage: null
}; };
//hash the ip 5000 times so no one can get it from the database
const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress);
const segmentInfo: DBSegment = await db.prepare("get", `SELECT * from "sponsorTimes" WHERE "UUID" = ?`, [UUID]); const segmentInfo: DBSegment = await db.prepare("get", `SELECT * from "sponsorTimes" WHERE "UUID" = ?`, [UUID]);
// segment doesnt exist // segment doesnt exist
if (!segmentInfo) { if (!segmentInfo) {
@@ -362,6 +362,7 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
const isTempVIP = await isUserTempVIP(nonAnonUserID, segmentInfo.videoID); const isTempVIP = await isUserTempVIP(nonAnonUserID, segmentInfo.videoID);
const isVIP = await isUserVIP(nonAnonUserID); const isVIP = await isUserVIP(nonAnonUserID);
const isBanned = await checkBanStatus(nonAnonUserID, hashedIP); // propagates IP bans
//check if user voting on own submission //check if user voting on own submission
const isOwnSubmission = nonAnonUserID === segmentInfo.userID; const isOwnSubmission = nonAnonUserID === segmentInfo.userID;
@@ -380,11 +381,19 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
if (warnings.length >= config.maxNumberOfActiveWarnings) { if (warnings.length >= config.maxNumberOfActiveWarnings) {
const warningReason = warnings[0]?.reason; const warningReason = warnings[0]?.reason;
lock.unlock();
return { status: 403, message: "Vote rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. " + return { status: 403, message: "Vote rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. " +
"Could you please send a message in Discord or Matrix so we can further help you?" + "Could you please send a message in Discord or Matrix so we can further help you?" +
`${(warningReason.length > 0 ? ` Warning reason: '${warningReason}'` : "")}` }; `${(warningReason.length > 0 ? ` Warning reason: '${warningReason}'` : "")}` };
} }
// we can return out of the function early if the user is banned after warning checks
// returning before warning checks would make them not appear on vote if the user is also banned
if (isBanned) {
lock.unlock();
return { status: 200 };
}
// no type but has category, categoryVote // no type but has category, categoryVote
if (!type && category) { if (!type && category) {
const result = categoryVote(UUID, nonAnonUserID, isVIP, isTempVIP, isOwnSubmission, category, hashedIP, finalResponse); const result = categoryVote(UUID, nonAnonUserID, isVIP, isTempVIP, isOwnSubmission, category, hashedIP, finalResponse);
@@ -486,13 +495,13 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
} }
// Only change the database if they have made a submission before and haven't voted recently // Only change the database if they have made a submission before and haven't voted recently
// ban status check was handled earlier (w/ early return)
const ableToVote = isVIP || isTempVIP || ( const ableToVote = isVIP || isTempVIP || (
(!(isOwnSubmission && incrementAmount > 0 && oldIncrementAmount >= 0) (!(isOwnSubmission && incrementAmount > 0 && oldIncrementAmount >= 0)
&& !(originalType === VoteType.Malicious && segmentInfo.actionType !== ActionType.Chapter) && !(originalType === VoteType.Malicious && segmentInfo.actionType !== ActionType.Chapter)
&& !finalResponse.blockVote && !finalResponse.blockVote
&& finalResponse.finalStatus === 200 && finalResponse.finalStatus === 200
&& (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ? AND "category" = ? AND "votes" > -2 AND "hidden" = 0 AND "shadowHidden" = 0 LIMIT 1`, [nonAnonUserID, segmentInfo.category], { useReplica: true }) !== undefined) && (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ? AND "category" = ? AND "votes" > -2 AND "hidden" = 0 AND "shadowHidden" = 0 LIMIT 1`, [nonAnonUserID, segmentInfo.category], { useReplica: true }) !== undefined)
&& (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [nonAnonUserID], { useReplica: true })) === undefined
&& (await privateDB.prepare("get", `SELECT "UUID" FROM "votes" WHERE "UUID" = ? AND "hashedIP" = ? AND "userID" != ?`, [UUID, hashedIP, userID], { useReplica: true })) === undefined) && (await privateDB.prepare("get", `SELECT "UUID" FROM "votes" WHERE "UUID" = ? AND "hashedIP" = ? AND "userID" != ?`, [UUID, hashedIP, userID], { useReplica: true })) === undefined)
); );

27
src/utils/checkBan.ts Normal file
View File

@@ -0,0 +1,27 @@
import { HashedUserID } from "../types/user.model";
import { db } from "../databases/databases";
import { Category, HashedIP } from "../types/segments.model";
import { banUser } from "../routes/shadowBanUser";
import { config } from "../config";
import { Logger } from "./logger";
export async function isUserBanned(userID: HashedUserID): Promise<boolean> {
return (await db.prepare("get", `SELECT 1 FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID], { useReplica: true })) !== undefined;
}
export async function isIPBanned(ip: HashedIP): Promise<boolean> {
return (await db.prepare("get", `SELECT 1 FROM "shadowBannedIPs" WHERE "hashedIP" = ? LIMIT 1`, [ip], { useReplica: true })) !== undefined;
}
// NOTE: this function will propagate IP bans
export async function checkBanStatus(userID: HashedUserID, ip: HashedIP): Promise<boolean> {
const userBanStatus = await isUserBanned(userID);
const ipBanStatus = await isIPBanned(ip);
if (!userBanStatus && ipBanStatus) {
// Make sure the whole user is banned
banUser(userID, true, true, 1, config.categoryList as Category[], config.deArrowTypes)
.catch((e) => Logger.error(`Error banning user after submitting from a banned IP: ${e}`));
}
return userBanStatus || ipBanStatus;
}