Browse Source

Initial Commit

Alexander Vornam 3 months ago
commit
a7799c6d25
12 changed files with 1294 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 1 0
      config/ban.json
  3. 175 0
      index.js
  4. 546 0
      package-lock.json
  5. 12 0
      package.json
  6. 39 0
      ssl/ca_bundle.crt
  7. 37 0
      ssl/certificate.crt
  8. 27 0
      ssl/private.key
  9. 142 0
      www/css/app.css
  10. 30 0
      www/css/theme.css
  11. 76 0
      www/index.html
  12. 206 0
      www/js/main.js

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.idea/
+node_modules/
+log.txt

+ 1 - 0
config/ban.json

@@ -0,0 +1 @@
+[]

+ 175 - 0
index.js

@@ -0,0 +1,175 @@
+const fs = require('fs');
+const https = require('https');
+
+const express = require('express');
+const app = express();
+
+const options = {
+    key: fs.readFileSync("ssl/private.key"),
+    cert: fs.readFileSync("ssl/certificate.crt"),
+    ca: [ fs.readFileSync('ssl/ca_bundle.crt') ]
+};
+
+const server = https.createServer(options, app);
+const io = require('socket.io')(server);
+
+const PORT = 443;
+
+const admins = {
+    'André': 'passwort'
+}
+
+var users = [];
+
+var bannedIps = [];
+
+fs.readFile("config/ban.json", "utf8", (err, data) => {
+    if (!err) {
+        bannedIps = JSON.parse(data);
+    }
+})
+
+app.use(express.static(__dirname + '/www'));
+app.use('/node_modules', express.static(__dirname + '/node_modules'));
+
+io.on('connection', (socket) => {
+    const ip = socket.handshake.address;
+
+    log('- New user joined the server:', ip);
+
+    for (const bannedItem of bannedIps) {
+        if (bannedItem.ip == ip) {
+            log('- User blacklisted, kicking:', ip)
+            socket.emit('serverKick');
+            socket.disconnect();
+            break;
+        }
+    }
+
+    socket.emit('serverHandshake');
+
+    const user = { ip: ip, socket: socket };
+    users.push(user);
+
+    socket.on('login', (username, password) => {
+        if (!username) {
+            socket.emit('usernameInvalid');
+            return;
+        }
+
+        for (let user of users) {
+            if (user.name == username) {
+                socket.emit('usernameTaken');
+                return;
+            }
+        }
+
+
+        if (admins && admins[username]) {
+            if (!password) {
+                log('- Attempted login as admin without password.', username, '(' + ip + ')');
+                socket.emit('passwordRequired');
+                return;
+            } else if (admins[username] != password) {
+                log('- Attempted login as admin with wrong password.', username, '(' + ip + ')');
+                socket.emit('passwordWrong');
+                return;
+            }
+
+            log('- Admin "' + username + '" login successful');
+            user.admin = true;
+
+            socket.on('requestKick', (userToBeKicked) => {
+                log('- Admin "' + username + '" requested kick of User "' + userToBeKicked.name + '"');
+                kickUser(userToBeKicked);
+            });
+
+            socket.on('requestLiftBan', (ip) => {
+                log('- Admin "' + username + '" requested to lift ban for ip "' + ip + '"');
+                liftBan(ip);
+            });
+        }
+
+        log('- New user logged in:', username, '(' + ip + ')');
+
+        user.name = username;
+
+        socket.emit('serverLogin', getCleanUser(user));
+        io.emit('userJoined', username);
+        updateUsers();
+        updateBanned();
+
+        socket.on('disconnect', () => {
+            log('- User joined the server:', ip);
+
+            users = users.filter((listUser) => listUser !== user);
+            io.emit('userLeft', username);
+            updateUsers();
+        });
+
+        socket.on('message', (msg) => {
+            if (msg) {
+                log(user.name + ':', msg, '(' + user.ip + ')');
+                io.emit('message', msg, getCleanUser(user), Date.now());
+            }
+        });
+    });
+});
+
+function updateUsers() {
+    io.emit('usersUpdated', users.filter((user) => user.name != null).map(getCleanUser));
+}
+
+function getCleanUser(user) {
+    return {
+        name: user.name,
+        admin: user.admin
+    }
+}
+
+function kickUser(user) {
+    let ip;
+
+    for (const exUser of users) {
+        if (exUser.name == user.name) {
+            ip = exUser.ip;
+            break;
+        }
+    }
+
+    bannedIps.push({ ip: ip, user: user });
+    updateBanned();
+
+    for (const exUser of users) {
+        if (exUser.ip == ip) {
+            exUser.socket.emit('serverKick');
+            exUser.socket.disconnect();
+        }
+    }
+}
+
+function liftBan(ip) {
+    bannedIps = bannedIps.filter((listItem) => listItem.ip != ip);
+    updateBanned();
+}
+
+function updateBanned() {
+    fs.writeFile('config/ban.json', JSON.stringify(bannedIps), (e) => {});
+    io.emit('bannedUpdated', bannedIps);
+}
+
+function log(...inputs) {
+    var output = '[' + new Date().toISOString().substr(0, 19).replace('T', ', ') + '] ';
+
+    for (const i of inputs) {
+        output += i + ' ';
+    }
+
+    console.log(output);
+
+    fs.appendFile('log.txt', '' + output + '\n', (e) => {});
+}
+
+server.listen(PORT, () => {
+    log('- Server up and running at port ' +  PORT);
+});

