chore: comment config.example.toml and format code with prettier/cargo
This commit is contained in:
@@ -1,35 +1,35 @@
|
||||
{
|
||||
"name": "@cvmts/cvmts",
|
||||
"version": "1.0.0",
|
||||
"description": "replacement for collabvm 1.2.11 because the old one :boom:",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc -outDir dist -rootDir src/",
|
||||
"serve": "node dist/index.js"
|
||||
},
|
||||
"author": "Elijah R, modeco80",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@computernewb/nodejs-rfb": "^0.3.0",
|
||||
"@computernewb/superqemu": "^0.1.0",
|
||||
"@cvmts/cvm-rs": "*",
|
||||
"@maxmind/geoip2-node": "^5.0.0",
|
||||
"execa": "^8.0.1",
|
||||
"ip-address": "^9.0.5",
|
||||
"mariadb": "^3.3.1",
|
||||
"mnemonist": "^0.39.5",
|
||||
"msgpackr": "^1.10.2",
|
||||
"pino": "^9.3.1",
|
||||
"sharp": "^0.33.3",
|
||||
"toml": "^3.0.0",
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/ws": "^8.5.5",
|
||||
"pino-pretty": "^11.2.1",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.4.4"
|
||||
}
|
||||
"name": "@cvmts/cvmts",
|
||||
"version": "1.0.0",
|
||||
"description": "replacement for collabvm 1.2.11 because the old one :boom:",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc -outDir dist -rootDir src/",
|
||||
"serve": "node dist/index.js",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"author": "Elijah R, modeco80",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@computernewb/nodejs-rfb": "^0.3.0",
|
||||
"@computernewb/superqemu": "^0.1.0",
|
||||
"@cvmts/cvm-rs": "*",
|
||||
"@maxmind/geoip2-node": "^5.0.0",
|
||||
"execa": "^8.0.1",
|
||||
"ip-address": "^9.0.5",
|
||||
"mariadb": "^3.3.1",
|
||||
"mnemonist": "^0.39.5",
|
||||
"msgpackr": "^1.10.2",
|
||||
"pino": "^9.3.1",
|
||||
"sharp": "^0.33.3",
|
||||
"toml": "^3.0.0",
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/ws": "^8.5.5",
|
||||
"pino-pretty": "^11.2.1",
|
||||
"typescript": "^5.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,80 @@
|
||||
import { ExecaSyncError, execa, execaCommand } from "execa";
|
||||
import { BanConfig } from "./IConfig";
|
||||
import pino from "pino";
|
||||
import { Database } from "./Database";
|
||||
import { Address6 } from "ip-address";
|
||||
import { isIP } from "net";
|
||||
import { ExecaSyncError, execa, execaCommand } from 'execa';
|
||||
import { BanConfig } from './IConfig';
|
||||
import pino from 'pino';
|
||||
import { Database } from './Database';
|
||||
import { Address6 } from 'ip-address';
|
||||
import { isIP } from 'net';
|
||||
|
||||
export class BanManager {
|
||||
private cfg: BanConfig;
|
||||
private logger: pino.Logger;
|
||||
private db: Database | undefined;
|
||||
private cfg: BanConfig;
|
||||
private logger: pino.Logger;
|
||||
private db: Database | undefined;
|
||||
|
||||
constructor(config: BanConfig, db: Database | undefined) {
|
||||
this.cfg = config;
|
||||
this.logger = pino({
|
||||
name: "CVMTS.BanManager"
|
||||
});
|
||||
this.db = db;
|
||||
}
|
||||
constructor(config: BanConfig, db: Database | undefined) {
|
||||
this.cfg = config;
|
||||
this.logger = pino({
|
||||
name: 'CVMTS.BanManager'
|
||||
});
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
private formatIP(ip: string) {
|
||||
switch (isIP(ip)) {
|
||||
case 4:
|
||||
// If IPv4, just return as-is
|
||||
return ip;
|
||||
case 6: {
|
||||
// If IPv6, return the /64 equivalent
|
||||
let addr = new Address6(ip);
|
||||
addr.subnetMask = 64;
|
||||
return addr.startAddress().canonicalForm() + '/64';
|
||||
}
|
||||
case 0:
|
||||
default:
|
||||
// Invalid IP
|
||||
throw new Error("Invalid IP address (what the hell did you even do???)");
|
||||
}
|
||||
}
|
||||
private formatIP(ip: string) {
|
||||
switch (isIP(ip)) {
|
||||
case 4:
|
||||
// If IPv4, just return as-is
|
||||
return ip;
|
||||
case 6: {
|
||||
// If IPv6, return the /64 equivalent
|
||||
let addr = new Address6(ip);
|
||||
addr.subnetMask = 64;
|
||||
return addr.startAddress().canonicalForm() + '/64';
|
||||
}
|
||||
case 0:
|
||||
default:
|
||||
// Invalid IP
|
||||
throw new Error('Invalid IP address (what the hell did you even do???)');
|
||||
}
|
||||
}
|
||||
|
||||
async BanUser(ip: string, username: string) {
|
||||
ip = this.formatIP(ip);
|
||||
// If cvmban enabled, write to DB
|
||||
if (this.cfg.cvmban) {
|
||||
if (!this.db) throw new Error("CVMBAN enabled but Database is undefined");
|
||||
await this.db.banIP(ip, username);
|
||||
}
|
||||
// If ban command enabled, run it
|
||||
try {
|
||||
async BanUser(ip: string, username: string) {
|
||||
ip = this.formatIP(ip);
|
||||
// If cvmban enabled, write to DB
|
||||
if (this.cfg.cvmban) {
|
||||
if (!this.db) throw new Error('CVMBAN enabled but Database is undefined');
|
||||
await this.db.banIP(ip, username);
|
||||
}
|
||||
// If ban command enabled, run it
|
||||
try {
|
||||
if (Array.isArray(this.cfg.bancmd)) {
|
||||
let args: string[] = this.cfg.bancmd.map((a: string) => this.banCmdArgs(a, ip, username));
|
||||
if (args.length || args[0].length) {
|
||||
this.logger.info(`Running "${JSON.stringify(args)}"`);
|
||||
this.logger.info(`Running "${JSON.stringify(args)}"`);
|
||||
await execa(args.shift()!, args, { stdout: process.stdout, stderr: process.stderr });
|
||||
}
|
||||
} else if (typeof this.cfg.bancmd == 'string') {
|
||||
let cmd: string = this.banCmdArgs(this.cfg.bancmd, ip, username);
|
||||
if (cmd.length) {
|
||||
// Run through JSON.stringify for char escaping
|
||||
this.logger.info(`Running ${JSON.stringify(cmd)}`);
|
||||
// Run through JSON.stringify for char escaping
|
||||
this.logger.info(`Running ${JSON.stringify(cmd)}`);
|
||||
await execaCommand(cmd, { stdout: process.stdout, stderr: process.stderr });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`Failed to ban ${ip} (${username}): ${(e as ExecaSyncError).shortMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
async isIPBanned(ip: string) {
|
||||
ip = this.formatIP(ip);
|
||||
if (!this.db) return false;
|
||||
if (await this.db.isIPBanned(ip)) {
|
||||
this.logger.info(`Banned IP ${ip} tried connecting.`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private banCmdArgs(arg: string, ip: string, username: string): string {
|
||||
return arg.replace(/\$IP/g, ip).replace(/\$NAME/g, username);
|
||||
}
|
||||
|
||||
}
|
||||
async isIPBanned(ip: string) {
|
||||
ip = this.formatIP(ip);
|
||||
if (!this.db) return false;
|
||||
if (await this.db.isIPBanned(ip)) {
|
||||
this.logger.info(`Banned IP ${ip} tried connecting.`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private banCmdArgs(arg: string, ip: string, username: string): string {
|
||||
return arg.replace(/\$IP/g, ip).replace(/\$NAME/g, username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ type VoteTally = {
|
||||
no: number;
|
||||
};
|
||||
|
||||
|
||||
export default class CollabVMServer {
|
||||
private Config: IConfig;
|
||||
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
import pino, { Logger } from "pino";
|
||||
import { MySQLConfig } from "./IConfig";
|
||||
import pino, { Logger } from 'pino';
|
||||
import { MySQLConfig } from './IConfig';
|
||||
import mariadb from 'mariadb';
|
||||
|
||||
export class Database {
|
||||
cfg: MySQLConfig;
|
||||
logger: Logger;
|
||||
db: mariadb.Pool;
|
||||
constructor(config: MySQLConfig) {
|
||||
this.cfg = config;
|
||||
this.logger = pino({
|
||||
name: "CVMTS.Database"
|
||||
});
|
||||
this.db = mariadb.createPool({
|
||||
host: this.cfg.host,
|
||||
user: this.cfg.username,
|
||||
password: this.cfg.password,
|
||||
database: this.cfg.database,
|
||||
connectionLimit: 5,
|
||||
multipleStatements: false,
|
||||
});
|
||||
}
|
||||
cfg: MySQLConfig;
|
||||
logger: Logger;
|
||||
db: mariadb.Pool;
|
||||
constructor(config: MySQLConfig) {
|
||||
this.cfg = config;
|
||||
this.logger = pino({
|
||||
name: 'CVMTS.Database'
|
||||
});
|
||||
this.db = mariadb.createPool({
|
||||
host: this.cfg.host,
|
||||
user: this.cfg.username,
|
||||
password: this.cfg.password,
|
||||
database: this.cfg.database,
|
||||
connectionLimit: 5,
|
||||
multipleStatements: false
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
// Make sure tables exist
|
||||
let conn = await this.db.getConnection();
|
||||
await conn.execute("CREATE TABLE IF NOT EXISTS bans (ip VARCHAR(43) PRIMARY KEY NOT NULL, username VARCHAR(20) NOT NULL, reason TEXT DEFAULT NULL, timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
|
||||
conn.release();
|
||||
this.logger.info("MySQL successfully initialized");
|
||||
}
|
||||
async init() {
|
||||
// Make sure tables exist
|
||||
let conn = await this.db.getConnection();
|
||||
await conn.execute(
|
||||
'CREATE TABLE IF NOT EXISTS bans (ip VARCHAR(43) PRIMARY KEY NOT NULL, username VARCHAR(20) NOT NULL, reason TEXT DEFAULT NULL, timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);'
|
||||
);
|
||||
conn.release();
|
||||
this.logger.info('MySQL successfully initialized');
|
||||
}
|
||||
|
||||
async banIP(ip: string, username: string, reason: string | null = null) {
|
||||
let conn = await this.db.getConnection();
|
||||
await conn.execute("INSERT INTO bans (ip, username, reason) VALUES (?, ?, ?);", [ip, username, reason]);
|
||||
conn.release();
|
||||
}
|
||||
async banIP(ip: string, username: string, reason: string | null = null) {
|
||||
let conn = await this.db.getConnection();
|
||||
await conn.execute('INSERT INTO bans (ip, username, reason) VALUES (?, ?, ?);', [ip, username, reason]);
|
||||
conn.release();
|
||||
}
|
||||
|
||||
async isIPBanned(ip: string): Promise<boolean> {
|
||||
let conn = await this.db.getConnection();
|
||||
let res = (await conn.query('SELECT COUNT(ip) AS cnt FROM bans WHERE ip = ?', [ip]));
|
||||
conn.release();
|
||||
return res[0]['cnt'] !== 0n;
|
||||
}
|
||||
}
|
||||
async isIPBanned(ip: string): Promise<boolean> {
|
||||
let conn = await this.db.getConnection();
|
||||
let res = await conn.query('SELECT COUNT(ip) AS cnt FROM bans WHERE ip = ?', [ip]);
|
||||
conn.release();
|
||||
return res[0]['cnt'] !== 0n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default interface IConfig {
|
||||
directory: string;
|
||||
accountID: string;
|
||||
licenseKey: string;
|
||||
}
|
||||
};
|
||||
tcp: {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class TCPClient extends EventEmitter implements NetworkClient {
|
||||
|
||||
send(msg: string): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
let _msg = new Uint32Array([TextHeader, ...Buffer.from(msg, "utf-8")]);
|
||||
let _msg = new Uint32Array([TextHeader, ...Buffer.from(msg, 'utf-8')]);
|
||||
this.socket.write(Buffer.from(_msg), (err) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { BanManager } from '../BanManager.js';
|
||||
export default class TCPServer extends EventEmitter implements NetworkServer {
|
||||
listener: Server;
|
||||
Config: IConfig;
|
||||
logger = pino({name: 'CVMTS.TCPServer'});
|
||||
logger = pino({ name: 'CVMTS.TCPServer' });
|
||||
clients: TCPClient[];
|
||||
private banmgr: BanManager;
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class TCPServer extends EventEmitter implements NetworkServer {
|
||||
private async onConnection(socket: Socket) {
|
||||
this.logger.info(`New TCP connection from ${socket.remoteAddress}`);
|
||||
if (await this.banmgr.isIPBanned(socket.remoteAddress!)) {
|
||||
socket.write("6.banned;");
|
||||
socket.write('6.banned;');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export default class WSServer extends EventEmitter implements NetworkServer {
|
||||
}
|
||||
|
||||
if (await this.banmgr.isIPBanned(ip)) {
|
||||
socket.write("HTTP/1.1 403 Forbidden\n\nYou have been banned.");
|
||||
socket.write('HTTP/1.1 403 Forbidden\n\nYou have been banned.');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ async function start() {
|
||||
let auth = Config.auth.enabled ? new AuthManager(Config.auth.apiEndpoint, Config.auth.secretKey) : null;
|
||||
// Database and ban manager
|
||||
if (Config.bans.cvmban && !Config.mysql.enabled) {
|
||||
logger.error("MySQL must be configured to use cvmban.");
|
||||
logger.error('MySQL must be configured to use cvmban.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!Config.bans.cvmban && !Config.bans.bancmd) {
|
||||
logger.warn("Neither cvmban nor ban command are configured. Bans will not function.");
|
||||
logger.warn('Neither cvmban nor ban command are configured. Bans will not function.');
|
||||
}
|
||||
let db = undefined;
|
||||
if (Config.mysql.enabled) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": [ "src/**/*" ],
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
}
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user