diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Types.hx | 15 | ||||
| -rw-r--r-- | src/client/Buttons.hx | 13 | ||||
| -rw-r--r-- | src/client/Main.hx | 46 | ||||
| -rw-r--r-- | src/server/ConsoleInput.hx | 59 | ||||
| -rw-r--r-- | src/server/Main.hx | 78 |
5 files changed, 188 insertions, 23 deletions
diff --git a/src/Types.hx b/src/Types.hx index 8eda528..49e93e4 100644 --- a/src/Types.hx +++ b/src/Types.hx @@ -15,9 +15,20 @@ typedef Config = { videoLimit:Int, leaderRequest:String, emotes:Array<Emote>, - filters:Array<Filter> + filters:Array<Filter>, + ?salt:String }; +typedef UserList = { + admins:Array<UserField>, + ?salt:String +} + +typedef UserField = { + name:String, + hash:String +} + typedef Emote = { name:String, image:String @@ -58,6 +69,7 @@ typedef WsEvent = { }, ?login:{ clientName:String, + ?passHash:String, ?clients:Array<ClientData>, ?isUnknownClient:Bool, }, @@ -119,6 +131,7 @@ typedef WsEvent = { enum abstract WsEventType(String) { var Connected; var Login; + var PasswordRequest; var LoginError; var Logout; var Message; diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx index 51ba75d..e567331 100644 --- a/src/client/Buttons.hx +++ b/src/client/Buttons.hx @@ -209,13 +209,12 @@ class Buttons { static function initChatInput(main:Main):Void { final guestName:InputElement = cast ge("#guestname"); guestName.onkeydown = e -> { - if (guestName.value.length == 0) return; - if (e.keyCode == 13) main.send({ - type: Login, - login: { - clientName: guestName.value - } - }); + if (e.keyCode == 13) main.guestLogin(guestName.value); + } + + final guestPass:InputElement = cast ge("#guestpass"); + guestPass.onkeydown = e -> { + if (e.keyCode == 13) main.userLogin(guestName.value, guestPass.value); } final chatLine:InputElement = cast ge("#chatline"); diff --git a/src/client/Main.hx b/src/client/Main.hx index c74a600..9d383bc 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -1,5 +1,6 @@ package client; +import haxe.crypto.Sha256; import haxe.Timer; import js.html.MouseEvent; import js.html.KeyboardEvent; @@ -217,10 +218,14 @@ class Main { case Login: onLogin(data.login.clients, data.login.clientName); + case PasswordRequest: + showGuestPasswordPanel(); + case LoginError: final text = Lang.get("usernameError") .replace("$MAX", '${config.maxLoginLength}'); serverMessage(4, text); + showGuestLoginPanel(); case Logout: updateClients(data.logout.clients); @@ -324,12 +329,12 @@ class Main { onLogin(connected.clients, connected.clientName); } final guestName:InputElement = cast ge("#guestname"); - if (guestName.value.length > 0) send({ - type: Login, - login: { - clientName: guestName.value - } - }); + final guestPass:InputElement = cast ge("#guestpass"); + if (config.salt != null && guestPass.value.length > 0) { + userLogin(guestName.value, guestPass.value); + } else { + guestLogin(guestName.value); + } clearChat(); serverMessage(1); for (message in connected.history) { @@ -338,6 +343,27 @@ class Main { player.setItems(connected.videoList, connected.itemPos); } + public function guestLogin(name:String):Void { + if (name.length == 0) return; + send({ + type: Login, login: { + clientName: name + } + }); + } + + public function userLogin(name:String, password:String):Void { + if (config.salt == null) return; + if (password.length == 0) return; + if (name.length == 0) return; + send({ + type: Login, login: { + clientName: name, + passHash: Sha256.encode(password + config.salt) + } + }); + } + function setConfig(config:Config):Void { this.config = config; pageTitle = config.channelName; @@ -386,6 +412,7 @@ class Main { function showGuestLoginPanel():Void { ge("#guestlogin").style.display = "block"; + ge("#guestpassword").style.display = "none"; ge("#chatline").style.display = "none"; ge("#exitBtn").textContent = Lang.get("login"); Browser.window.dispatchEvent(new Event("resize")); @@ -393,12 +420,19 @@ class Main { function hideGuestLoginPanel():Void { ge("#guestlogin").style.display = "none"; + ge("#guestpassword").style.display = "none"; ge("#chatline").style.display = "block"; ge("#exitBtn").textContent = Lang.get("exit"); if (isAdmin()) ge("#clearchatbtn").style.display = "inline-block"; Browser.window.dispatchEvent(new Event("resize")); } + function showGuestPasswordPanel():Void { + ge("#guestlogin").style.display = "none"; + ge("#chatline").style.display = "none"; + ge("#guestpassword").style.display = "block"; + } + function updateClients(newClients:Array<ClientData>):Void { clients.resize(0); for (client in newClients) { diff --git a/src/server/ConsoleInput.hx b/src/server/ConsoleInput.hx new file mode 100644 index 0000000..8b2463f --- /dev/null +++ b/src/server/ConsoleInput.hx @@ -0,0 +1,59 @@ +package server; + +import js.html.Console; +import js.node.Readline; +import js.Node.process; +using StringTools; + +class ConsoleInput { + + final main:Main; + + public function new(main:Main) { + this.main = main; + } + + public function initConsoleInput():Void { + final rl = Readline.createInterface(process.stdin, process.stdout); + haxe.Log.trace = (msg, ?pos) -> { + Readline.clearLine(process.stdout, 0); + Readline.cursorTo(process.stdout, 0, null); + Console.log(msg); + rl.prompt(true); + }; + rl.prompt(); + rl.on("line", line -> { + parseLine(line); + rl.prompt(); + }); + // rl.on("close", exit); + } + + function parseLine(line:String):Void { + if (line.startsWith("/addAdmin")) { + final args = line.split(" "); + if (args.length != 3) { + trace("Wrong count of arguments"); + return; + } + final name = args[1]; + final password = args[2]; + if (main.badNickName(name)) { + final error = Lang.get("usernameError") + .replace("$MAX", '${main.config.maxLoginLength}'); + trace(error); + return; + } + main.addAdmin(name, password); + + } else if (line == "/exit") { + main.exit(); + return; + } else { + trace('Unknown command "$line". List: +/addAdmin name password | Adds channel admin +/exit | Exit process'); + } + } + +} diff --git a/src/server/Main.hx b/src/server/Main.hx index aaf9277..424f22f 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -1,5 +1,6 @@ package server; +import haxe.crypto.Sha256; import js.lib.Date; import sys.FileSystem; import sys.io.File; @@ -25,9 +26,11 @@ class Main { final localIp:String; var globalIp:String; final port:Int; - final config:Config; + public final config:Config; + final userList:UserList; final clients:Array<Client> = []; final freeIds:Array<Int> = []; + final consoleInput:ConsoleInput; final videoList = new VideoList(); final videoTimer = new VideoTimer(); final messages:Array<Message> = []; @@ -39,10 +42,6 @@ class Main { final envPort = (process.env : Dynamic).PORT; if (envPort != null) port = envPort; statePath = '$rootDir/user/state.json'; - function exit() { - saveState(); - process.exit(); - } // process.on("exit", exit); process.on("SIGINT", exit); // ctrl+c process.on("SIGUSR1", exit); // kill pid @@ -59,9 +58,13 @@ class Main { logError("unhandledRejection", reason); exit(); }); + consoleInput = new ConsoleInput(this); + consoleInput.initConsoleInput(); initIntergationHandlers(); loadState(); - config = getUserConfig(); + config = loadUserConfig(); + userList = loadUsers(); + config.salt = generateConfigSalt(); localIp = Utils.getLocalIp(); globalIp = localIp; this.port = port; @@ -84,7 +87,18 @@ class Main { wss.on("connection", onConnect); } - function getUserConfig():Config { + public function exit():Void { + saveState(); + process.exit(); + } + + function generateConfigSalt():String { + if (userList.salt == null) + userList.salt = Sha256.encode('${Math.random()}'); + return userList.salt; + } + + function loadUserConfig():Config { final config:Config = Json.parse(File.getContent('$rootDir/default-config.json')); final customPath = '$rootDir/user/config.json'; if (!FileSystem.exists(customPath)) return config; @@ -96,6 +110,23 @@ class Main { return config; } + function loadUsers():UserList { + final customPath = '$rootDir/user/users.json'; + if (!FileSystem.exists(customPath)) return { + admins: [] + }; + return Json.parse(File.getContent(customPath)); + } + + function writeUsers(users:UserList):Void { + final folder = '$rootDir/user'; + if (!FileSystem.exists(folder)) { + FileSystem.createDirectory(folder); + } + final data = Json.stringify(users, "\t"); + File.saveContent('$folder/users.json', data); + } + function saveState():Void { trace("Saving state..."); final data:ServerState = { @@ -146,6 +177,18 @@ class Main { } } + 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.'); + } + function onConnect(ws:WebSocket, req:IncomingMessage):Void { final ip = req.connection.remoteAddress; final id = freeIds.length > 0 ? freeIds.shift() : clients.length; @@ -153,7 +196,7 @@ class Main { trace('$name connected ($ip)'); final isAdmin = req.connection.localAddress == ip; final client = new Client(ws, req, id, name, 0); - if (isAdmin) client.group.set(Admin); + client.isAdmin = isAdmin; clients.push(client); if (clients.length == 1 && videoList.length > 0) if (videoTimer.isPaused()) videoTimer.play(); @@ -205,6 +248,22 @@ class Main { send(client, {type: LoginError}); return; } + final hash = data.login.passHash; + if (hash == null) { + if (userList.admins.exists(a -> a.name == name)) { + send(client, {type: PasswordRequest}); + return; + } + } else { + if (userList.admins.exists( + a -> a.name == name && a.hash == hash + )) client.isAdmin = true; + else { + // TODO server msg type + send(client, {type: LoginError}); + return; + } + } client.name = name; client.isUser = true; send(client, { @@ -217,6 +276,7 @@ class Main { }); sendClientList(); + case PasswordRequest: case LoginError: case Logout: final oldName = client.name; @@ -425,7 +485,7 @@ class Main { final htmlChars = ~/[&^<>'"]/; - function badNickName(name:String):Bool { + public function badNickName(name:String):Bool { if (name.length == 0) return true; if (htmlChars.match(name)) return true; return false; |
