Skip to content

Commit b5b01fa

Browse files
committed
Add back ip ban
1 parent 6d5b6dd commit b5b01fa

3 files changed

Lines changed: 108 additions & 3 deletions

File tree

databases/_private_indexes.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ CREATE INDEX IF NOT EXISTS "privateDB_sponsorTimes_v4"
44
ON public."sponsorTimes" USING btree
55
("videoID" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, "timeSubmitted" ASC NULLS LAST);
66

7+
CREATE INDEX IF NOT EXISTS "privateDB_time"
8+
ON public."sponsorTimes" USING btree
9+
("timeSubmitted" ASC NULLS LAST);
10+
711
-- votes
812

913
CREATE INDEX IF NOT EXISTS "votes_userID"

src/routes/shadowBanUser.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { db } from "../databases/databases";
1+
import { db, privateDB } from "../databases/databases";
22
import { getHashCache } from "../utils/getHashCache";
33
import { Request, Response } from "express";
44
import { config } from "../config";
5-
import { Category, DeArrowType, Service, VideoID, VideoIDHash } from "../types/segments.model";
5+
import { Category, DeArrowType, HashedIP, Service, VideoID, VideoIDHash } from "../types/segments.model";
66
import { UserID } from "../types/user.model";
77
import { QueryCacher } from "../utils/queryCacher";
88
import { isUserVIP } from "../utils/isUserVIP";
@@ -20,6 +20,7 @@ export async function shadowBanUser(req: Request, res: Response): Promise<Respon
2020
const enabled = req.query.enabled === undefined
2121
? true
2222
: req.query.enabled === "true";
23+
const lookForIPs = req.query.lookForIPs2 === "true";
2324

