Work on banned channels feature

This commit is contained in:
Calvin Montgomery 2022-08-31 20:02:41 -07:00
parent 7921f41174
commit 8338fe2f25
10 changed files with 130 additions and 11 deletions

View file

@ -94,7 +94,10 @@ function Channel(name) {
}, USERCOUNT_THROTTLE); }, USERCOUNT_THROTTLE);
const self = this; const self = this;
db.channels.load(this, function (err) { 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.emit("loadFail", "Failed to load channel data from the database. Please try again later.");
self.setFlag(Flags.C_ERROR); self.setFlag(Flags.C_ERROR);
} else { } else {

View file

@ -209,7 +209,9 @@ module.exports = {
return; 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) { function (err, res) {
if (err) { if (err) {
callback(err, []); callback(err, []);
@ -245,13 +247,28 @@ module.exports = {
return; 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) { if (err) {
callback(err, null); callback(err, null);
return; 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); callback("Channel is not registered", null);
return; return;
} }
@ -704,5 +721,30 @@ module.exports = {
LOGGER.error(`Failed to update owner_last_seen column for channel ID ${channelId}: ${error}`); 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
};
});
} }
}; };

View file

@ -156,4 +156,15 @@ export async function initTables() {
t.primary(['type', 'id']); t.primary(['type', 'id']);
t.index('updated_at'); 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);
});
} }

View file

@ -71,6 +71,7 @@ var Server = function () {
const globalMessageBus = this.initModule.getGlobalMessageBus(); const globalMessageBus = this.initModule.getGlobalMessageBus();
globalMessageBus.on('UserProfileChanged', this.handleUserProfileChange.bind(this)); globalMessageBus.on('UserProfileChanged', this.handleUserProfileChange.bind(this));
globalMessageBus.on('ChannelDeleted', this.handleChannelDelete.bind(this)); globalMessageBus.on('ChannelDeleted', this.handleChannelDelete.bind(this));
globalMessageBus.on('ChannelBanned', this.handleChannelBanned.bind(this));
globalMessageBus.on('ChannelRegistered', this.handleChannelRegister.bind(this)); globalMessageBus.on('ChannelRegistered', this.handleChannelRegister.bind(this));
// database init ------------------------------------------------------ // 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) { Server.prototype.handleChannelRegister = function (event) {
try { try {
const lname = event.channel.toLowerCase(); const lname = event.channel.toLowerCase();

View file

@ -102,10 +102,6 @@ User.prototype.handleJoinChannel = function handleJoinChannel(data) {
if (!chan.is(Flags.C_READY)) { if (!chan.is(Flags.C_READY)) {
chan.once("loadFail", reason => { chan.once("loadFail", reason => {
this.socket.emit("errorMsg", {
msg: reason,
alert: true
});
this.kick(`Channel could not be loaded: ${reason}`); this.kick(`Channel could not be loaded: ${reason}`);
}); });
} }

View file

@ -265,6 +265,8 @@ async function handleNewChannel(req, res) {
}); });
} }
let banInfo = await db.channels.getBannedChannel(name);
db.channels.listUserChannels(user.name, function (err, channels) { db.channels.listUserChannels(user.name, function (err, channels) {
if (err) { if (err) {
sendPug(res, "account-channels", { sendPug(res, "account-channels", {
@ -274,6 +276,14 @@ async function handleNewChannel(req, res) {
return; 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"))) { if (name.match(Config.get("reserved-names.channels"))) {
sendPug(res, "account-channels", { sendPug(res, "account-channels", {
channels: channels, channels: channels,

View file

@ -4,13 +4,22 @@ import { sendPug } from '../pug';
import * as HTTPStatus from '../httpstatus'; import * as HTTPStatus from '../httpstatus';
import { HTTPError } from '../../errors'; import { HTTPError } from '../../errors';
export default function initialize(app, ioConfig, chanPath) { export default function initialize(app, ioConfig, chanPath, getBannedChannel) {
app.get(`/${chanPath}/:channel`, (req, res) => { app.get(`/${chanPath}/:channel`, async (req, res) => {
if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) { if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) {
throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` + throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` +
'channel name.', { status: HTTPStatus.NOT_FOUND }); '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(); const endpoints = ioConfig.getSocketEndpoints();
if (endpoints.length === 0) { if (endpoints.length === 0) {
throw new HTTPError('No socket.io endpoints configured'); throw new HTTPError('No socket.io endpoints configured');

View file

@ -194,7 +194,13 @@ module.exports = {
LOGGER.info('Enabled express-minify for CSS and JS'); 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/index')(app, channelIndex, webConfig.getMaxIndexEntries());
require('./routes/socketconfig')(app, clusterClient); require('./routes/socketconfig')(app, clusterClient);
require('./routes/contact')(app, webConfig); require('./routes/contact')(app, webConfig);

View file

@ -24,6 +24,7 @@ block content
tbody tbody
for c in channels for c in channels
tr tr
// TODO: seems like it should be a <td>, lol
th 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');") 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) input(type="hidden", name="_csrf", value=csrfToken)
@ -32,6 +33,9 @@ block content
button.btn.btn-xs.btn-danger(type="submit") Delete button.btn.btn-xs.btn-danger(type="submit") Delete
span.glyphicon.glyphicon-trash span.glyphicon.glyphicon-trash
a(href=`/${channelPath}/${c.name}`, style="margin-left: 5px")= c.name a(href=`/${channelPath}/${c.name}`, style="margin-left: 5px")= c.name
if c.banReason != null
| &nbsp;
span.label.label-danger Banned
.col-lg-6.col-md-6 .col-lg-6.col-md-6
h3 Register a new channel h3 Register a new channel
if newChannelError if newChannelError

View file

@ -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