Continue working on banned channels

This commit is contained in:
Calvin Montgomery 2022-09-01 20:17:21 -07:00
parent 8338fe2f25
commit ae5dbf5f48
7 changed files with 190 additions and 4 deletions

93
bin/admin.js Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env node
const Config = require('../lib/config');
Config.load('config.yaml');
if (!Config.get('service-socket.enabled')){
console.error('The Service Socket is not enabled.');
process.exit(1);
}
const net = require('net');
const path = require('path');
const readline = require('node:readline/promises');
const socketPath = path.resolve(__dirname, '..', Config.get('service-socket.socket'));
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
async function doCommand(params) {
return new Promise((resolve, reject) => {
const client = net.createConnection(socketPath);
client.on('connect', () => {
client.write(JSON.stringify(params) + '\n');
});
client.on('data', data => {
client.end();
resolve(JSON.parse(data));
});
client.on('error', error => {
reject(error);
});
});
}
let commands = [
{
command: 'ban-channel',
handler: async args => {
if (args.length !== 3) {
console.log('Usage: ban-channel <name> <externalReason> <internalReason>');
process.exit(1);
}
let [name, externalReason, internalReason] = args;
let answer = await rl.question(`Ban ${name} with external reason "${externalReason}" and internal reason "${internalReason}"? `);
if (/^[yY]$/.test(answer)) {
let res = await doCommand({
command: 'ban-channel',
name,
externalReason,
internalReason
});
console.log(`Status: ${res.status}`);
if (res.status === 'error') {
console.log('Error:', res.error);
process.exit(1);
} else {
process.exit(0);
}
} else {
console.log('Aborted.');
}
}
}
];
let found = false;
commands.forEach(cmd => {
if (cmd.command === process.argv[2]) {
found = true;
cmd.handler(process.argv.slice(3)).then(() => {
process.exit(0);
}).catch(error => {
console.log('Error in command:', error.stack);
});
}
});
if (!found) {
console.log('Available commands:');
commands.forEach(cmd => {
console.log(` * ${cmd.command}`);
});
process.exit(1);
}

View File

@ -0,0 +1,12 @@
import Server from '../server';
export async function handleBanChannel({ name, externalReason, internalReason }) {
await Server.getServer().bannedChannelsController.banChannel({
name,
externalReason,
internalReason,
bannedBy: '[console]'
});
return { status: 'success' };
}

View File

@ -0,0 +1,29 @@
const LOGGER = require('@calzoneman/jsli')('BannedChannelsController');
export class BannedChannelsController {
constructor(dbChannels, globalMessageBus) {
this.dbChannels = dbChannels;
this.globalMessageBus = globalMessageBus;
}
async banChannel({ name, externalReason, internalReason, bannedBy }) {
LOGGER.info(`Banning channel ${name} (banned by ${bannedBy})`);
let banInfo = await this.dbChannels.getBannedChannel(name);
if (banInfo !== null) {
LOGGER.warn(`Channel ${name} is already banned, updating ban reason`);
}
await this.dbChannels.putBannedChannel({
name,
externalReason,
internalReason,
bannedBy
});
this.globalMessageBus.emit(
'ChannelBanned',
{ channel: name, externalReason }
);
}
}

View File

@ -2,6 +2,7 @@ var db = require("../database");
var valid = require("../utilities").isValidChannelName; var valid = require("../utilities").isValidChannelName;
var Flags = require("../flags"); var Flags = require("../flags");
var util = require("../utilities"); var util = require("../utilities");
// TODO: I think newer knex has native support for this
import { createMySQLDuplicateKeyUpdate } from '../util/on-duplicate-key-update'; import { createMySQLDuplicateKeyUpdate } from '../util/on-duplicate-key-update';
import Config from '../config'; import Config from '../config';
@ -746,5 +747,26 @@ module.exports = {
updatedAt: rows[0].updated_at updatedAt: rows[0].updated_at
}; };
}); });
},
putBannedChannel: async function putBannedChannel({ name, externalReason, internalReason, bannedBy }) {
if (!valid(name)) {
throw new Error("Invalid channel name");
}
return await db.getDB().runTransaction(async tx => {
let insert = tx.table('banned_channels')
.insert({
channel_name: name,
external_reason: externalReason,
internal_reason: internalReason,
banned_by: bannedBy
});
let update = tx.raw(createMySQLDuplicateKeyUpdate(
['external_reason', 'internal_reason']
));
return tx.raw(insert.toString() + update.toString());
});
} }
}; };

