Continue working on banned channels

This commit is contained in:
Calvin Montgomery 2022-09-03 15:58:42 -07:00
parent ae5dbf5f48
commit 913348d46e
8 changed files with 191 additions and 28 deletions

107
bin/admin.js Normal file → Executable file
View file

@ -49,24 +49,107 @@ let commands = [
let [name, externalReason, internalReason] = args; let [name, externalReason, internalReason] = args;
let answer = await rl.question(`Ban ${name} with external reason "${externalReason}" and internal reason "${internalReason}"? `); let answer = await rl.question(`Ban ${name} with external reason "${externalReason}" and internal reason "${internalReason}"? `);
if (!/^[yY]$/.test(answer)) {
console.log('Aborted.');
process.exit(1);
}
if (/^[yY]$/.test(answer)) { let res = await doCommand({
let res = await doCommand({ command: 'ban-channel',
command: 'ban-channel', name,
name, externalReason,
externalReason, internalReason
internalReason });
});
console.log(`Status: ${res.status}`); switch (res.status) {
if (res.status === 'error') { case 'error':
console.log('Error:', res.error); console.log('Error:', res.error);
process.exit(1); process.exit(1);
} else { break;
case 'success':
console.log('Ban succeeded.');
process.exit(0); process.exit(0);
} break;
} else { default:
console.log(`Unknown result: ${res.status}`);
process.exit(1);
break;
}
}
},
{
command: 'unban-channel',
handler: async args => {
if (args.length !== 1) {
console.log('Usage: unban-channel <name>');
process.exit(1);
}
let [name] = args;
let answer = await rl.question(`Unban ${name}? `);
if (!/^[yY]$/.test(answer)) {
console.log('Aborted.'); console.log('Aborted.');
process.exit(1);
}
let res = await doCommand({
command: 'unban-channel',
name
});
switch (res.status) {
case 'error':
console.log('Error:', res.error);
process.exit(1);
break;
case 'success':
console.log('Unban succeeded.');
process.exit(0);
break;
default:
console.log(`Unknown result: ${res.status}`);
process.exit(1);
break;
}
}
},
{
command: 'show-banned-channel',
handler: async args => {
if (args.length !== 1) {
console.log('Usage: show-banned-channel <name>');
process.exit(1);
}
let [name] = args;
let res = await doCommand({
command: 'show-banned-channel',
name
});
switch (res.status) {
case 'error':
console.log('Error:', res.error);
process.exit(1);
break;
case 'success':
if (res.ban != null) {
console.log(`Channel: ${name}`);
console.log(`Ban issued: ${res.ban.createdAt}`);
console.log(`Banned by: ${res.ban.bannedBy}`);
console.log(`External reason:\n${res.ban.externalReason}`);
console.log(`Internal reason:\n${res.ban.internalReason}`);
} else {
console.log(`Channel ${name} is not banned.`);
}
process.exit(0);
break;
default:
console.log(`Unknown result: ${res.status}`);
process.exit(1);
break;
} }
} }
} }

View file

@ -10,3 +10,15 @@ export async function handleBanChannel({ name, externalReason, internalReason })
return { status: 'success' }; return { status: 'success' };
} }
export async function handleUnbanChannel({ name }) {
await Server.getServer().bannedChannelsController.unbanChannel(name, '[console]');
return { status: 'success' };
}
export async function handleShowBannedChannel({ name }) {
let banInfo = await Server.getServer().bannedChannelsController.getBannedChannel(name);
return { status: 'success', ban: banInfo };
}

View file