+ 546 - 0
package-lock.json

@@ -0,0 +1,546 @@
+{
+	"name": "Chat",
+	"version": "0.0.1",
+	"lockfileVersion": 1,
+	"requires": true,
+	"dependencies": {
+		"@types/component-emitter": {
+			"version": "1.2.10",
+			"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
+			"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
+		},
+		"@types/cookie": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+			"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+		},
+		"@types/cors": {
+			"version": "2.8.12",
+			"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
+			"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
+		},
+		"@types/node": {
+			"version": "16.7.1",
+			"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz",
+			"integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A=="
+		},
+		"accepts": {
+			"version": "1.3.7",
+			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+			"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+			"requires": {
+				"mime-types": "~2.1.24",
+				"negotiator": "0.6.2"
+			}
+		},
+		"array-flatten": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+			"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+		},
+		"base64-arraybuffer": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
+			"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
+		},
+		"base64id": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+			"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
+		},
+		"body-parser": {
+			"version": "1.19.0",
+			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+			"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+			"requires": {
+				"bytes": "3.1.0",
+				"content-type": "~1.0.4",
+				"debug": "2.6.9",
+				"depd": "~1.1.2",
+				"http-errors": "1.7.2",
+				"iconv-lite": "0.4.24",
+				"on-finished": "~2.3.0",
+				"qs": "6.7.0",
+				"raw-body": "2.4.0",
+				"type-is": "~1.6.17"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "2.6.9",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+					"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+					"requires": {
+						"ms": "2.0.0"
+					}
+				},
+				"ms": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+					"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+				}
+			}
+		},
+		"bytes": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+			"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+		},
+		"component-emitter": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+			"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
+		},
+		"content-disposition": {
+			"version": "0.5.3",
+			"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+			"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+			"requires": {
+				"safe-buffer": "5.1.2"
+			}
+		},
+		"content-type": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+			"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+		},
+		"cookie": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+			"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
+		},
+		"cookie-signature": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+			"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+		},
+		"cors": {
+			"version": "2.8.5",
+			"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+			"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+			"requires": {
+				"object-assign": "^4",
+				"vary": "^1"
+			}
+		},
+		"debug": {
+			"version": "4.3.2",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+			"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+			"requires": {
+				"ms": "2.1.2"
+			}
+		},
+		"depd": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+			"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+		},
+		"destroy": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+			"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+		},
+		"ee-first": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+			"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+		},
+		"encodeurl": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+			"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+		},
+		"engine.io": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.1.1.tgz",
+			"integrity": "sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w==",
+			"requires": {
+				"accepts": "~1.3.4",
+				"base64id": "2.0.0",
+				"cookie": "~0.4.1",
+				"cors": "~2.8.5",
+				"debug": "~4.3.1",
+				"engine.io-parser": "~4.0.0",
+				"ws": "~7.4.2"
+			}
+		},
+		"engine.io-parser": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
+			"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
+			"requires": {
+				"base64-arraybuffer": "0.1.4"
+			}
+		},
+		"escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+		},
+		"etag": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+			"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+		},
+		"express": {
+			"version": "4.17.1",
+			"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+			"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+			"requires": {
+				"accepts": "~1.3.7",
+				"array-flatten": "1.1.1",
+				"body-parser": "1.19.0",
+				"content-disposition": "0.5.3",
+				"content-type": "~1.0.4",
+				"cookie": "0.4.0",
+				"cookie-signature": "1.0.6",
+				"debug": "2.6.9",
+				"depd": "~1.1.2",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"finalhandler": "~1.1.2",
+				"fresh": "0.5.2",
+				"merge-descriptors": "1.0.1",
+				"methods": "~1.1.2",
+				"on-finished": "~2.3.0",
+				"parseurl": "~1.3.3",
+				"path-to-regexp": "0.1.7",
+				"proxy-addr": "~2.0.5",
+				"qs": "6.7.0",
+				"range-parser": "~1.2.1",
+				"safe-buffer": "5.1.2",
+				"send": "0.17.1",
+				"serve-static": "1.14.1",
+				"setprototypeof": "1.1.1",
+				"statuses": "~1.5.0",
+				"type-is": "~1.6.18",
+				"utils-merge": "1.0.1",
+				"vary": "~1.1.2"
+			},
+			"dependencies": {
+				"cookie": {
+					"version": "0.4.0",
+					"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+					"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+				},
+				"debug": {
+					"version": "2.6.9",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+					"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+					"requires": {
+						"ms": "2.0.0"
+					}
+				},
+				"ms": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+					"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+				}
+			}
+		},
+		"finalhandler": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+			"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+			"requires": {
+				"debug": "2.6.9",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"on-finished": "~2.3.0",
+				"parseurl": "~1.3.3",
+				"statuses": "~1.5.0",
+				"unpipe": "~1.0.0"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "2.6.9",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+					"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+					"requires": {
+						"ms": "2.0.0"
+					}
+				},
+				"ms": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+					"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+				}
+			}
+		},
+		"forwarded": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+			"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+		},
+		"fresh": {
+			"version": "0.5.2",
+			"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+			"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+		},
+		"http-errors": {
+			"version": "1.7.2",
+			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+			"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+			"requires": {
+				"depd": "~1.1.2",
+				"inherits": "2.0.3",
+				"setprototypeof": "1.1.1",
+				"statuses": ">= 1.5.0 < 2",
+				"toidentifier": "1.0.0"
+			}
+		},
+		"iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"requires": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			}
+		},
+		"inherits": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+			"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+		},
+		"ipaddr.js": {
+			"version": "1.9.1",
+			"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+			"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+		},
+		"media-typer": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+			"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+		},
+		"merge-descriptors": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+			"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+		},
+		"methods": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+			"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+		},
+		"mime": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+			"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+		},
+		"mime-db": {
+			"version": "1.49.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
+			"integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA=="
+		},
+		"mime-types": {
+			"version": "2.1.32",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
+			"integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
+			"requires": {
+				"mime-db": "1.49.0"
+			}
+		},
+		"ms": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+		},
+		"negotiator": {
+			"version": "0.6.2",
+			"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+			"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+		},
+		"object-assign": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+			"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+		},
+		"on-finished": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+			"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+			"requires": {
+				"ee-first": "1.1.1"
+			}
+		},
+		"parseurl": {
+			"version": "1.3.3",
+			"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+			"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+		},
+		"path-to-regexp": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+			"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+		},
+		"proxy-addr": {
+			"version": "2.0.7",
+			"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+			"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+			"requires": {
+				"forwarded": "0.2.0",
+				"ipaddr.js": "1.9.1"
+			}
+		},
+		"qs": {
+			"version": "6.7.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+			"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+		},
+		"range-parser": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+			"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+		},
+		"raw-body": {
+			"version": "2.4.0",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+			"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+			"requires": {
+				"bytes": "3.1.0",
+				"http-errors": "1.7.2",
+				"iconv-lite": "0.4.24",
+				"unpipe": "1.0.0"
+			}
+		},
+		"safe-buffer": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+		},
+		"safer-buffer": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+		},
+		"send": {
+			"version": "0.17.1",
+			"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+			"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+			"requires": {
+				"debug": "2.6.9",
+				"depd": "~1.1.2",
+				"destroy": "~1.0.4",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"fresh": "0.5.2",
+				"http-errors": "~1.7.2",
+				"mime": "1.6.0",
+				"ms": "2.1.1",
+				"on-finished": "~2.3.0",
+				"range-parser": "~1.2.1",
+				"statuses": "~1.5.0"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "2.6.9",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+					"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+					"requires": {
+						"ms": "2.0.0"
+					},
+					"dependencies": {
+						"ms": {
+							"version": "2.0.0",
+							"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+							"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+						}
+					}
+				},
+				"ms": {
+					"version": "2.1.1",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+					"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+				}
+			}
+		},
+		"serve-static": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+			"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+			"requires": {
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"parseurl": "~1.3.3",
+				"send": "0.17.1"
+			}
+		},
+		"setprototypeof": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+			"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+		},
+		"socket.io": {
+			"version": "4.1.3",
+			"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz",
+			"integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==",
+			"requires": {
+				"@types/cookie": "^0.4.0",
+				"@types/cors": "^2.8.10",
+				"@types/node": ">=10.0.0",
+				"accepts": "~1.3.4",
+				"base64id": "~2.0.0",
+				"debug": "~4.3.1",
+				"engine.io": "~5.1.1",
+				"socket.io-adapter": "~2.3.1",
+				"socket.io-parser": "~4.0.4"
+			}
+		},
+		"socket.io-adapter": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.1.tgz",
+			"integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw=="
+		},
+		"socket.io-parser": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
+			"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
+			"requires": {
+				"@types/component-emitter": "^1.2.10",
+				"component-emitter": "~1.3.0",
+				"debug": "~4.3.1"
+			}
+		},
+		"statuses": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+			"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+		},
+		"toidentifier": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+			"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+		},
+		"type-is": {
+			"version": "1.6.18",
+			"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+			"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+			"requires": {
+				"media-typer": "0.3.0",
+				"mime-types": "~2.1.24"
+			}
+		},
+		"unpipe": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+			"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+		},
+		"utils-merge": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+			"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+		},
+		"vary": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+			"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+		},
+		"ws": {
+			"version": "7.4.6",
+			"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+			"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
+		}
+	}
+}

