diff --git a/src/routes/shadowBanUser.ts b/src/routes/shadowBanUser.ts index f7579a1..b3e192d 100644 --- a/src/routes/shadowBanUser.ts +++ b/src/routes/shadowBanUser.ts @@ -17,6 +17,10 @@ export async function shadowBanUser(req: Request, res: Response): Promise ? AND "userID" = ?`, [ipLoggingFixedTime, userID])) as { timeSubmitted: number }[]; + const ips = (await Promise.all(timeSubmitted.map((s) => { + return privateDB.prepare("all", `SELECT "hashedIP" FROM "sponsorTimes" WHERE "timeSubmitted" = ?`, [s.timeSubmitted]) as Promise<{ hashedIP: HashedIP }[]>; + }))).flat(); - //add it to the table - await db.prepare("run", `INSERT INTO "shadowBannedUsers" VALUES(?)`, [userID]); - - //find all previous submissions and hide them - if (unHideOldSubmissions) { - await unHideSubmissionsByUser(categories, userID, type); - } - } else if (!enabled && row.userCount > 0) { - //remove them from the shadow ban list - await db.prepare("run", `DELETE FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]); - - //find all previous submissions and unhide them - if (unHideOldSubmissions) { - const segmentsToIgnore = (await db.prepare("all", `SELECT "UUID" FROM "sponsorTimes" st - JOIN "lockCategories" ns on "st"."videoID" = "ns"."videoID" AND st.category = ns.category AND "st"."service" = "ns"."service" WHERE "st"."userID" = ?` - , [userID])).map((item: { UUID: string }) => item.UUID); - const allSegments = (await db.prepare("all", `SELECT "UUID" FROM "sponsorTimes" st WHERE "st"."userID" = ?`, [userID])) - .map((item: { UUID: string }) => item.UUID); - - await Promise.all(allSegments.filter((item: { uuid: string }) => { - return segmentsToIgnore.indexOf(item) === -1; - }).map(async (UUID: string) => { - // collect list for unshadowbanning - (await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "UUID" = ? AND "shadowHidden" >= 1 AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID])) - .forEach((videoInfo: { category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID }) => { - QueryCacher.clearSegmentCache(videoInfo); - } - ); - - return db.prepare("run", `UPDATE "sponsorTimes" SET "shadowHidden" = 0 WHERE "UUID" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID]); - })); - } - // already shadowbanned - } else if (enabled && row.userCount > 0) { - // apply unHideOldSubmissions if applicable - if (unHideOldSubmissions) { - await unHideSubmissionsByUser(categories, userID, type); - return res.sendStatus(200); - } - - // otherwise ban already exists, send 409 - return res.sendStatus(409); + await Promise.all(ips.map((ip) => { + return banIP(ip.hashedIP, enabled, unHideOldSubmissions, type, categories, true); + })); } } else if (hashedIP) { - //check to see if this user is already shadowbanned - const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedIPs" WHERE "hashedIP" = ?`, [hashedIP]); - - if (enabled && row.userCount == 0) { - //add them to the shadow ban list - - //add it to the table - await db.prepare("run", `INSERT INTO "shadowBannedIPs" VALUES(?)`, [hashedIP]); - - //find all previous submissions and hide them - if (unHideOldSubmissions) { - await unHideSubmissionsByIP(categories, hashedIP, type); - } - } else if (!enabled) { - if (row.userCount > 0) { - //remove them from the shadow ban list - await db.prepare("run", `DELETE FROM "shadowBannedIPs" WHERE "hashedIP" = ?`, [hashedIP]); - } - - //find all previous submissions and unhide them - if (unHideOldSubmissions) { - await unHideSubmissionsByIP(categories, hashedIP, "0"); - } - } else if (enabled && row.userCount > 0) { - // apply unHideOldSubmissions if applicable - if (unHideOldSubmissions) { - await unHideSubmissionsByIP(categories, hashedIP, type); - return res.sendStatus(200); - } - - // otherwise ban already exists, send 409 - return res.sendStatus(409); + const result = await banIP(hashedIP, enabled, unHideOldSubmissions, type, categories, banUsers); + if (result) { + res.sendStatus(result); } } return res.sendStatus(200); } +async function banUser(userID: UserID, enabled: boolean, unHideOldSubmissions: boolean, type: string, categories: Category[]): Promise { + //check to see if this user is already shadowbanned + const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]); + + if (enabled && row.userCount == 0) { + //add them to the shadow ban list + + //add it to the table + await db.prepare("run", `INSERT INTO "shadowBannedUsers" VALUES(?)`, [userID]); + + //find all previous submissions and hide them + if (unHideOldSubmissions) { + await unHideSubmissionsByUser(categories, userID, type); + } + } else if (enabled && row.userCount > 0) { + // apply unHideOldSubmissions if applicable + if (unHideOldSubmissions) { + await unHideSubmissionsByUser(categories, userID, type); + } else { + // otherwise ban already exists, send 409 + return 409; + } + } else if (!enabled && row.userCount > 0) { + //remove them from the shadow ban list + await db.prepare("run", `DELETE FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID]); + + //find all previous submissions and unhide them + if (unHideOldSubmissions) { + const segmentsToIgnore = (await db.prepare("all", `SELECT "UUID" FROM "sponsorTimes" st + JOIN "lockCategories" ns on "st"."videoID" = "ns"."videoID" AND st.category = ns.category AND "st"."service" = "ns"."service" WHERE "st"."userID" = ?` + , [userID])).map((item: { UUID: string }) => item.UUID); + const allSegments = (await db.prepare("all", `SELECT "UUID" FROM "sponsorTimes" st WHERE "st"."userID" = ?`, [userID])) + .map((item: { UUID: string }) => item.UUID); + + await Promise.all(allSegments.filter((item: { uuid: string }) => { + return segmentsToIgnore.indexOf(item) === -1; + }).map(async (UUID: string) => { + // collect list for unshadowbanning + (await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "UUID" = ? AND "shadowHidden" >= 1 AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID])) + .forEach((videoInfo: { category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID }) => { + QueryCacher.clearSegmentCache(videoInfo); + } + ); + + return db.prepare("run", `UPDATE "sponsorTimes" SET "shadowHidden" = 0 WHERE "UUID" = ? AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [UUID]); + })); + } + // already shadowbanned + } + + return 200; +} + +async function banIP(hashedIP: HashedIP, enabled: boolean, unHideOldSubmissions: boolean, type: string, categories: Category[], banUsers: boolean): Promise { + //check to see if this user is already shadowbanned + const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedIPs" WHERE "hashedIP" = ?`, [hashedIP]); + + if (enabled) { + if (row.userCount == 0) { + await db.prepare("run", `INSERT INTO "shadowBannedIPs" VALUES(?)`, [hashedIP]); + } + + //find all previous submissions and hide them + if (unHideOldSubmissions) { + const users = await unHideSubmissionsByIP(categories, hashedIP, type); + await Promise.all([...users].map((user) => { + return banUser(user, enabled, unHideOldSubmissions, type, categories); + })) + } else if (row.userCount > 0) { + // Nothing to do, and already added + return 409; + } + } else if (!enabled) { + if (row.userCount > 0) { + //remove them from the shadow ban list + await db.prepare("run", `DELETE FROM "shadowBannedIPs" WHERE "hashedIP" = ?`, [hashedIP]); + } + + //find all previous submissions and unhide them + if (unHideOldSubmissions) { + await unHideSubmissionsByIP(categories, hashedIP, "0"); + } + } + + return 200; +} + async function unHideSubmissionsByUser(categories: string[], userID: UserID, type = "1") { if (!["1", "2"].includes(type)) return; @@ -139,15 +168,17 @@ async function unHideSubmissionsByUser(categories: string[], userID: UserID, typ }); } -async function unHideSubmissionsByIP(categories: string[], hashedIP: HashedIP, type = "1") { +async function unHideSubmissionsByIP(categories: string[], hashedIP: HashedIP, type = "1"): Promise> { if (!["0", "1", "2"].includes(type)) return; const submissions = await privateDB.prepare("all", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "hashedIP" = ?`, [hashedIP]) as { timeSubmitted: number }[]; + const users: Set = new Set(); await Promise.all(submissions.map(async (submission) => { - (await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service", "votes", "views", "userID" FROM "sponsorTimes" WHERE "timeSubmitted" = ? AND "shadowHidden" >= 1 AND "category" in (${categories.map((c) => `'${c}'`).join(",")})`, [submission.timeSubmitted])) + (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])) .forEach((videoInfo: { category: Category, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID }) => { QueryCacher.clearSegmentCache(videoInfo); + users.add(videoInfo.userID); } ); @@ -155,4 +186,6 @@ async function unHideSubmissionsByIP(categories: string[], hashedIP: HashedIP, t AND NOT EXISTS ( SELECT "videoID", "category" FROM "lockCategories" WHERE "sponsorTimes"."videoID" = "lockCategories"."videoID" AND "sponsorTimes"."service" = "lockCategories"."service" AND "sponsorTimes"."category" = "lockCategories"."category")`, [submission.timeSubmitted]); })); -} + + return users; +} \ No newline at end of file diff --git a/test/cases/shadowBanUser.ts b/test/cases/shadowBanUser.ts index 9f7b024..8e52918 100644 --- a/test/cases/shadowBanUser.ts +++ b/test/cases/shadowBanUser.ts @@ -41,6 +41,13 @@ describe("shadowBanUser", () => { await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-71", "shadowBanned7", 2332, 50, "intro", "YouTube", 0, videohash]); await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-72", "shadowBanned7", 4923, 50, "interaction", "YouTube", 0, videohash]); + await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-80", "shadowBanned8", 1674590916068933, 50, "sponsor", "YouTube", 0, videohash]); + await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-81", "shadowBanned8", 1674590916062936, 50, "intro", "YouTube", 0, videohash]); + await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-82", "shadowBanned8", 1674590916064324, 50, "interaction", "YouTube", 0, videohash]); + await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-90", "shadowBanned9", 1674590916062443, 50, "sponsor", "YouTube", 0, videohash]); + await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-91", "shadowBanned9", 1674590916062342, 50, "intro", "YouTube", 0, videohash]); + await db.prepare("run", insertQuery, [video, 20, 10, 2, 0, "shadow-92", "shadowBanned9", 1674590916069491, 50, "interaction", "YouTube", 0, videohash]); + await db.prepare("run", `INSERT INTO "shadowBannedUsers" ("userID") VALUES(?)`, ["shadowBanned3"]); await db.prepare("run", `INSERT INTO "shadowBannedUsers" ("userID") VALUES(?)`, ["shadowBanned4"]); @@ -53,6 +60,13 @@ describe("shadowBanUser", () => { await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP7", 383848, "YouTube"]); await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP7", 2332, "YouTube"]); await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP7", 4923, "YouTube"]); + + await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916068933, "YouTube"]); + await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916062936, "YouTube"]); + await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916064324, "YouTube"]); + await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916062443, "YouTube"]); + await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916062342, "YouTube"]); + await privateDB.prepare("run", privateInsertQuery, [video, "shadowBannedIP8", 1674590916069491, "YouTube"]); }); it("Should be able to ban user and hide submissions", (done) => { @@ -336,7 +350,7 @@ describe("shadowBanUser", () => { const normalShadowRow = await getShadowBan(userID); const ipShadowRow = await getIPShadowBan(hashedIP); assert.ok(ipShadowRow); - assert.ok(!normalShadowRow); + assert.ok(normalShadowRow); assert.strictEqual(videoRow.length, 2); done(); }) @@ -362,7 +376,7 @@ describe("shadowBanUser", () => { const normalShadowRow = await getShadowBan(userID); const ipShadowRow = await getIPShadowBan(hashedIP); assert.ok(!ipShadowRow); - assert.ok(!normalShadowRow); + assert.ok(normalShadowRow); assert.strictEqual(videoRow.length, 2); done(); }) @@ -389,10 +403,43 @@ describe("shadowBanUser", () => { const normalShadowRow = await getShadowBan(userID); const ipShadowRow = await getIPShadowBan(hashedIP); assert.ok(!ipShadowRow); - assert.ok(!normalShadowRow); + assert.ok(normalShadowRow); assert.strictEqual(videoRow.length, 1); done(); }) .catch(err => done(err)); }); + + it("Should be able to ban user by userID and other users who used that IP and hide specific category", (done) => { + const hashedIP = "shadowBannedIP8"; + const userID = "shadowBanned8"; + const userID2 = "shadowBanned9"; + client({ + method: "POST", + url: endpoint, + params: { + userID, + enabled: true, + categories: `["sponsor", "intro"]`, + unHideOldSubmissions: true, + adminUserID: VIPuserID, + lookForIPs: true + } + }) + .then(async res => { + assert.strictEqual(res.status, 200); + const videoRow = await getShadowBanSegments(userID, 1); + const videoRow2 = await getShadowBanSegments(userID2, 1); + const normalShadowRow = await getShadowBan(userID); + const normalShadowRow2 = await getShadowBan(userID2); + const ipShadowRow = await getIPShadowBan(hashedIP); + assert.ok(ipShadowRow); + assert.ok(normalShadowRow); + assert.ok(normalShadowRow2); + assert.strictEqual(videoRow.length, 2); + assert.strictEqual(videoRow2.length, 2); + done(); + }) + .catch(err => done(err)); + }); });