@ -66,7 +66,6 @@ var defaults = {
} }
}, },
"youtube-v3-key": "", "youtube-v3-key": "",
"channel-blacklist": [],
"channel-path": "r", "channel-path": "r",
"channel-save-interval": 5, "channel-save-interval": 5,
"max-channels-per-user": 5, "max-channels-per-user": 5,
@ -372,13 +371,6 @@ function preprocessConfig(cfg) {
} }
} }
/* Convert channel blacklist to a hashtable */
var tbl = {};
cfg["channel-blacklist"].forEach(function (c) {
tbl[c.toLowerCase()] = true;
});
cfg["channel-blacklist"] = tbl;
/* Check channel path */ /* Check channel path */
if(!/^[-\w]+$/.test(cfg["channel-path"])){ if(!/^[-\w]+$/.test(cfg["channel-path"])){
LOGGER.error("Channel paths may only use the same characters as usernames and channel names."); LOGGER.error("Channel paths may only use the same characters as usernames and channel names.");

View file

@ -1,3 +1,4 @@
import { eventlog } from '../logger';
const LOGGER = require('@calzoneman/jsli')('BannedChannelsController'); const LOGGER = require('@calzoneman/jsli')('BannedChannelsController');
export class BannedChannelsController { export class BannedChannelsController {
@ -6,8 +7,13 @@ export class BannedChannelsController {
this.globalMessageBus = globalMessageBus; this.globalMessageBus = globalMessageBus;
} }
/*
* TODO: add an audit log to the database
*/
async banChannel({ name, externalReason, internalReason, bannedBy }) { async banChannel({ name, externalReason, internalReason, bannedBy }) {
LOGGER.info(`Banning channel ${name} (banned by ${bannedBy})`); LOGGER.info(`Banning channel ${name} (banned by ${bannedBy})`);
eventlog.log(`[acp] ${bannedBy} banned channel ${name}`);
let banInfo = await this.dbChannels.getBannedChannel(name); let banInfo = await this.dbChannels.getBannedChannel(name);
if (banInfo !== null) { if (banInfo !== null) {
@ -26,4 +32,61 @@ export class BannedChannelsController {
{ channel: name, externalReason } { channel: name, externalReason }
); );
} }
async unbanChannel(name, unbannedBy) {
LOGGER.info(`Unbanning channel ${name}`);
eventlog.log(`[acp] ${unbannedBy} unbanned channel ${name}`);
this.globalMessageBus.emit(
'ChannelUnbanned',
{ channel: name }
);
await this.dbChannels.removeBannedChannel(name);
}
async getBannedChannel(name) {
// TODO: cache
return this.dbChannels.getBannedChannel(name);
}
}
class Cache {
constructor({ maxElem, maxAge }) {
this.maxElem = maxElem;
this.maxAge = maxAge;
this.cache = new Map();
}
put(key, value) {
this.cache.set(key, { value: value, at: Date.now() });
if (this.cache.size > this.maxElem) {
this.cache.delete(this.cache.keys().next());
}
}
get(key) {
let val = this.cache.get(key);
if (val != null) {
return val.value;
} else {
return null;
}
}
delete(key) {
this.cache.delete(key);
}
cleanup() {
let now = Date.now();
for (let [key, value] of this.cache) {
if (value.at < now - this.maxAge) {
this.cache.delete(key);
}
}
}
} }

View file

@ -763,10 +763,22 @@ module.exports = {
banned_by: bannedBy banned_by: bannedBy
}); });
let update = tx.raw(createMySQLDuplicateKeyUpdate( let update = tx.raw(createMySQLDuplicateKeyUpdate(
['external_reason', 'internal_reason'] ['external_reason', 'internal_reason', 'banned_by']
)); ));
return tx.raw(insert.toString() + update.toString()); return tx.raw(insert.toString() + update.toString());
}); });
},
removeBannedChannel: async function removeBannedChannel(name) {
if (!valid(name)) {
throw new Error("Invalid channel name");
}
return await db.getDB().runTransaction(async tx => {
await tx.table('banned_channels')
.where({ channel_name: name })
.delete();
});
} }
}; };

View file

@ -33,7 +33,11 @@ async function handleCliCmd(cmd) {
try { try {
switch (cmd.command) { switch (cmd.command) {
case 'ban-channel': case 'ban-channel':
return await bannedChannels.handleBanChannel(cmd); return bannedChannels.handleBanChannel(cmd);
case 'unban-channel':
return bannedChannels.handleUnbanChannel(cmd);
case 'show-banned-channel':
return bannedChannels.handleShowBannedChannel(cmd);
default: default:
throw new Error(`Unrecognized command "${cmd.command}"`); throw new Error(`Unrecognized command "${cmd.command}"`);
} }
@ -52,6 +56,7 @@ function handleLine(line, client) {
client.write(JSON.stringify(res) + '\n'); client.write(JSON.stringify(res) + '\n');
}).catch(error => { }).catch(error => {
LOGGER.error(`Unexpected error in handleCliCmd: ${error.stack}`); LOGGER.error(`Unexpected error in handleCliCmd: ${error.stack}`);
client.write('{"status":"error","error":"internal error"}\n');
}); });
} catch (_error) { } catch (_error) {
} }

View file

@ -76,10 +76,6 @@ User.prototype.handleJoinChannel = function handleJoinChannel(data) {
} }
data.name = data.name.toLowerCase(); data.name = data.name.toLowerCase();
if (data.name in Config.get("channel-blacklist")) {
this.kick("This channel is blacklisted.");
return;
}
this.waitFlag(Flags.U_READY, () => { this.waitFlag(Flags.U_READY, () => {
var chan; var chan;

View file

@ -198,8 +198,8 @@ module.exports = {
app, app,
ioConfig, ioConfig,
chanPath, chanPath,
async name => null // TODO: banController
/*require('../database/channels').getBannedChannel*/ 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);