+ 12 - 0
package.json

@@ -0,0 +1,12 @@
+{
+	"name": "Chat",
+	"version": "0.0.1",
+	"description": "",
+	"dependencies": {
+		"express": "^4.15.2",
+		"socket.io": "^4.1.3"
+	},
+	"scripts": {
+		"start": "node index.js"
+	}
+}

+ 39 - 0
ssl/ca_bundle.crt

@@ -0,0 +1,39 @@
+-----BEGIN CERTIFICATE-----
+MIIG1TCCBL2gAwIBAgIQbFWr29AHksedBwzYEZ7WvzANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjAw
+MTMwMDAwMDAwWhcNMzAwMTI5MjM1OTU5WjBLMQswCQYDVQQGEwJBVDEQMA4GA1UE
+ChMHWmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBSU0EgRG9tYWluIFNlY3VyZSBT
+aXRlIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAhmlzfqO1Mdgj
+4W3dpBPTVBX1AuvcAyG1fl0dUnw/MeueCWzRWTheZ35LVo91kLI3DDVaZKW+TBAs
+JBjEbYmMwcWSTWYCg5334SF0+ctDAsFxsX+rTDh9kSrG/4mp6OShubLaEIUJiZo4
+t873TuSd0Wj5DWt3DtpAG8T35l/v+xrN8ub8PSSoX5Vkgw+jWf4KQtNvUFLDq8mF
+WhUnPL6jHAADXpvs4lTNYwOtx9yQtbpxwSt7QJY1+ICrmRJB6BuKRt/jfDJF9Jsc
+RQVlHIxQdKAJl7oaVnXgDkqtk2qddd3kCDXd74gv813G91z7CjsGyJ93oJIlNS3U
+gFbD6V54JMgZ3rSmotYbz98oZxX7MKbtCm1aJ/q+hTv2YK1yMxrnfcieKmOYBbFD
+hnW5O6RMA703dBK92j6XRN2EttLkQuujZgy+jXRKtaWMIlkNkWJmOiHmErQngHvt
+iNkIcjJumq1ddFX4iaTI40a6zgvIBtxFeDs2RfcaH73er7ctNUUqgQT5rFgJhMmF
+x76rQgB5OZUkodb5k2ex7P+Gu4J86bS15094UuYcV09hVeknmTh5Ex9CBKipLS2W
+2wKBakf+aVYnNCU6S0nASqt2xrZpGC1v7v6DhuepyyJtn3qSV2PoBiU5Sql+aARp
+wUibQMGm44gjyNDqDlVp+ShLQlUH9x8CAwEAAaOCAXUwggFxMB8GA1UdIwQYMBaA
+FFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBTI2XhootkZaNU9ct5fCj7c
+tYaGpjAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUE
+FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQIC
+TjAIBgZngQwBAgEwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1
+c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYG
+CCsGAQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3Qu
+Y29tL1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRw
+Oi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQAVDwoIzQDV
+ercT0eYqZjBNJ8VNWwVFlQOtZERqn5iWnEVaLZZdzxlbvz2Fx0ExUNuUEgYkIVM4
+YocKkCQ7hO5noicoq/DrEYH5IuNcuW1I8JJZ9DLuB1fYvIHlZ2JG46iNbVKA3ygA
+Ez86RvDQlt2C494qqPVItRjrz9YlJEGT0DrttyApq0YLFDzf+Z1pkMhh7c+7fXeJ
+qmIhfJpduKc8HEQkYQQShen426S3H0JrIAbKcBCiyYFuOhfyvuwVCFDfFvrjADjd
+4jX1uQXd161IyFRbm89s2Oj5oU1wDYz5sx+hoCuh6lSs+/uPuWomIq3y1GDFNafW
++LsHBU16lQo5Q2yh25laQsKRgyPmMpHJ98edm6y2sHUabASmRHxvGiuwwE25aDU0
+2SAeepyImJ2CzB80YG7WxlynHqNhpE7xfC7PzQlLgmfEHdU+tHFeQazRQnrFkW2W
+kqRGIq7cKRnyypvjPMkjeiV9lRdAM9fSJvsB3svUuu1coIG1xxI1yegoGM4r5QP4
+RGIVvYaiI76C0djoSbQ/dkIUUXQuB8AL5jyH34g3BZaaXyvpmnV4ilppMXVAnAYG
+ON51WhJ6W0xNdNJwzYASZYH+tmCWI+N60Gv2NNMGHwMZ7e9bXgzUCZH5FaBFDGR5
+S9VWqHB73Q+OyIVvIbKYcSc2w/aSuFKGSA==
+-----END CERTIFICATE-----

