From 64d4774d001673917b0948606e754dbb724e9f07 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Sun, 4 Aug 2024 15:50:00 -0400 Subject: [PATCH] chore: comment config.example.toml and format code with prettier/cargo --- .prettierrc.json | 3 +- config.example.toml | 27 +++- cvm-rs/index.d.ts | 11 +- cvm-rs/package.json | 3 +- cvm-rs/src/guac.rs | 243 ++++++++++++++++---------------- cvm-rs/src/guac_js.rs | 2 +- cvm-rs/src/jpeg_compressor.rs | 6 +- cvm-rs/src/jpeg_js.rs | 8 +- cvm-rs/src/lib.rs | 4 +- cvmts/package.json | 66 ++++----- cvmts/src/BanManager.ts | 119 ++++++++-------- cvmts/src/CollabVMServer.ts | 1 - cvmts/src/Database.ts | 78 +++++----- cvmts/src/IConfig.ts | 2 +- cvmts/src/TCP/TCPClient.ts | 2 +- cvmts/src/TCP/TCPServer.ts | 4 +- cvmts/src/WebSocket/WSServer.ts | 2 +- cvmts/src/index.ts | 4 +- cvmts/tsconfig.json | 10 +- package.json | 7 +- yarn.lock | 33 ++++- 21 files changed, 340 insertions(+), 295 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index d41a549..7b2c8c5 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -16,5 +16,6 @@ "tabWidth": 4, "trailingComma": "none", "useTabs": true, - "vueIndentScriptAndStyle": false + "vueIndentScriptAndStyle": false, + "plugins": ["prettier-plugin-toml"] } diff --git a/config.example.toml b/config.example.toml index a93eebf..3a95758 100644 --- a/config.example.toml +++ b/config.example.toml @@ -21,15 +21,21 @@ accountID = "" licenseKey = "" [tcp] +# Enabled the raw TCP socket server +# You usually want to leave this disabled enabled = false host = "0.0.0.0" port = 6014 [auth] +# Enables the CollabVM account authentication system +# Requires an authentication server (https://git.computernewb.com/collabvm/CollabVMAuthServer) enabled = false apiEndpoint = "" secretKey = "hunter2" +# When account authentication is enabled, this section defines what guests can and can't do +# Has no effect if auth is disabled [auth.guestPermissions] chat = true turn = false @@ -37,13 +43,21 @@ callForReset = false vote = true [vm] +# Configures the type of VM this server will use +# Supported values: +# "qemu" - Runs a QEMU VM +# "vncvm" - Connects to an existing VNC server type = "qemu" +# QEMU options +# Only used if vm.type is set to "qemu" [qemu] qemuArgs = "qemu-system-x86_64" vncPort = 5900 snapshots = true +# VNC options +# Only used if vm.type is set to "vncvm" [vncvm] vncHost = "127.0.0.1" vncPort = 5900 @@ -69,12 +83,16 @@ database = "cvmts" cvmban = false [collabvm] +# Node ID for this server +# Make sure this is unique among all the other nodes in a webapp node = "acoolvm" +# HTML display name shown on the VM list displayname = "A Really Cool CollabVM Instance" +# HTML message shown in the chat when a user joins motd = "welcome!" # Maximum amount of active connections allowed from the same IP. maxConnections = 3 -# Moderator rank enabled +# Moderator rank enabled (permissions are defined below) moderatorEnabled = true # List of disallowed usernames usernameblacklist = [] @@ -83,9 +101,9 @@ maxChatLength = 100 # Maximum messages in the chat history buffer before old messages are overwritten maxChatHistoryLength = 10 # Limit the amount of users allowed in the turn queue at the same time from the same IP -turnlimit = {enabled = true, maximum = 1} +turnlimit = { enabled = true, maximum = 1 } # Temporarily mute a user if they send more than x messages in n seconds -automute = {enabled = true, seconds = 5, messages = 5} +automute = { enabled = true, seconds = 5, messages = 5 } # How long a temporary mute lasts, in seconds tempMuteTime = 30 # How long a turn lasts, in seconds @@ -103,8 +121,9 @@ modpass = "fb8c2e2b85ca81eb4350199faddd983cb26af3064614e737ea9f479621cfa57a" turnwhitelist = false # SHA256 sum for the password to take a turn. Only takes effect if turnwhitelist == true. If set to an empty string or not provided, only admins and mods can take turns turnpass = "" -[collabvm.moderatorPermissions] + # What a moderator can and can't do +[collabvm.moderatorPermissions] restore = true reboot = true ban = true diff --git a/cvm-rs/index.d.ts b/cvm-rs/index.d.ts index 9e664f2..af6e202 100644 --- a/cvm-rs/index.d.ts +++ b/cvm-rs/index.d.ts @@ -5,21 +5,20 @@ export function guacDecode(input: string): string[]; export function guacEncode(...items: string[]): string; interface JpegInputArgs { - width: number, - height: number, - stride: number, // The width of your input framebuffer OR your image width (if encoding a full image) - buffer: Buffer + width: number; + height: number; + stride: number; // The width of your input framebuffer OR your image width (if encoding a full image) + buffer: Buffer; // TODO: Allow different formats, or export a boxed ffi object which can store a format // (i.e: new JpegEncoder(FORMAT_xxx)). } /// Performs JPEG encoding. -export function jpegEncode(input: JpegInputArgs) : Promise; +export function jpegEncode(input: JpegInputArgs): Promise; // TODO: Version that can downscale? - /* remoting API? js side api: diff --git a/cvm-rs/package.json b/cvm-rs/package.json index 6154c9e..1f82bc6 100644 --- a/cvm-rs/package.json +++ b/cvm-rs/package.json @@ -8,7 +8,8 @@ "scripts": { "build": "cargo-cp-artifact -nc index.node -- cargo build --release --message-format=json-render-diagnostics", "install": "yarn build", - "test": "cargo test" + "test": "cargo test", + "format": "cargo fmt" }, "devDependencies": { "cargo-cp-artifact": "^0.1" diff --git a/cvm-rs/src/guac.rs b/cvm-rs/src/guac.rs index b92f289..c87bf5d 100644 --- a/cvm-rs/src/guac.rs +++ b/cvm-rs/src/guac.rs @@ -8,30 +8,30 @@ pub type Elements = Vec; /// Errors during decoding #[derive(Debug, Clone)] pub enum DecodeError { - /// Invalid guacamole instruction format - InvalidFormat, + /// Invalid guacamole instruction format + InvalidFormat, - /// Instruction is too long for the current decode policy. - InstructionTooLong, + /// Instruction is too long for the current decode policy. + InstructionTooLong, - /// Element is too long for the current decode policy. - ElementTooLong, + /// Element is too long for the current decode policy. + ElementTooLong, - /// Invalid element size. - ElementSizeInvalid, + /// Invalid element size. + ElementSizeInvalid, } pub type DecodeResult = std::result::Result; impl fmt::Display for DecodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidFormat => write!(f, "Invalid Guacamole instruction while decoding"), - Self::InstructionTooLong => write!(f, "Instruction too long for current decode policy"), - Self::ElementTooLong => write!(f, "Element too long for current decode policy"), - Self::ElementSizeInvalid => write!(f, "Element size is invalid") - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidFormat => write!(f, "Invalid Guacamole instruction while decoding"), + Self::InstructionTooLong => write!(f, "Instruction too long for current decode policy"), + Self::ElementTooLong => write!(f, "Element too long for current decode policy"), + Self::ElementSizeInvalid => write!(f, "Element size is invalid"), + } + } } // this decode policy abstraction would in theory be useful, @@ -40,154 +40,153 @@ impl fmt::Display for DecodeError { pub struct StaticDecodePolicy(); impl StaticDecodePolicy { - fn max_instruction_size(&self) -> usize { - INST_SIZE - } + fn max_instruction_size(&self) -> usize { + INST_SIZE + } - fn max_element_size(&self) -> usize { - ELEM_SIZE - } + fn max_element_size(&self) -> usize { + ELEM_SIZE + } } /// The default decode policy. pub type DefaultDecodePolicy = StaticDecodePolicy<12288, 4096>; - /// Encodes elements into a Guacamole instruction pub fn encode_instruction(elements: &Elements) -> String { - let mut str = String::new(); + let mut str = String::new(); - for elem in elements.iter() { - str.push_str(&format!("{}.{},", elem.len(), elem)); - } + for elem in elements.iter() { + str.push_str(&format!("{}.{},", elem.len(), elem)); + } - // hacky, but whatever - str.pop(); - str.push(';'); + // hacky, but whatever + str.pop(); + str.push(';'); - str + str } /// Decodes a Guacamole instruction to individual elements pub fn decode_instruction(element_string: &String) -> DecodeResult { - let policy = DefaultDecodePolicy {}; + let policy = DefaultDecodePolicy {}; - let mut vec: Elements = Vec::new(); - let mut current_position: usize = 0; + let mut vec: Elements = Vec::new(); + let mut current_position: usize = 0; - // Instruction is too long. Don't even bother - if policy.max_instruction_size() < element_string.len() { - return Err(DecodeError::InstructionTooLong); - } + // Instruction is too long. Don't even bother + if policy.max_instruction_size() < element_string.len() { + return Err(DecodeError::InstructionTooLong); + } - let chars = element_string.chars().collect::>(); + let chars = element_string.chars().collect::>(); - loop { - let mut element_size: usize = 0; + loop { + let mut element_size: usize = 0; - // Scan the integer value in by hand. This is mostly because - // I'm stupid, and the Rust integer parsing routines (seemingly) - // require a substring (or a slice, but, if you can generate a slice, - // you can also just scan the value in by hand.) - // - // We bound this anyways and do quite the checks, so even though it's not great, - // it should be generally fine (TM). - loop { - let c = chars[current_position]; + // Scan the integer value in by hand. This is mostly because + // I'm stupid, and the Rust integer parsing routines (seemingly) + // require a substring (or a slice, but, if you can generate a slice, + // you can also just scan the value in by hand.) + // + // We bound this anyways and do quite the checks, so even though it's not great, + // it should be generally fine (TM). + loop { + let c = chars[current_position]; - if c >= '0' && c <= '9' { - element_size = element_size * 10 + (c as usize) - ('0' as usize); - } else { - if c == '.' { - break; - } + if c >= '0' && c <= '9' { + element_size = element_size * 10 + (c as usize) - ('0' as usize); + } else { + if c == '.' { + break; + } - return Err(DecodeError::InvalidFormat); - } - current_position += 1; - } + return Err(DecodeError::InvalidFormat); + } + current_position += 1; + } - // Eat the '.' seperating the size and the element data; - // our integer scanning ensures we only get here in the case that this is actually the '.' - // character. - current_position += 1; + // Eat the '.' seperating the size and the element data; + // our integer scanning ensures we only get here in the case that this is actually the '.' + // character. + current_position += 1; - // Make sure the element size doesn't overflow the decode policy - // or the size of the whole instruction. + // Make sure the element size doesn't overflow the decode policy + // or the size of the whole instruction. - if element_size >= policy.max_element_size() { - return Err(DecodeError::ElementTooLong); - } + if element_size >= policy.max_element_size() { + return Err(DecodeError::ElementTooLong); + } - if element_size >= element_string.len() { - return Err(DecodeError::ElementSizeInvalid); - } + if element_size >= element_string.len() { + return Err(DecodeError::ElementSizeInvalid); + } - // cutoff elements or something - if current_position + element_size > chars.len()-1 { - //println!("? {current_position} a {}", chars.len()); - return Err(DecodeError::InvalidFormat); - } - - let element = chars - .iter() - .skip(current_position) - .take(element_size) - .collect::(); + // cutoff elements or something + if current_position + element_size > chars.len() - 1 { + //println!("? {current_position} a {}", chars.len()); + return Err(DecodeError::InvalidFormat); + } - current_position += element_size; + let element = chars + .iter() + .skip(current_position) + .take(element_size) + .collect::(); - vec.push(element); - - // make sure seperator is proper - match chars[current_position] { - ',' => {} - ';' => break, - _ => return Err(DecodeError::InvalidFormat), - } + current_position += element_size; - // eat the ',' - current_position += 1; - } + vec.push(element); - Ok(vec) + // make sure seperator is proper + match chars[current_position] { + ',' => {} + ';' => break, + _ => return Err(DecodeError::InvalidFormat), + } + + // eat the ',' + current_position += 1; + } + + Ok(vec) } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn decode_basic() { - let test = String::from("7.connect,3.vm1;"); - let res = decode_instruction(&test); + #[test] + fn decode_basic() { + let test = String::from("7.connect,3.vm1;"); + let res = decode_instruction(&test); - assert!(res.is_ok()); - assert_eq!(res.unwrap(), vec!["connect", "vm1"]); - } + assert!(res.is_ok()); + assert_eq!(res.unwrap(), vec!["connect", "vm1"]); + } - #[test] - fn decode_errors() { - let test = String::from("700.connect,3.vm1;"); - let res = decode_instruction(&test); + #[test] + fn decode_errors() { + let test = String::from("700.connect,3.vm1;"); + let res = decode_instruction(&test); - eprintln!("Error for: {}", res.clone().unwrap_err()); + eprintln!("Error for: {}", res.clone().unwrap_err()); - assert!(res.is_err()) - } + assert!(res.is_err()) + } - // generally just test that the codec even works - // (we can decode a instruction we created) - #[test] - fn general_codec_works() { - let vec = vec![String::from("connect"), String::from("vm1")]; - let test = encode_instruction(&vec); + // generally just test that the codec even works + // (we can decode a instruction we created) + #[test] + fn general_codec_works() { + let vec = vec![String::from("connect"), String::from("vm1")]; + let test = encode_instruction(&vec); - assert_eq!(test, "7.connect,3.vm1;"); + assert_eq!(test, "7.connect,3.vm1;"); - let res = decode_instruction(&test); + let res = decode_instruction(&test); - assert!(res.is_ok()); - assert_eq!(res.unwrap(), vec); - } + assert!(res.is_ok()); + assert_eq!(res.unwrap(), vec); + } } diff --git a/cvm-rs/src/guac_js.rs b/cvm-rs/src/guac_js.rs index 1a8dd01..4a9aaee 100644 --- a/cvm-rs/src/guac_js.rs +++ b/cvm-rs/src/guac_js.rs @@ -1,5 +1,5 @@ -use neon::prelude::*; use crate::guac; +use neon::prelude::*; fn guac_decode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsArray> { let input = cx.argument::(0)?.value(cx); diff --git a/cvm-rs/src/jpeg_compressor.rs b/cvm-rs/src/jpeg_compressor.rs index 40dd77f..f716192 100644 --- a/cvm-rs/src/jpeg_compressor.rs +++ b/cvm-rs/src/jpeg_compressor.rs @@ -61,13 +61,13 @@ impl JpegCompressor { (TJFLAG_NOREALLOC) as i32, ); - // TODO: Result sex so we can actually notify failure + // TODO: Result sex so we can actually notify failure if res == -1 { return Vec::new(); } - // Truncate down to the size we're given back - vec.truncate(size as usize); + // Truncate down to the size we're given back + vec.truncate(size as usize); return vec; } } diff --git a/cvm-rs/src/jpeg_js.rs b/cvm-rs/src/jpeg_js.rs index de87f9b..4547036 100644 --- a/cvm-rs/src/jpeg_js.rs +++ b/cvm-rs/src/jpeg_js.rs @@ -41,7 +41,7 @@ fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise> let copy: Arc>> = Arc::new(Mutex::new(Vec::with_capacity(buf.len()))); - // Copy from the node buffer to our temporary buffer + // Copy from the node buffer to our temporary buffer { let mut locked = copy.lock().unwrap(); let cap = locked.capacity(); @@ -49,8 +49,8 @@ fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise> locked.copy_from_slice(buf); } - // Spawn off a tokio blocking pool thread that will do the work for us - runtime.spawn_blocking(move || { + // Spawn off a tokio blocking pool thread that will do the work for us + runtime.spawn_blocking(move || { let clone = Arc::clone(©); let locked = clone.lock().unwrap(); @@ -70,7 +70,7 @@ fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise> b.compress_buffer(&image) }); - // Fulfill the Javascript promise with our encoded buffer + // Fulfill the Javascript promise with our encoded buffer deferred.settle_with(&channel, move |mut cx| { let mut buf = cx.buffer(vec.len())?; let slice = buf.as_mut_slice(&mut cx); diff --git a/cvm-rs/src/lib.rs b/cvm-rs/src/lib.rs index e6c6291..39f4bbd 100644 --- a/cvm-rs/src/lib.rs +++ b/cvm-rs/src/lib.rs @@ -4,13 +4,11 @@ mod guac_js; mod jpeg_compressor; mod jpeg_js; - use neon::prelude::*; - #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { - // Mostly transitionary, later on API should change + // Mostly transitionary, later on API should change cx.export_function("jpegEncode", jpeg_js::jpeg_encode)?; cx.export_function("guacDecode", guac_js::guac_decode)?; diff --git a/cvmts/package.json b/cvmts/package.json index cf9497c..dddf500 100644 --- a/cvmts/package.json +++ b/cvmts/package.json @@ -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" + } } diff --git a/cvmts/src/BanManager.ts b/cvmts/src/BanManager.ts index b214d6b..23094f3 100644 --- a/cvmts/src/BanManager.ts +++ b/cvmts/src/BanManager.ts @@ -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); } -} \ No newline at end of file + 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); + } +} diff --git a/cvmts/src/CollabVMServer.ts b/cvmts/src/CollabVMServer.ts index 1c25c6b..3d1eb2b 100644 --- a/cvmts/src/CollabVMServer.ts +++ b/cvmts/src/CollabVMServer.ts @@ -39,7 +39,6 @@ type VoteTally = { no: number; }; - export default class CollabVMServer { private Config: IConfig; diff --git a/cvmts/src/Database.ts b/cvmts/src/Database.ts index 9049b54..775344c 100644 --- a/cvmts/src/Database.ts +++ b/cvmts/src/Database.ts @@ -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 { - 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; - } -} \ No newline at end of file + async isIPBanned(ip: string): Promise { + 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; + } +} diff --git a/cvmts/src/IConfig.ts b/cvmts/src/IConfig.ts index 99fd10b..0c309c1 100644 --- a/cvmts/src/IConfig.ts +++ b/cvmts/src/IConfig.ts @@ -14,7 +14,7 @@ export default interface IConfig { directory: string; accountID: string; licenseKey: string; - } + }; tcp: { enabled: boolean; host: string; diff --git a/cvmts/src/TCP/TCPClient.ts b/cvmts/src/TCP/TCPClient.ts index f20498e..7d3ff9c 100644 --- a/cvmts/src/TCP/TCPClient.ts +++ b/cvmts/src/TCP/TCPClient.ts @@ -37,7 +37,7 @@ export default class TCPClient extends EventEmitter implements NetworkClient { send(msg: string): Promise { 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); diff --git a/cvmts/src/TCP/TCPServer.ts b/cvmts/src/TCP/TCPServer.ts index 9af8cb5..8a8158b 100644 --- a/cvmts/src/TCP/TCPServer.ts +++ b/cvmts/src/TCP/TCPServer.ts @@ -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; } diff --git a/cvmts/src/WebSocket/WSServer.ts b/cvmts/src/WebSocket/WSServer.ts index 8af7cec..82e7a24 100644 --- a/cvmts/src/WebSocket/WSServer.ts +++ b/cvmts/src/WebSocket/WSServer.ts @@ -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; } diff --git a/cvmts/src/index.ts b/cvmts/src/index.ts index a985e96..ac4bb91 100644 --- a/cvmts/src/index.ts +++ b/cvmts/src/index.ts @@ -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) { diff --git a/cvmts/tsconfig.json b/cvmts/tsconfig.json index baa2339..517be24 100644 --- a/cvmts/tsconfig.json +++ b/cvmts/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../tsconfig.json", - "include": [ "src/**/*" ], - "compilerOptions": { - "resolveJsonModule": true, - } + "extends": "../tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "resolveJsonModule": true + } } diff --git a/package.json b/package.json index fcb9aa2..9678684 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "cvmts-repo", "workspaces": [ - "shared", "cvm-rs", "cvmts", "collab-vm-1.2-binary-protocol" @@ -13,7 +12,8 @@ "@types/jsbn": "^1.2.33", "@types/node": "^20.14.10", "parcel": "^2.12.0", - "prettier": "^3.2.5", + "prettier": "^3.3.3", + "prettier-plugin-toml": "^2.0.1", "rimraf": "^6.0.1", "typescript": "^5.4.4" }, @@ -21,6 +21,7 @@ "scripts": { "build": "yarn workspaces foreach -Apt run build", "serve": "node cvmts/dist/index.js", - "clean": "npx rimraf .parcel-cache .yarn **/node_modules **/dist cvm-rs/target cvm-rs/index.node" + "clean": "npx rimraf .parcel-cache .yarn **/node_modules **/dist cvm-rs/target cvm-rs/index.node", + "format": "prettier -w config.example.toml && yarn workspaces foreach -Apt run format" } } diff --git a/yarn.lock b/yarn.lock index 6d8793f..f7510d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -88,7 +88,6 @@ __metadata: msgpackr: "npm:^1.10.2" pino: "npm:^9.3.1" pino-pretty: "npm:^11.2.1" - prettier: "npm:^3.2.5" sharp: "npm:^0.33.3" toml: "npm:^3.0.0" typescript: "npm:^5.4.4" @@ -1466,6 +1465,22 @@ __metadata: languageName: node linkType: hard +"@taplo/core@npm:^0.1.0": + version: 0.1.1 + resolution: "@taplo/core@npm:0.1.1" + checksum: 10c0/c36f761431b2e959742d8e186e74306fb8991d84589e2f03b6481244cc407275fa448a217ef87b8aa1e226615fd7ba85c60e6f0221f01d891b90dd30b45cb13b + languageName: node + linkType: hard + +"@taplo/lib@npm:^0.4.0-alpha.2": + version: 0.4.0-alpha.2 + resolution: "@taplo/lib@npm:0.4.0-alpha.2" + dependencies: + "@taplo/core": "npm:^0.1.0" + checksum: 10c0/650ed35ba949054eb8dcfbaf77d6154d9639f5d8fa96d89546c399a95e699d7e01f65e308f89478d41e0120baa3572f25595fe088d19e62cc56ad3eb2b5acbb5 + languageName: node + linkType: hard + "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -1966,7 +1981,8 @@ __metadata: "@types/jsbn": "npm:^1.2.33" "@types/node": "npm:^20.14.10" parcel: "npm:^2.12.0" - prettier: "npm:^3.2.5" + prettier: "npm:^3.3.3" + prettier-plugin-toml: "npm:^2.0.1" rimraf: "npm:^6.0.1" typescript: "npm:^5.4.4" languageName: unknown @@ -3504,7 +3520,18 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.2.5": +"prettier-plugin-toml@npm:^2.0.1": + version: 2.0.1 + resolution: "prettier-plugin-toml@npm:2.0.1" + dependencies: + "@taplo/lib": "npm:^0.4.0-alpha.2" + peerDependencies: + prettier: ^3.0.3 + checksum: 10c0/8a67133b1a71d82f6fe20fe92e18251ae2930d0fe96d18af8c2333cc8fffce408642b6d6f21441932a97a248815cc82b3f372971eae84aab80cad2f0d78bdc68 + languageName: node + linkType: hard + +"prettier@npm:^3.3.3": version: 3.3.3 resolution: "prettier@npm:3.3.3" bin: