diff options
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/ConsoleInput.hx | 23 | ||||
| -rw-r--r-- | src/server/HttpServer.hx | 3 | ||||
| -rw-r--r-- | src/server/Main.hx | 120 |
3 files changed, 126 insertions, 20 deletions
diff --git a/src/server/ConsoleInput.hx b/src/server/ConsoleInput.hx index 9b8faf3..119d87e 100644 --- a/src/server/ConsoleInput.hx +++ b/src/server/ConsoleInput.hx @@ -18,6 +18,7 @@ private typedef CommandData = { private enum abstract Command(String) from String { var AddAdmin = "addAdmin"; + var RemoveAdmin = "removeAdmin"; var Replay = "replay"; var LogList = "logList"; var Exit = "exit"; @@ -30,6 +31,10 @@ class ConsoleInput { args: ["name", "password"], desc: "Adds channel admin" }, + RemoveAdmin => { + args: ["name"], + desc: "Removes channel admin" + }, Replay => { args: ["name"], desc: "Replay log file on server from user/logs/" @@ -54,10 +59,10 @@ class ConsoleInput { output: process.stdout, completer: onCompletion }); - haxe.Log.trace = (msg, ?pos) -> { + haxe.Log.trace = (msg:Dynamic, ?infos:haxe.PosInfos) -> { Readline.clearLine(process.stdout, 0); Readline.cursorTo(process.stdout, 0, null); - Console.log(msg); + Console.log(formatOutput(msg, infos)); rl.prompt(true); }; rl.prompt(); @@ -68,6 +73,16 @@ class ConsoleInput { // rl.on("close", exit); } + function formatOutput(v:Dynamic, infos:haxe.PosInfos):String { + var str = Std.string(v); + if (infos == null) return str; + if (infos.customParams != null) { + for (v in infos.customParams) + str += ", " + Std.string(v); + } + return str; + } + function onCompletion(line:String):Array<Or<Array<String>, String>> { final commands:Array<String> = [ for (item in commands.keys()) '/$item ' @@ -102,6 +117,10 @@ class ConsoleInput { } main.addAdmin(name, password); + case RemoveAdmin: + final name = args[0]; + main.removeAdmin(name); + case Replay: Utils.ensureDir(main.logsDir); final name = args[0]; diff --git a/src/server/HttpServer.hx b/src/server/HttpServer.hx index 44e4f36..2076336 100644 --- a/src/server/HttpServer.hx +++ b/src/server/HttpServer.hx @@ -58,8 +58,7 @@ class HttpServer { res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Type", getMimeType(ext)); - if (allowLocalRequests - && req.connection.remoteAddress == req.connection.localAddress + if (allowLocalRequests && req.socket.remoteAddress == req.socket.localAddress || allowedLocalFiles[url]) { if (isMediaExtension(ext)) { allowedLocalFiles[url] = true; diff --git a/src/server/Main.hx b/src/server/Main.hx index ea1e40b..2f3a8a4 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -196,16 +196,32 @@ class Main { function loadUsers():UserList { final customPath = '$rootDir/user/users.json'; if (!FileSystem.exists(customPath)) return { - admins: [] + admins: [], + bans: [] }; - return Json.parse(File.getContent(customPath)); + final users:UserList = Json.parse(File.getContent(customPath)); + if (users.admins == null) users.admins = []; + if (users.bans == null) users.bans = []; + for (field in users.bans) { + field.toDate = Date.fromString(cast field.toDate); + } + return users; } function writeUsers(users:UserList):Void { final folder = '$rootDir/user'; Utils.ensureDir(folder); - final data = Json.stringify(users, "\t"); - File.saveContent('$folder/users.json', data); + final data:UserList = { + admins: users.admins, + bans: [ + for (field in users.bans) { + ip: field.ip, + toDate: cast field.toDate.toString() + } + ], + salt: users.salt + } + File.saveContent('$folder/users.json', Json.stringify(data, "\t")); } function saveState():Void { @@ -222,6 +238,7 @@ class Main { } final json = Json.stringify(data, "\t"); File.saveContent(statePath, json); + writeUsers(userList); } function loadState():Void { @@ -251,10 +268,12 @@ class Main { File.saveContent('$crashesFolder/$name.json', Json.stringify(data, "\t")); } + var isHeroku = false; + function initIntergationHandlers():Void { + isHeroku = process.env["_"] != null && process.env["_"].contains("heroku"); // Prevent heroku idle when clients online (needs APP_URL env var) - if (process.env["_"] != null && process.env["_"].contains("heroku") - && process.env["APP_URL"] != null) { + if (isHeroku && process.env["APP_URL"] != null) { var url = process.env["APP_URL"]; if (!url.startsWith("http")) url = 'http://$url'; new Timer(10 * 60 * 1000).run = function() { @@ -265,18 +284,34 @@ class Main { } } + function clientIp(req:IncomingMessage):String { + // Heroku uses internal proxy, so header cannot be spoofed + if (isHeroku) { + var forwarded:String = req.headers["x-forwarded-for"]; + forwarded = forwarded.split(",")[0].trim(); + if (forwarded == null || forwarded.length == 0) return req.socket.remoteAddress; + return forwarded; + } + return req.socket.remoteAddress; + } + public function addAdmin(name:String, password:String):Void { password += config.salt; final hash = Sha256.encode(password); - if (userList.admins == null) userList.admins = []; userList.admins.push({ name: name, hash: hash }); - writeUsers(userList); trace('Admin $name added.'); } + public function removeAdmin(name:String):Void { + userList.admins.remove( + userList.admins.find(item -> item.name == name) + ); + trace('Admin $name removed.'); + } + public function replayLog(events:Array<ServerEvent>):Void { final timer = new Timer(1000); timer.run = () -> { @@ -309,11 +344,11 @@ class Main { } function onConnect(ws:WebSocket, req:IncomingMessage):Void { - final ip = req.connection.remoteAddress; + final ip = clientIp(req); final id = freeIds.length > 0 ? freeIds.shift() : clients.length; final name = 'Guest ${id + 1}'; trace('$name connected ($ip)'); - final isAdmin = config.localAdmins && req.connection.localAddress == ip; + final isAdmin = config.localAdmins && req.socket.localAddress == ip; final client = new Client(ws, req, id, name, 0); client.isAdmin = isAdmin; clients.push(client); @@ -366,6 +401,7 @@ class Main { if (videoTimer.isPaused()) videoTimer.play(); } + checkBan(client); send(client, { type: Connected, connected: { @@ -407,6 +443,34 @@ class Main { case UpdateClients: sendClientList(); + + case BanClient: + if (!checkPermission(client, BanClientPerm)) return; + final name = data.banClient.name; + final bannedClient = clients.getByName(name); + if (bannedClient == null) return; + if (client.name == name || bannedClient.isAdmin) { + serverMessage(client, "adminsCannotBeBannedError"); + return; + } + final ip = clientIp(bannedClient.req); + userList.bans.remove(userList.bans.find(item -> item.ip == ip)); + if (data.banClient.time == 0) { + bannedClient.isBanned = false; + sendClientList(); + return; + } + final currentTime = Date.now().getTime(); + final time = currentTime + data.banClient.time * 1000; + if (time < currentTime) return; + userList.bans.push({ + ip: ip, + toDate: Date.fromTime(time) + }); + checkBan(bannedClient); + serverMessage(client, '${bannedClient.name} ($ip) has been banned.'); + sendClientList(); + case Login: final name = data.login.clientName.trim(); final lcName = name.toLowerCase(); @@ -434,6 +498,7 @@ class Main { } client.name = name; client.isUser = true; + checkBan(client); send(client, { type: data.type, login: { @@ -750,16 +815,39 @@ class Main { } function checkPermission(client:Client, perm:Permission):Bool { + if (client.isBanned) checkBan(client); final state = client.hasPermission(perm, config.permissions); - if (!state) send(client, { - type: ServerMessage, - serverMessage: { - textId: "accessError" - } - }); + if (!state) { + send(client, { + type: ServerMessage, + serverMessage: { + textId: "accessError" + } + }); + } return state; } + function checkBan(client:Client):Void { + if (client.isAdmin) { + client.isBanned = false; + return; + } + final ip = clientIp(client.req); + final currentTime = Date.now().getTime(); + for (ban in userList.bans) { + if (ban.ip != ip) continue; + final isOutdated = ban.toDate.getTime() < currentTime; + client.isBanned = !isOutdated; + if (isOutdated) { + userList.bans.remove(ban); + trace('${client.name} ban removed'); + sendClientList(); + } + break; + } + } + final matchHtmlChars = ~/[&^<>'"]/; final matchGuestName = ~/guest [0-9]+/; |