+ 37 - 0
ssl/certificate.crt

@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----

+ 27 - 0
ssl/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAkD/CPeVao+ij8jvyW0MkczqTf6fPG1rFwuA/nyE8sIyC6I3I
+LNuZhTlMYBbXlbgCdhzpnqgE+5J8ZvVr/0rMaKScZ2XFMKJm3a24ctOA6dPkdBWV
+y3UMSZuN8jitziCTMS5UlwQLJAKI11JZzIfcc5rWuSfGEaknP9bBnaxQ34Q1fphe
+huKcO3ezuvfdYrBBJO329HN7KNpwSkHoRNlVLbwe5Mon7yRs8Sh4ARTl2d8VDDOo
+YNe49iBDAsyLUyq8NezTrafVAtzraHDlRYXVMQD4oPZXVH6OT02xfpRaIH0fQJ4I
+whXRL6Xk15Vb3bzQBbPJYOdoR4jgpxcpKJyuQwIDAQABAoIBAH4WnW2JO8+mnRgy
+ekh3yjbG7wNY5dodYFxVtIcegHQ6fntU47MCSZGAYlhj3xJKBCzGXReH+sMEaqV8
+xWgkM8UMjoJ7HQDEFHKVVXNZmYfK5hjqfUOZDqKQzGT8UkCpjMAipWJT8IELjh3Z
+KBF2eKa8pBC4yZPKbjqJODjg1Nhq4jUiybS4XW6+cWJhd8pp34LVVO8+bphCzFg3
+KOH41k+XUSM+rsycStJoCYbAMVHJX4fC4jdUTxdxLhfqA+TJaifEHVpXzEZoiglK
+3dskJZVpXlA2AnDFYI0rHwDcuzD+mSepUKVRNG72054YIscxQ/k+nktqLIDWvYJE
+LiIG3iECgYEAyCek4eqzGhF6d4mT3C2Rt6XZgBWjFNPp8lx5z3uyU0+lYkAsBUnT
+AbYSfjRzR+RKydEY+p0JCWNK8GmrmnjeJQvOWOJYxpy8ZP3J7Cg/bKTzyIr/PnG4
+fXn2xQBBoYdvs12v2Tui+HA1FmXLLoWFJLk9DRiBnaXNiirEg0Zq5zkCgYEAuH71
+2fl+cdtbTYvMuddY7UXSY73GSHfX8AWX+Mi3NuIvFGYgcgz4uXxDla+F1GX8CSiz
+J46HJbZHVTiOqzLGk2M4YoI2Ph/2keb2IN5qs2bLmI82H1FC4e0kUXrGrWsK5DQS
+qJ45LGKeMGM9jIGgDiMv8VYYzaXtSeENFbN7ZVsCgYEAqjD7hJX6wNnH1skHDxs4
+Yn4FmWHMj7M5pDl54jD+CtUYfZivVbfWUggtZV7X/3NhHIZNxRuuSWtCl3Zi0jCg
+Q8PsK6wbbJZtozohbksy0wDXwdhe/QvZoegJKq3zIJR3KH8rPX32L2XJ3kekIuSp
+t/ZCsVX7ML+BLFD9U9qWoBECgYA9HSk5PDELbBsxc8asJM57Qm6vxXRCGxi3lFLE
+AVDXaFMqEa5buTpGzwfgNJVDR0kWi5nU15yi/F9itmpkAVzQA8TwtKtdJt9Zc0VC
+nAqCROHaNk46T4O1LQWjy+S2G0gvUaSAoHDV6BD4fFcuDN5E8Jj3+4oZzQXuPBNG
+hf6xEwKBgQCeJGc8suKhRZn4RYZ5EmHrla2+yWnYKeGDF0BQfqdkVfaVNIDj5pTG
+Hm88UEO0pPzZba1qCqRxKAFvEZFkLyCxFMEVayOpyYXLiC1iZLxdXCzqRGunnIZl
+bSMLVSuCZZXND0JohtMp0JUuf7lL+nQrknOlbC0QpPluYCkvMmFF9Q==
+-----END RSA PRIVATE KEY-----