2425
//if enabled is false and the old submissions should be made visible again
2526
const unHideOldSubmissions = req.query.unHideOldSubmissions !== "false";
@@ -42,6 +43,19 @@ export async function shadowBanUser(req: Request, res: Response): Promise<Respon
4243
return res.sendStatus(403);
4344
}
4445
const result = await banUser(userID, enabled, unHideOldSubmissions, type, categories, deArrowTypes);
46+
47+
if (enabled && lookForIPs) {
48+
const ipLoggingFixedTime = 1675295716000;
49+
const timeSubmitted = (await db.prepare("all", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "timeSubmitted" > ? AND "userID" = ?`, [ipLoggingFixedTime, userID])) as { timeSubmitted: number }[];
50+
const ips = (await Promise.all(timeSubmitted.map((s) => {
51+
return privateDB.prepare("all", `SELECT "hashedIP" FROM "sponsorTimes" WHERE "timeSubmitted" = ?`, [s.timeSubmitted]) as Promise<{ hashedIP: HashedIP }[]>;
52+
}))).flat();
53+
54+
await Promise.all([...new Set(ips.map((ip) => ip.hashedIP))].map((ip) => {
55+
return banIP(ip, unHideOldSubmissions, type, categories, deArrowTypes);
56+
}));
57+
}
58+
4559
if (result) {
4660
res.sendStatus(result);
4761
return;
@@ -124,4 +138,49 @@ async function unHideSubmissionsByUser(categories: string[], deArrowTypes: DeArr
124138
.forEach((videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHash; service: Service; }) => {
125139
QueryCacher.clearBrandingCache(videoInfo);
126140
});
141+
}
142+
143+
export async function banIP(hashedIP: HashedIP, unHideOldSubmissions: boolean, type: number,
144+
categories: Category[], deArrowTypes: DeArrowType[]): Promise<number> {
145+
146+
//check to see if this user is already shadowbanned
147+
const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedIPs" WHERE "hashedIP" = ?`, [hashedIP]);
148+
149+
if (row.userCount == 0) {
150+
await db.prepare("run", `INSERT INTO "shadowBannedIPs" VALUES(?)`, [hashedIP]);
151+
}
152+
153+
//find all previous submissions and hide them
154+
if (unHideOldSubmissions) {
155+
const users = await unHideSubmissionsByIP(categories, hashedIP, type);
156+
157+
await Promise.all([...users].map((user) => {
158+
return banUser(user, true, unHideOldSubmissions, type, categories, deArrowTypes);
159+
}));
160+
} else if (row.userCount > 0) {
161+
// Nothing to do, and already added
162+
return 409;
163+
}
164+
165+
return 200;
166+
}
167+
168+
async function unHideSubmissionsByIP(categories: string[], hashedIP: HashedIP, type = 1): Promise<Set<UserID>> {
169+
const submissions = await privateDB.prepare("all", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "hashedIP" = ?`, [hashedIP]) as { timeSubmitted: number }[];
170+
171+
const users: Set<UserID> = new Set();
172+
await Promise.all(submissions.map(async (submission) => {
173+
(await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "timeSubmitted" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [submission.timeSubmitted]))
174+
.forEach((videoInfo: { category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID }) => {
175+
QueryCacher.clearSegmentCache(videoInfo);
176+
users.add(videoInfo.userID);
177+
}
178+
);
179+
180+
await db.prepare("run", `UPDATE "sponsorTimes" SET "shadowHidden" = ${type} WHERE "timeSubmitted" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")})
181+
AND NOT EXISTS ( SELECT "videoID", "category" FROM "lockCategories" WHERE
182+
"sponsorTimes"."videoID" = "lockCategories"."videoID" AND "sponsorTimes"."service" = "lockCategories"."service" AND "sponsorTimes"."category" = "lockCategories"."category")`, [submission.timeSubmitted]);
183+
}));
184+
185+
return users;
127186
}

test/cases/shadowBanUser.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { db } from "../../src/databases/databases";
1+
import { db, privateDB } from "../../src/databases/databases";
22
import { getHash } from "../../src/utils/getHash";
33
import assert from "assert";
44
import { Category, Service } from "../../src/types/segments.model";
@@ -10,6 +10,7 @@ describe("shadowBanUser", () => {
1010
const getShadowBanSegmentCategory = (userID: string, status: number): Promise<{shadowHidden: number, category: Category}[]> => db.prepare("all", `SELECT "shadowHidden", "category" FROM "sponsorTimes" WHERE "userID" = ? AND "shadowHidden" = ?`, [userID, status]);
1111
const getShadowBanTitles = (userID: string, status: number) => db.prepare("all", `SELECT tv."shadowHidden" FROM "titles" t JOIN "titleVotes" tv ON t."UUID" = tv."UUID" WHERE t."userID" = ? AND tv."shadowHidden" = ?`, [userID, status]);
1212
const getShadowBanThumbnails = (userID: string, status: number) => db.prepare("all", `SELECT tv."shadowHidden" FROM "thumbnails" t JOIN "thumbnailVotes" tv ON t."UUID" = tv."UUID" WHERE t."userID" = ? AND tv."shadowHidden" = ?`, [userID, status]);
13+
const getIPShadowBan = (hashedIP: string) => db.prepare("get", `SELECT * FROM "shadowBannedIPs" WHERE "hashedIP" = ?`, [hashedIP]);
1314

1415
const endpoint = "/api/shadowBanUser";
1516
const VIPuserID = "shadow-ban-vip";
@@ -56,6 +57,14 @@ describe("shadowBanUser", () => {
5657

5758
await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES(?)`, [getHash(VIPuserID)]);
5859

60+
const privateInsertQuery = `INSERT INTO "sponsorTimes" ("videoID", "hashedIP", "timeSubmitted", "service") VALUES(?, ?, ?, ?)`;
61+
await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916068933, "YouTube"]);
62+
await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916062936, "YouTube"]);
63+
await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916064324, "YouTube"]);
64+
await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916062443, "YouTube"]);
65+
await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916062342, "YouTube"]);
66+
await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916069491, "YouTube"]);
67+
5968
const titleQuery = `INSERT INTO "titles" ("videoID", "title", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
6069
const titleVotesQuery = `INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden", "verification") VALUES (?, ?, ?, ?, ?)`;
6170
const thumbnailQuery = `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)`;
@@ -392,6 +401,39 @@ describe("shadowBanUser", () => {
392401
.catch(err => done(err));
393402
});
394403

404+
it("Should be able to ban user by userID and other users who used that IP and hide specific category", (done) => {
405+
const hashedIP = "shadowBannedIP8";
406+
const userID = "shadowBanned8";
407+
const userID2 = "shadowBanned9";
408+
client({
409+
method: "POST",
410+
url: endpoint,
411+
params: {
412+
userID,
413+
enabled: true,
414+
categories: `["sponsor", "intro"]`,
415+
unHideOldSubmissions: true,
416+
adminUserID: VIPuserID,
417+
lookForIPs2: true
418+
}
419+
})
420+
.then(async res => {
421+
assert.strictEqual(res.status, 200);
422+
const videoRow = await getShadowBanSegments(userID, 1);
423+
const videoRow2 = await getShadowBanSegments(userID2, 1);
424+
const normalShadowRow = await getShadowBan(userID);
425+
const normalShadowRow2 = await getShadowBan(userID2);
426+
const ipShadowRow = await getIPShadowBan(hashedIP);
427+
assert.ok(ipShadowRow);
428+
assert.ok(normalShadowRow);
429+
assert.ok(normalShadowRow2);
430+
assert.strictEqual(videoRow.length, 2);
431+
assert.strictEqual(videoRow2.length, 2);
432+
done();
433+
})
434+
.catch(err => done(err));
435+
});
436+
395437
it("Should be able to ban user and hide dearrow submissions", (done) => {
396438
const userID = "userID1-ban";
397439
client({

0 commit comments

Comments
 (0)