From 8338fe2f25e8aa652e1b6066b38376eb39a589c1 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Wed, 31 Aug 2022 20:02:41 -0700 Subject: [PATCH] Work on banned channels feature --- src/channel/channel.js | 5 +++- src/database/channels.js | 48 +++++++++++++++++++++++++++++++--- src/database/tables.js | 11 ++++++++ src/server.js | 29 ++++++++++++++++++++ src/user.js | 4 --- src/web/account.js | 10 +++++++ src/web/routes/channel.js | 13 +++++++-- src/web/webserver.js | 8 +++++- templates/account-channels.pug | 4 +++ templates/banned_channel.pug | 9 +++++++ 10 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 templates/banned_channel.pug diff --git a/src/channel/channel.js b/src/channel/channel.js index 744884fe..cb101acc 100644 --- a/src/channel/channel.js +++ b/src/channel/channel.js @@ -94,7 +94,10 @@ function Channel(name) { }, USERCOUNT_THROTTLE); const self = this; db.channels.load(this, function (err) { - if (err && err !== "Channel is not registered") { + if (err && err.code === 'EBANNED') { + self.emit("loadFail", err.message); + self.setFlag(Flags.C_ERROR); + } else if (err && err !== "Channel is not registered") { self.emit("loadFail", "Failed to load channel data from the database. Please try again later."); self.setFlag(Flags.C_ERROR); } else { diff --git a/src/database/channels.js b/src/database/channels.js index 90180356..57d5c391 100644 --- a/src/database/channels.js +++ b/src/database/channels.js @@ -209,7 +209,9 @@ module.exports = { return; } - db.query("SELECT * FROM `channels` WHERE owner=?", [owner], + db.query("SELECT c.*, bc.external_reason as banReason " + + "FROM channels c LEFT OUTER JOIN banned_channels bc " + + "ON bc.channel_name = c.name WHERE c.owner=?", [owner], function (err, res) { if (err) { callback(err, []); @@ -245,13 +247,28 @@ module.exports = { return; } - db.query("SELECT * FROM `channels` WHERE name=?", chan.name, function (err, res) { + db.query("SELECT c.*, bc.external_reason as banReason " + + "FROM channels c LEFT OUTER JOIN banned_channels bc " + + "ON bc.channel_name = c.name WHERE c.name=? " + + "UNION " + + "SELECT c.*, bc.external_reason as banReason " + + "FROM channels c RIGHT OUTER JOIN banned_channels bc " + + "ON bc.channel_name = c.name WHERE bc.channel_name=? ", + [chan.name, chan.name], + function (err, res) { if (err) { callback(err, null); return; } - if (res.length === 0) { + if (res.length > 0 && res[0].banReason !== null) { + let banError = new Error(`Channel is banned: ${res[0].banReason}`); + banError.code = 'EBANNED'; + callback(banError, null); + return; + } + + if (res.length === 0 || res[0].id === null) { callback("Channel is not registered", null); return; } @@ -704,5 +721,30 @@ module.exports = { LOGGER.error(`Failed to update owner_last_seen column for channel ID ${channelId}: ${error}`); } }); + }, + + getBannedChannel: async function getBannedChannel(name) { + if (!valid(name)) { + throw new Error("Invalid channel name"); + } + + return await db.getDB().runTransaction(async tx => { + let rows = await tx.table('banned_channels') + .where({ channel_name: name }) + .select(); + + if (rows.length === 0) { + return null; + } + + return { + channelName: rows[0].channel_name, + externalReason: rows[0].external_reason, + internalReason: rows[0].internal_reason, + bannedBy: rows[0].banned_by, + createdAt: rows[0].created_at, + updatedAt: rows[0].updated_at + }; + }); } }; diff --git a/src/database/tables.js b/src/database/tables.js index dee036bf..2debf71d 100644 --- a/src/database/tables.js +++ b/src/database/tables.js @@ -156,4 +156,15 @@ export async function initTables() { t.primary(['type', 'id']); t.index('updated_at'); }); + + await ensureTable('banned_channels', t => { + t.charset('utf8mb4'); + t.string('channel_name', 30) + .notNullable() + .unique(); + t.text('external_reason').notNullable(); + t.text('internal_reason').notNullable(); + t.string('banned_by', 20).notNullable(); + t.timestamps(/* useTimestamps */ true, /* defaultToNow */ true); + }); } diff --git a/src/server.js b/src/server.js index 9974953e..7b7eafdc 100644 --- a/src/server.js +++ b/src/server.js @@ -71,6 +71,7 @@ var Server = function () { const globalMessageBus = this.initModule.getGlobalMessageBus(); globalMessageBus.on('UserProfileChanged', this.handleUserProfileChange.bind(this)); globalMessageBus.on('ChannelDeleted', this.handleChannelDelete.bind(this)); + globalMessageBus.on('ChannelBanned', this.handleChannelBanned.bind(this)); globalMessageBus.on('ChannelRegistered', this.handleChannelRegister.bind(this)); // database init ------------------------------------------------------ @@ -549,6 +550,34 @@ Server.prototype.handleChannelDelete = function (event) { } }; +Server.prototype.handleChannelBanned = function (event) { + try { + const lname = event.channel.toLowerCase(); + const reason = event.externalReason; + + this.channels.forEach(channel => { + if (channel.dead) return; + + if (channel.uniqueName === lname) { + channel.clearFlag(Flags.C_REGISTERED); + + const users = Array.prototype.slice.call(channel.users); + users.forEach(u => { + u.kick(`Channel was banned: ${reason}`); + }); + + if (!channel.dead && !channel.dying) { + channel.emit('empty'); + } + + LOGGER.info('Processed banned channel %s', lname); + } + }); + } catch (error) { + LOGGER.error('handleChannelBanned failed: %s', error); + } +}; + Server.prototype.handleChannelRegister = function (event) { try { const lname = event.channel.toLowerCase(); diff --git a/src/user.js b/src/user.js index c3699d83..88ddab6e 100644 --- a/src/user.js +++ b/src/user.js @@ -102,10 +102,6 @@ User.prototype.handleJoinChannel = function handleJoinChannel(data) { if (!chan.is(Flags.C_READY)) { chan.once("loadFail", reason => { - this.socket.emit("errorMsg", { - msg: reason, - alert: true - }); this.kick(`Channel could not be loaded: ${reason}`); }); } diff --git a/src/web/account.js b/src/web/account.js index 248a75d4..a020f936 100644 --- a/src/web/account.js +++ b/src/web/account.js @@ -265,6 +265,8 @@ async function handleNewChannel(req, res) { }); } + let banInfo = await db.channels.getBannedChannel(name); + db.channels.listUserChannels(user.name, function (err, channels) { if (err) { sendPug(res, "account-channels", { @@ -274,6 +276,14 @@ async function handleNewChannel(req, res) { return; } + if (banInfo !== null) { + sendPug(res, "account-channels", { + channels: channels, + newChannelError: `Cannot register "${name}": this channel is banned.` + }); + return; + } + if (name.match(Config.get("reserved-names.channels"))) { sendPug(res, "account-channels", { channels: channels, diff --git a/src/web/routes/channel.js b/src/web/routes/channel.js index e7113d84..8afdc7d1 100644 --- a/src/web/routes/channel.js +++ b/src/web/routes/channel.js @@ -4,13 +4,22 @@ import { sendPug } from '../pug'; import * as HTTPStatus from '../httpstatus'; import { HTTPError } from '../../errors'; -export default function initialize(app, ioConfig, chanPath) { - app.get(`/${chanPath}/:channel`, (req, res) => { +export default function initialize(app, ioConfig, chanPath, getBannedChannel) { + app.get(`/${chanPath}/:channel`, async (req, res) => { if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) { throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` + 'channel name.', { status: HTTPStatus.NOT_FOUND }); } + // TODO: add a cache + let banInfo = await getBannedChannel(req.params.channel); + if (banInfo !== null) { + sendPug(res, 'banned_channel', { + externalReason: banInfo.externalReason + }); + return; + } + const endpoints = ioConfig.getSocketEndpoints(); if (endpoints.length === 0) { throw new HTTPError('No socket.io endpoints configured'); diff --git a/src/web/webserver.js b/src/web/webserver.js index e2f51bcd..d3ae2ec1 100644 --- a/src/web/webserver.js +++ b/src/web/webserver.js @@ -194,7 +194,13 @@ module.exports = { LOGGER.info('Enabled express-minify for CSS and JS'); } - require('./routes/channel')(app, ioConfig, chanPath); + require('./routes/channel')( + app, + ioConfig, + chanPath, + async name => null + /*require('../database/channels').getBannedChannel*/ + ); require('./routes/index')(app, channelIndex, webConfig.getMaxIndexEntries()); require('./routes/socketconfig')(app, clusterClient); require('./routes/contact')(app, webConfig); diff --git a/templates/account-channels.pug b/templates/account-channels.pug index 7b2a1dc8..a4175f57 100644 --- a/templates/account-channels.pug +++ b/templates/account-channels.pug @@ -24,6 +24,7 @@ block content tbody for c in channels tr + // TODO: seems like it should be a , lol th form.form-inline.pull-right(action="/account/channels", method="post", onsubmit="return confirm('Are you sure you want to delete " +c.name+ "? This cannot be undone');") input(type="hidden", name="_csrf", value=csrfToken) @@ -32,6 +33,9 @@ block content button.btn.btn-xs.btn-danger(type="submit") Delete span.glyphicon.glyphicon-trash a(href=`/${channelPath}/${c.name}`, style="margin-left: 5px")= c.name + if c.banReason != null + |   + span.label.label-danger Banned .col-lg-6.col-md-6 h3 Register a new channel if newChannelError diff --git a/templates/banned_channel.pug b/templates/banned_channel.pug new file mode 100644 index 00000000..69c1d5cd --- /dev/null +++ b/templates/banned_channel.pug @@ -0,0 +1,9 @@ +extends layout.pug + +block content + .col-md-12 + .alert.alert-danger + h1 Banned Channel + strong This channel is banned: + p + = externalReason