+ 142 - 0
www/css/app.css

@@ -0,0 +1,142 @@
+#chat-host ul {
+    margin: 0;
+    padding: 0;
+}
+
+#chat-host ul li {
+    list-style-type: none;
+}
+
+#chat-host input, #chat-host textarea, #chat-host button {
+    font-family: inherit;
+    font-size: inherit;
+    border: none;
+}
+
+#chat-host input, #chat-host textarea {
+    background-color: #ffffff;
+}
+
+#chat-host button {
+    cursor: pointer;
+}
+
+
+
+#chat-host {
+    position: fixed;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: stretch;
+
+    padding: 1em;
+}
+
+#chat-host .login {
+    align-self: center;
+    display: flex;
+    flex-direction: column;
+}
+
+#chat-host .login .loginError {
+    margin-bottom: 2em;
+
+    font-weight: bold;
+    color: #ad2424;
+}
+
+#chat-host .login #chat-login-form {
+    display: flex;
+    flex-direction: column;
+}
+
+#chat-host .login #chat-login-form .inputWrapper {
+    display: flex;
+    margin: 0.5em;
+}
+
+#chat-host .login #chat-login-form .inputWrapper label {
+    width: 10em;
+    margin-right: 1em;
+}
+
+#chat-host .login #chat-login-form .inputWrapper input {
+    flex-grow: 1;
+}
+
+#chat-host .login #chat-login-form button {
+    margin-top: 2em;
+}
+
+#chat-host .main {
+    flex-grow: 1;
+    display: flex;
+    overflow: hidden;
+}
+
+#chat-host .messages, #chat-host .userlistWrapper {
+    padding: 0.3em;
+    flex-grow: 1;
+}
+
+#chat-host .messages {
+    flex-basis: 80%;
+    overflow: auto;
+}
+
+#chat-host .messages .systemMsg {
+    color: #666666;
+    font-size: 85%;
+    font-style: italic;
+}
+
+#chat-host .messages .userMsg {
+    display: flex;
+    align-items: baseline;
+}
+
+#chat-host .messages .userMsg .user {
+    margin-right: 0.3em;
+    font-weight: bold;
+}
+
+#chat-host .messages .userMsg .time {
+    margin-right: 0.3em;
+    font-size: 85%;
+    font-style: italic;
+}
+
+#chat-host .messages .userMsg .message {
+    white-space: pre-wrap;
+}
+
+#chat-host .userlistWrapper {
+    text-align: right;
+    flex-basis: 20%;
+    min-width: 8em;
+}
+
+#chat-host .userlistWrapper button {
+    width: 2em;
+    height: 2em;
+    padding: 0.3em;
+    margin-left: 0.5em;
+    border-radius: 50%;
+}
+
+#chat-host .messageForm {
+    display: flex;
+    margin-top: 1em;
+}
+
+#chat-host .messageForm textarea {
+    flex-grow: 1;
+    margin-right: 1em;
+    resize: none;
+}