View File

@ -2,6 +2,7 @@ import Config from './config';
import * as Switches from './switches'; import * as Switches from './switches';
import { eventlog } from './logger'; import { eventlog } from './logger';
require('source-map-support').install(); require('source-map-support').install();
import * as bannedChannels from './cli/banned-channels';
const LOGGER = require('@calzoneman/jsli')('main'); const LOGGER = require('@calzoneman/jsli')('main');
@ -28,10 +29,33 @@ if (!Config.get('debug')) {
}); });
} }
async function handleCliCmd(cmd) {
try {
switch (cmd.command) {
case 'ban-channel':
return await bannedChannels.handleBanChannel(cmd);
default:
throw new Error(`Unrecognized command "${cmd.command}"`);
}
} catch (error) {
return { status: 'error', error: String(error) };
}
}
// TODO: this can probably just be part of servsock.js // TODO: this can probably just be part of servsock.js
// servsock should also be refactored to send replies instead of // servsock should also be refactored to send replies instead of
// relying solely on tailing logs // relying solely on tailing logs
function handleLine(line) { function handleLine(line, client) {
try {
let cmd = JSON.parse(line);
handleCliCmd(cmd).then(res => {
client.write(JSON.stringify(res) + '\n');
}).catch(error => {
LOGGER.error(`Unexpected error in handleCliCmd: ${error.stack}`);
});
} catch (_error) {
}
if (line === '/reload') { if (line === '/reload') {
LOGGER.info('Reloading config'); LOGGER.info('Reloading config');
try { try {
@ -81,9 +105,9 @@ if (Config.get('service-socket.enabled')) {
const ServiceSocket = require('./servsock'); const ServiceSocket = require('./servsock');
const sock = new ServiceSocket(); const sock = new ServiceSocket();
sock.init( sock.init(
line => { (line, client) => {
try { try {
handleLine(line); handleLine(line, client);
} catch (error) { } catch (error) {
LOGGER.error( LOGGER.error(
'Error in UNIX socket command handler: %s', 'Error in UNIX socket command handler: %s',

View File

@ -48,6 +48,7 @@ import { PartitionModule } from './partition/partitionmodule';
import { Gauge } from 'prom-client'; import { Gauge } from 'prom-client';
import { EmailController } from './controller/email'; import { EmailController } from './controller/email';
import { CaptchaController } from './controller/captcha'; import { CaptchaController } from './controller/captcha';
import { BannedChannelsController } from './controller/banned-channels';
var Server = function () { var Server = function () {
var self = this; var self = this;
@ -109,6 +110,11 @@ var Server = function () {
Config.getCaptchaConfig() Config.getCaptchaConfig()
); );
self.bannedChannelsController = new BannedChannelsController(
self.db.channels,
globalMessageBus
);
// webserver init ----------------------------------------------------- // webserver init -----------------------------------------------------
const ioConfig = IOConfiguration.fromOldConfig(Config); const ioConfig = IOConfiguration.fromOldConfig(Config);
const webConfig = WebConfiguration.fromOldConfig(Config); const webConfig = WebConfiguration.fromOldConfig(Config);

View File

@ -34,7 +34,7 @@ export default class ServiceSocket {
delete this.connections[id]; delete this.connections[id];
}); });
stream.on('data', (msg) => { stream.on('data', (msg) => {
this.handler(msg.toString()); this.handler(msg.toString(), stream);
}); });
}).listen(this.socket); }).listen(this.socket);
process.on('exit', this.closeServiceSocket.bind(this)); process.on('exit', this.closeServiceSocket.bind(this));