+ 30 - 0
www/css/theme.css

@@ -0,0 +1,30 @@
+/* Theme */
+
+#chat-host {
+    background-color: #e2e2e2;
+    font-family: "Ubuntu", sans-serif;
+    font-size: 13px;
+}
+
+#chat-host .main .messages {
+    border: none;
+    margin-right: 1em;
+    background-color: #f8f8f8;
+}
+
+#chat-host .main .userlistWrapper {
+    border-left: 1px solid #d0d0d0;
+    padding-left: 1em;
+}
+
+#chat-host button {
+    background-color: #751b09;
+    color: #ffffff;
+    padding: 1em;
+}
+
+#chat-host h2 {
+    font-size: inherit;
+    text-transform: uppercase;
+    color: #4d4d4d;
+}

+ 76 - 0
www/index.html

@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <title>Chat</title>
+
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <link rel="stylesheet" type="text/css" href="css/app.css" />
+    <link rel="stylesheet" type="text/css" href="css/theme.css" />
+</head>
+
+<script src="/socket.io/socket.io.js" type="application/javascript"></script>
+<script src="https://code.jquery.com/jquery-1.11.1.js" type="application/javascript"></script>
+
+<script src="js/main.js" type="application/javascript"></script>
+
+    <body>
+        <div id="chat-host"></div>
+    </body>
+
+    <script id="chat-login-template" type="text/x-custom-template">
+        <div class="login">
+            <span class="loginError"></span>
+            <form id="chat-login-form" action="">
+                <div class="inputWrapper usernameWrapper">
+                    <label>Nickname:</label>
+                    <input class="username" autocomplete="off" />
+                </div>
+                <div class="inputWrapper passwordWrapper" style="display: none">
+                    <label>Passwort:</label>
+                    <input class="password" type="password" autocomplete="off" />
+                </div>
+                <button>Chat betreten</button>
+            </form>
+        </div>
+    </script>
+
+    <script id="chat-template" type="text/x-custom-template">
+        <div class="main">
+            <ul class="messages"></ul>
+            <div class="userlistWrapper">
+                <h2>Benutzer im Chat</h2>
+                <ul class="userlist"></ul>
+            </div>
+        </div>
+
+        <form class="messageForm" action="">
+            <textarea class="messageField" />
+            <button>Abschicken</button>
+        </form>
+    </script>
+
+    <script id="chat-admin-template" type="text/x-custom-template">
+        <div class="main">
+            <ul class="messages"></ul>
+            <div class="userlistWrapper">
+                <h2>Benutzer im Chat</h2>
+                <ul class="userlist"></ul>
+                <h2>Gesperrte IPs</h2>
+                <ul class="bannedlist"></ul>
+            </div>
+        </div>
+
+        <form class="messageForm" action="">
+            <textarea class="messageField" />
+            <button>Abschicken</button>
+        </form>
+    </script>
+
+    <script id="chat-kick-template" type="text/x-custom-template">
+        <div class="login">
+            <div class="loginError">Sie wurden aus dem Chat entfernt.</div>
+        </div>
+    </script>
+</html>

+ 206 - 0
www/js/main.js

@@ -0,0 +1,206 @@
+const socket = io();
+
+$(() => {
+    socket.on('serverHandshake', onServerHandshake);
+    socket.on('serverLogin', onServerLogin);
+    socket.on('serverKick', onServerKick);
+});
+
+function onServerHandshake() {
+    resetChatListeners();
+    resetLoginListeners();
+
+    var template = $('#chat-login-template').html();
+    $('#chat-host').empty();
+    $('#chat-host').append(template);
+
+    $('#chat-login-form').submit((e) => {
+        e.preventDefault();
+
+        console.debug('submit login form', e);
+
+        socket.emit('login', $('#chat-login-form .username').val(), $('#chat-login-form .password').val());
+
+        return false;
+    });
+
+    socket.on('usernameInvalid', () => {
+        $('#chat-login-form .username').focus();
+        $('#chat-host .loginError').text('Username invalid.');
+    });
+
+    socket.on('usernameTaken', () => {
+        $('#chat-login-form .username').focus();
+        $('#chat-host .loginError').text('Username already in use.');
+    });
+
+    socket.on('passwordRequired', () => {
+        $('#chat-login-form .passwordWrapper').css('display', '');
+        $('#chat-login-form .password').focus();
+        $('#chat-host .loginError').text('Für diesen reservierten Benutzer ist ein Passwort erforderlich.');
+    });
+
+    socket.on('passwordWrong', () => {
+        $('#chat-login-form .passwordWrapper').css('display', '');
+        $('#chat-login-form .password').focus();
+        $('#chat-host .loginError').text('Das eingegebene Passwort ist ungültig.');
+    });
+}
+
+function onServerLogin(user) {
+    resetChatListeners();
+    resetLoginListeners();
+
+    var template;
+    if (user.admin) {
+        template = $('#chat-admin-template').html()
+    } else {
+        template = $('#chat-template').html()
+    }
+
+    $('#chat-host').empty();
+    $('#chat-host').append(template);
+
+    $('#chat-host .messageField').focus();
+
+    socket.on('usersUpdated', (userList) => {
+        $('#chat-host .main .userlist').empty();
+
+        userList.sort((userA, userB) => userA.name.localeCompare(userB.name));
+        userList.sort((userA, userB) => {
+            if (userA.admin && !userB.admin) {
+                return Number.MIN_SAFE_INTEGER;
+            } else if (!userA.admin && userB.admin) {
+                return Number.MAX_SAFE_INTEGER;
+            }
+
+            return 0;
+        });
+
+        for (let listUser of userList) {
+            let userString = listUser.name;
+            if (listUser.admin) {
+                userString = '👑 ' + userString;
+            }
+
+            const userListEl = $('<li>');
+            userListEl.text(userString);
+            if (user.admin) {
+                const kickBtn = $('<button class="kickBtn">🥾</button>');
+                kickBtn.click(() => onKickUser(listUser));
+                userListEl.append(kickBtn);
+            }
+            $('#chat-host .main .userlist').append(userListEl);
+        }
+    });
+
+    if (user.admin) {
+        socket.on('bannedUpdated', (bannedList) => {
+            $('#chat-host .main .bannedlist').empty();
+
+            for (let bannedItem of bannedList) {
+                const bannedListEl = $('<li>');
+
+                bannedListEl.text(bannedItem.ip + ' (' + bannedItem.user.name + ')');
+
+                const removeBtn = $('<button class="removeBtn">✖</button>');
+                removeBtn.click(() => onLiftBan(bannedItem.ip));
+                bannedListEl.append(removeBtn);
+
+                $('#chat-host .main .bannedlist').append(bannedListEl);
+            }
+        });
+    }
+
+    socket.on('message', (msg, user, timestamp) => {
+        const messageWrapper = $('<li class="userMsg">');
+
+        const timeEl = $('<span class="time">');
+        timeEl.text(new Date(timestamp).toLocaleTimeString());
+        timeEl.prop('title', new Date(timestamp).toLocaleString());
+        messageWrapper.append(timeEl);
+
+        const userEl = $('<span class="user">');
+        let userString = user.name + ':';
+        if (user.admin) {
+            userString = '👑 ' + userString;
+        }
+        userEl.text(userString);
+        messageWrapper.append(userEl);
+
+        const messageEl = $('<span class="message">');
+        messageEl.text(msg)
+        messageWrapper.append(messageEl);
+
+        appendMessage(messageWrapper);
+    });
+
+    socket.on('userJoined', (msg) => {
+        appendMessage($('<li class="systemMsg user joined">').text(msg + ' hat den Chat betreten.'));
+    });
+
+    socket.on('userLeft', (msg) => {
+        appendMessage($('<li class="systemMsg user left">').text(msg + ' hat den Chat verlassen.'));
+    });
+
+    $('#chat-host .messageForm').submit((e) => {
+        e.preventDefault();
+        socket.emit('message', $('#chat-host .messageForm .messageField').val());
+        $('#chat-host .messageForm .messageField').val('');
+        $('#chat-host .messageForm .messageField').focus();
+        return false;
+    });
+
+    $("#chat-host .messageField").keypress((e) => {
+        if(e.key == 'Enter' && !e.shiftKey) {
+            e.preventDefault();
+            $('#chat-host .messageForm').submit();
+            return false;
+        }
+    });
+}
+
+function onServerKick() {
+    resetChatListeners();
+    resetLoginListeners();
+
+    var template = $('#chat-kick-template').html();
+    $('#chat-host').empty();
+    $('#chat-host').append(template);
+}
+
+function resetChatListeners() {
+    socket.removeAllListeners('usersUpdated');
+    socket.removeAllListeners('message');
+    socket.removeAllListeners('userJoined');
+    socket.removeAllListeners('userLeft');
+}
+
+function resetLoginListeners() {
+    socket.removeAllListeners('usernameInvalid');
+    socket.removeAllListeners('usernameTaken');
+    socket.removeAllListeners('passwordRequired');
+    socket.removeAllListeners('passwordWrong');
+}
+
+function appendMessage(element) {
+    const messagesListEl = $('#chat-host .main .messages')[0];
+    const scrollMax = messagesListEl.scrollHeight - messagesListEl.clientHeight;
+    const scrolled = messagesListEl.scrollTop != scrollMax;
+
+    $('#chat-host .main .messages').append(element);
+
+    if (!scrolled) {
+        messagesListEl.scrollTop = messagesListEl.scrollHeight - messagesListEl.clientHeight;
+    }
+}
+
+function onKickUser(user) {
+    if (confirm('Möchten Sie den Benutzer "' + user.name + '" wirklich bannen?\nDies wird sich auf alle Benutzer mit der gleichen IP-Adresse auswirken.')) {
+        socket.emit('requestKick', user);
+    }
+}
+
+function onLiftBan(ip) {
+    socket.emit('requestLiftBan', ip);
+}