diff --git a/classes/extension.js b/classes/extension.js index 1bae5dc..e2320d5 100644 --- a/classes/extension.js +++ b/classes/extension.js @@ -124,4 +124,29 @@ module.exports = class Extension { res.writeHead(200, args) return res.end(data) } + + set_cookie(key, value, secure=false) { + if (secure) + return this.cookie.serialize( + key, + value, { + secure: true, + httpOnly: true + } + ) + else + return this.cookie.serialize( + key, + value + ) + } + + del_cookie(key) { + return this.cookie.serialize( + key, + '', { + expires: new Date(1) + } + ) + } } diff --git a/config/config.js.example b/config/config.js.example index b12a7d3..6cd4ef2 100644 --- a/config/config.js.example +++ b/config/config.js.example @@ -1,4 +1,5 @@ const {tmpdir} = require('os') + /** Whether the server is running behind an nginx instance */ exports.nginx = false /** The domain of the instance. */ @@ -12,7 +13,9 @@ exports.salt = "Password salt here!" /** Location of the temporary directory. Defaults to your system's default temp dir */ exports.tmp_dir = tmpdir() /** The location where the dicebear instance is hosted */ -exports.dicebear_host = "api.dicebear.com" +exports.dicebear_host = "https://api.dicebear.com/7.x/lorelei/svg" +/** The location where the client files are hosted */ +exports.client_location = "https://github.com/keukeiland/keuknet-client/releases/latest/download/" /** Whether connections are logged */ exports.logging = false diff --git a/index.js b/index.js index 1638ec8..7230bce 100644 --- a/index.js +++ b/index.js @@ -3,9 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// import http servers -const http2 = require('http2') -const http = require('http') +const cookie = require('cookie') // enable use of dotenv require('dotenv').config() @@ -17,6 +15,7 @@ let texts = require('./config/texts') // set up global context const global = require('./global') +global.cookie = cookie global.config = config global.wg_config = wg_config global.texts = texts @@ -30,9 +29,7 @@ log.init(config.logging) fetch.init(`${__dirname}/www/static/`) log.status("Initializing database") data.init(`${__dirname}/data/`, config.salt, function (err) { - if (err) { - log.err(err) - } + if (err) log.err(err) log.status("Database initialized") // set up request handler handle.init(Object.freeze(global)) @@ -45,21 +42,26 @@ const requestListener = function (req, res) { req.ip = process.env.IP } - // if no authorization headers set it to false, to prevent errors - req.headers.authorization ??= false + req.cookies = cookie.parse(req.headers.cookie || '') + // get authorization info + req.headers.authorization ??= req.cookies.auth // get requested host, HTTP/<=1.1 uses host, HTTP/>=2 uses :authority req.headers.host ??= req.headers[':authority'] - // set user agent to "NULL", to prevent errors - req.headers['user-agent'] ??= "NULL" - // make sure cookie is defined - if (isNaN(req.headers.cookie)) req.headers.cookie = 0 - // get requesting IP - req.ip ??= req.headers['x-real-ip'] || req.socket?.remoteAddress || req.connection?.remoteAddress || req.connection.socket?.remoteAddress - - // if request is not for any domain served here, act like server isn't here - if (req.headers.host != config.domain) { - log.con_err(req) - return + + // If standalone + if (!config.nginx) { + // get requesting IP + req.ip ??= req.socket?.remoteAddress || req.connection?.remoteAddress || req.connection.socket?.remoteAddress + + // if request is not for any domain served here, act like server isn't here + if (req.headers.host != config.domain) { + log.con_err(req) + return + } + // If behind NGINX + } else { + // get requesting IP + req.ip = req.headers['x-real-ip'] || '0.0.0.0' } // separate url arguments from the url itself @@ -109,39 +111,52 @@ const requestListener = function (req, res) { } } - -// Handle insecure HTTP requests -const insecureRequestListener = function (req, res) { - // redirect request to HTTPS +// Redirect requests to HTTPS +const httpsRedirect = function (req, res) { res.writeHead(307, {"Location": `https://${req.headers.host}${req.url}`}) res.end() } -if (!config.nginx) { - // fetch https encryption keys - log.status("Fetching encryption keys") - fetch.key(config.private_key_path, function(private_key, err) { - if (err) log.err("Failed fetching private key") - fetch.key(config.server_cert_path, function(server_cert, err) { - if (err) log.err("Failed fetching server certificate") - fetch.key(config.ca_cert_path, function(ca_cert, err) { - if (err) log.err("Failed fetching CA certificate") - log.status("Encryption keys fetched") - http2.createSecureServer({ - key: private_key, - cert: server_cert, - ca: ca_cert, - allowHTTP1: true, - }, requestListener) - .listen(config.https_port, config.host, () => { - console.log(`\x1b[1mHTTP/2 & HTTPS server running on https://${config.domain}:${config.https_port}, interface '${config.host}'\n\x1b[0m`) - }) + +function startServer(http, https) { + if (https) { + log.status("Fetching encryption keys") + // Private key + fetch.key(config.private_key_path, function(key, err) { + if (err) log.err("Failed fetching private key") + // Certificate + fetch.key(config.server_cert_path, function(cert, err) { + if (err) log.err("Failed fetching server certificate") + // Certificate chain + fetch.key(config.ca_cert_path, function(ca, err) { + if (err) log.err("Failed fetching CA certificate") + log.status("Encryption keys fetched") + // Start server + require('http2').createSecureServer({ + key, + cert, + ca, + allowHTTP1: true, + }, requestListener).listen( + config.https_port, + config.host, + () => log.serverStart("https", config.domain, config.host, config.https_port) + ) + }) }) }) - }) + } + if (http) { + // Start server + require('http').createServer( + https ? httpsRedirect : requestListener + ).listen( + config.http_port, + config.host, + () => log.serverStart("http", config.domain, config.host, config.http_port) + ) + } } -// Start HTTP server -http.createServer(config.nginx ? requestListener : insecureRequestListener) - .listen(config.http_port, config.host, () => { - console.log(`\x1b[1mHTTP/1.1 server running on http://${config.domain}:${config.http_port}, interface '${config.host}'\n\x1b[0m`) - }) + +startServer(true, !config.nginx) + \ No newline at end of file diff --git a/modules/data.js b/modules/data.js index 08eff17..2df2fd2 100644 --- a/modules/data.js +++ b/modules/data.js @@ -14,6 +14,11 @@ exports.init = function(path, saltq, callback) { } exports.db = () => {return db} +function __hash_pw(password) { + if (!password) return + return crypto.pbkdf2Sync(password, salt, 10000, 128, 'sha512').toString('base64') +} + function __decrypt_auth(auth, callback) { if (!auth) { return callback(undefined, undefined, new Error("Quit early")) @@ -25,9 +30,13 @@ function __decrypt_auth(auth, callback) { return callback(undefined, undefined, new Error("Missing name or password")) } [name, password] = data.split(":") + // hash password - hash = crypto.pbkdf2Sync(password, salt, 10000, 128, 'sha512').toString('base64') - return callback(name, hash) + password = __hash_pw(password) + if (!password) + return callback(undefined, undefined, new Error("Missing name or password")) + + return callback(name, password) } function __exists(name, callback) { @@ -38,42 +47,49 @@ function __exists(name, callback) { } -exports.addUser = function (auth, callback) { - __decrypt_auth(auth, function (name, password, err) { - if (err) return callback(err) +exports.addUser = function (name, password, callback) { + if (name && password) { + password = __hash_pw(password) // Check if username is already taken __exists(name, function (exists, err) { if (err) return callback(err) if (exists) return callback(new Error("Username already taken")) // add user to db - db.run("INSERT INTO user(name,password) VALUES($name,$password)", [name, password], function (err) { + db.run("INSERT INTO user(name,password,pfp_code) VALUES($name,$password,$pfp_code)", [name, password, 'seed='+name], function (err) { return callback(err) }) }) - }) + } else { + return callback(new Error("Missing name or password")) + } } + exports.authenticate = function (auth, ip, ip_scope, callback) { - // Try to get name and password - __decrypt_auth(auth, function (name, password, err) { - if (err) { - if (ip.startsWith(ip_scope)) { - // Try using IP-address if no name and password - db.get("SELECT u.* FROM user u JOIN profile p ON p.user_id = u.id WHERE p.ip=$ip", ip, function (err, user) { - return callback(user, err) - }) - return - } - return callback(undefined, err) - } - // Auth using name and password - db.get("SELECT * FROM user WHERE name=$name", name, function (err, user) { - if (user) { - if (password == user.password) { - return callback(user, err) + if (auth) { + // Try to get name and password + __decrypt_auth(auth, function (name, password, err) { + if (err) { + if (ip.startsWith(ip_scope)) { + // Try using IP-address if no name and password + db.get("SELECT u.* FROM user u JOIN _profile_device p ON p.user_id = u.id WHERE p.ip=$ip", ip, function (err, user) { + return callback(user, err) + }) + return } + return callback(undefined, err) } - return callback(undefined, err) + // Auth using name and password + db.get("SELECT * FROM user WHERE name=$name", name, function (err, user) { + if (user) { + if (password == user.password) { + return callback(user, err) + } + } + return callback(undefined, new Error("Wrong name or password")) + }) }) - }) + } else { + return callback(undefined, null) + } } diff --git a/modules/log.js b/modules/log.js index 7ee7852..82e58d8 100644 --- a/modules/log.js +++ b/modules/log.js @@ -6,7 +6,7 @@ exports.init = function(we_log) { function __mask_ip(ip) { let tmp = "" // if IPv4 - if (ip.startsWith("::ffff:")) { + if (ip.includes('.')) { // strip 4to6 prefix ip = ip.substring(ip.lastIndexOf(':')+1,ip.length) // mask ip @@ -37,7 +37,7 @@ exports.con = function(req) { url = __mask_url(req.url) console.log( `\x1b[32m [${ip}]=>'${req.method} ${url} - HTTP/${req.httpVersion} ${req.headers['user-agent'].split(" ",1)[0]} ${req.headers.authorization? "auth" : "noauth"}\x1b[0m` + HTTP/${req.httpVersion} ${(req.headers['user-agent'] ?? "NULL").split(" ",1)[0]} ${req.headers.authorization? "auth" : "noauth"}\x1b[0m` ) } } @@ -58,3 +58,7 @@ exports.status = function(msg) { exports.err = function(err) { console.log(`\x1b[31m>> ${err}\x1b[0m`) } + +exports.serverStart = function(type, domain, host, port) { + console.log(`\x1b[1m${type.toUpperCase()} server running on ${type}://${domain}:${port}, interface '${host}'\n\x1b[0m`) +} diff --git a/package-lock.json b/package-lock.json index 31b7fab..6cc112c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "keuknet", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "keuknet", - "version": "2.3.0", + "version": "2.4.0", "license": "MPL-2.0", "dependencies": { + "cookie": "^0.6.0", "dotenv": "^16.4.5", "nunjucks": "^3.2.4", "sqlite3": "^5.1.6" @@ -176,12 +177,15 @@ ] }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "devOptional": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bindings": { @@ -346,6 +350,14 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "optional": true }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -392,9 +404,9 @@ "optional": true }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "engines": { "node": ">=8" } @@ -795,6 +807,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -985,9 +998,9 @@ } }, "node_modules/node-abi": { - "version": "3.54.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", - "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", "dependencies": { "semver": "^7.3.5" }, @@ -1163,9 +1176,9 @@ } }, "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -1310,12 +1323,9 @@ "optional": true }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -1401,9 +1411,9 @@ } }, "node_modules/socks": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", - "integrity": "sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "optional": true, "dependencies": { "ip-address": "^9.0.5", @@ -1524,9 +1534,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", diff --git a/package.json b/package.json index 12cc83f..ed7e7b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keuknet", - "version": "2.3.0", + "version": "2.4.0", "description": "A webserver and client program for easily managing a WireGuard-network in a client-server setting.", "main": "index.js", "scripts": { @@ -10,6 +10,7 @@ "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", "dependencies": { + "cookie": "^0.6.0", "dotenv": "^16.4.5", "nunjucks": "^3.2.4", "sqlite3": "^5.1.6" diff --git a/www/extensions/chat/index.html b/www/extensions/chat/index.html index 88f3093..e96ed42 100644 --- a/www/extensions/chat/index.html +++ b/www/extensions/chat/index.html @@ -32,7 +32,7 @@ data = http.response.messages; for (var i=0; i'; + row.insertCell(-1).innerHTML = ''; row.insertCell(-1).appendChild(document.createTextNode(data[i].user.name)); row.insertCell(-1).appendChild(document.createTextNode(data[i].time)); row.insertCell(-1).appendChild(document.createTextNode(data[i].content)); @@ -49,7 +49,7 @@ {% for msg in chat %} - + diff --git a/www/extensions/profile/devices/linux.html b/www/extensions/profile/devices/linux.html index 44c0c56..5ef61b3 100644 --- a/www/extensions/profile/devices/linux.html +++ b/www/extensions/profile/devices/linux.html @@ -3,7 +3,7 @@

Install guide for Linux.

Installation

If you havent yet, install the KeukNet client on Linux:
- please run curl https://{{host}}/keuknet-client/installer.py -o /tmp/installer.py && chmod +x /tmp/installer.py && sudo /tmp/installer.py in the terminal. + please run curl {{client_location}}installer.py -o /tmp/installer.py && chmod +x /tmp/installer.py && sudo /tmp/installer.py in the terminal.

If you feel uncomfortable running it like that you're free to download it normally and inspect the code, it's just a simple Python script. diff --git a/www/extensions/profile/devices/windows.html b/www/extensions/profile/devices/windows.html index 525ce7e..561f7cc 100644 --- a/www/extensions/profile/devices/windows.html +++ b/www/extensions/profile/devices/windows.html @@ -20,7 +20,7 @@

Installation

- cmd.exe /c curl -o "%TEMP%\installer.exe" "https://{{host}}/keuknet-client/installer.exe" && "%TEMP%\installer.exe" + cmd.exe /c curl -o "%TEMP%\installer.exe" "{{client_location}}installer.exe" && "%TEMP%\installer.exe"

diff --git a/www/extensions/profile/index.html b/www/extensions/profile/index.html index 5cffbfc..c1e57f1 100644 --- a/www/extensions/profile/index.html +++ b/www/extensions/profile/index.html @@ -6,7 +6,7 @@ {% block body %}

-

Status: {{ "connected" if connected else "disconnected" }}

+

Status: {{ "connected" if connected_ip else "disconnected" }}

Connect Disconnect
@@ -24,10 +24,13 @@

Your devices

{% else %} Device {{loop.index}} {% endif %} + {% if p.ip == connected_ip %} + (Current device) + {% endif %}

{{p.uuid}}

IP: {{p.ip}}

- Install + InstallRenameDelete
diff --git a/www/extensions/profile/index.js b/www/extensions/profile/index.js index 29c3a56..9df6c3d 100644 --- a/www/extensions/profile/index.js +++ b/www/extensions/profile/index.js @@ -5,10 +5,12 @@ module.exports = (Extension) => {return class extends Extension { dependencies = ['content','nj','fetch'] crypto = require('crypto') wg = require('./wireguard') + wg_config = null constructor (global, path, data_path) { super(global, path) this.wg.init(data_path, global.wg_config, global.config.tmp_dir) + this.wg_config = global.wg_config } @@ -26,6 +28,7 @@ module.exports = (Extension) => {return class extends Extension { case undefined: { this.db.select('device', ['*'], 'user_id=$id', null, [req.context.user.id], (err, profiles) => { req.context.profiles = profiles + req.context.connected_ip = req.ip.startsWith(this.wg_config.subnet) ? req.ip : false this.return_html(req, res, 'index', err) }) break diff --git a/www/extensions/root/error.html b/www/extensions/root/error.html deleted file mode 100644 index 2d7bc73..0000000 --- a/www/extensions/root/error.html +++ /dev/null @@ -1 +0,0 @@ -{% include "snippets/error.html" %} diff --git a/www/extensions/root/index.js b/www/extensions/root/index.js index ed43eca..64ffe0e 100644 --- a/www/extensions/root/index.js +++ b/www/extensions/root/index.js @@ -2,7 +2,7 @@ module.exports = (Extension) => {return class extends Extension { name = 'root' title = 'Home' tables = true - dependencies = ['content','nj','fetch','data','texts'] + dependencies = ['content','nj','fetch','data','texts','cookie'] constructor (global, path, config_path) { super(global, path) @@ -11,6 +11,9 @@ module.exports = (Extension) => {return class extends Extension { requires_login(path) { + if (path.at(0) == '_') { + return true + } return false } @@ -25,66 +28,82 @@ module.exports = (Extension) => {return class extends Extension { return this.return_html(req, res, 'user') } case 'login': { - // if invalid credentials display the error + // If user not logged in if (!req.context.user) { - let err = req.context.auth_err ?? new Error("Wrong name or password") - // if cancelled return to home - if (req.headers.cookie > 0 && err.message == "Quit early") { - res.writeHead(307, {Location: "/", 'Set-Cookie': 0}) - return res.end() - } - if (req.headers.cookie >= 1 && req.headers.cookie%2 == 1) { - req.context.err = err - req.context.location = "/login" - req.context.return = "/" - return this.return_html(req, res, 'error', null, 500, 200, {'Set-Cookie': parseInt(req.headers.cookie)+1}) + // Attempt + if (req.data) { + // Login + if (req.data.login) { + let auth = ''; + if (req.data.username && req.data.password) { + auth = Buffer.from(req.data.username+":"+req.data.password).toString('base64') + } + return this.return_html(req, res, 'login', null, 500, 303, { + "Location": "/login", + "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true) + }) + } + // Register + else if (req.data.register) { + return this.data.addUser(req.data.username, req.data.password, (err) => { + // if invalid credentials + if (err) { + req.context.auth_err = err + return this.return_html(req, res, 'login', null) + } + // success + else { + let auth = Buffer.from(req.data.username+":"+req.data.password).toString('base64') + return this.return_html(req, res, 'login', null, 500, 303, { + "Location": "/", + "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true) + }) + } + }) + } } - res.writeHead(401, {"WWW-Authenticate": `Basic`, 'Set-Cookie': parseInt(req.headers.cookie)+1}) - return res.end() + // First load + return this.return_html(req, res, 'login', null, 500, 200, { + "Set-Cookie": this.del_cookie('auth') + }) } // if logged in - res.writeHead(307, {"Location": "/", 'Set-Cookie': 0}) + res.writeHead(307, {"Location": "/"}) return res.end() } case 'logout': { - // if user is logged out - if (!req.context.user) { - res.writeHead(307, {"Location": "/"}) - return res.end() - } - // log user out and redirect - req.context.destination = '/' - return this.return_html(req, res, 'logout', null, 500, 401) - } - case 'register': { - // if user already logged in if (req.context.user) { - res.writeHead(307, {"Location": "/"}) + // log user out and redirect + res.writeHead(307, { + "Location": "/", + "Set-Cookie": this.del_cookie('auth') + }) return res.end() } - // try to create account - this.data.addUser(req.headers.authorization, (err) => { - // if invalid credentials - if (err) { - // if cancelled return to home - if (req.headers.cookie > 0 && err.message == "Quit early") { - res.writeHead(307, {Location: "/", 'Set-Cookie': 0}) - return res.end() + // if user is logged out + res.writeHead(307, {"Location": "/"}) + return res.end() + } + case '_': { + var item = req.path.shift() + switch (item) { + case 'pfp': { + var args = req.url.split('?').at(1) + if (args) { + try { + args = decodeURIComponent(args) + } catch {} + this.db.update('user', ['pfp_code=$args'], 'id=$id', [args, req.context.user.id], (err) => { + res.writeHead(307, {"Location": "/"}) + res.end() + }) + return } - // display the error if there is one - if (req.headers.cookie >= 1 && req.headers.cookie%2 == 1) { - req.context.err = err - req.context.location = "/register" - req.context.return = "/" - return this.return_html(req, res, 'error', null, 500, 200, {'Set-Cookie': parseInt(req.headers.cookie)+1}) + else { + return this.return_html(req, res, 'pfp', null) } - res.writeHead(401, {"WWW-Authenticate": `Basic`, 'Set-Cookie': parseInt(req.headers.cookie)+1}) - return res.end() } - // return to home - res.writeHead(307, {Location: "/", 'Set-Cookie': 0}) - return res.end() - }) + } break } default: { diff --git a/www/extensions/root/login.html b/www/extensions/root/login.html new file mode 100644 index 0000000..8427eb0 --- /dev/null +++ b/www/extensions/root/login.html @@ -0,0 +1,25 @@ +{% extends "layout.html" %} + +{% block body %} +

Register / Log in

+ +
{{msg.user.name}} {{msg.time}} {{msg.content}}
+ + + + + + + + + + + + +
+ + +
+ {{auth_err}} + +{% endblock %} diff --git a/www/extensions/root/logout.html b/www/extensions/root/logout.html deleted file mode 100644 index 22f732f..0000000 --- a/www/extensions/root/logout.html +++ /dev/null @@ -1 +0,0 @@ -{% include "snippets/redirect.html" %} diff --git a/www/extensions/root/pfp.html b/www/extensions/root/pfp.html new file mode 100644 index 0000000..a28d83c --- /dev/null +++ b/www/extensions/root/pfp.html @@ -0,0 +1,16 @@ +{% extends "extension.html" %} + +{% block body %} +
+
+
+
+ +
+ +
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/www/extensions/root/static/index.css b/www/extensions/root/static/index.css index e5ca900..0cad870 100644 --- a/www/extensions/root/static/index.css +++ b/www/extensions/root/static/index.css @@ -67,17 +67,9 @@ a:visited { .button:hover { background-color: darkgrey; } -.disabledbutton { +.button.disabled { background-color: darkgrey; - padding: 5px 10px; - border-radius: var(--margin-small); - display: inline-block; - width: fit-content; - height: 16px; - font-size: 15px; box-shadow: inset 0 0 2px black; - text-decoration: none; - color: black; cursor: default; } @@ -173,12 +165,11 @@ header > nav > a:visited { top: -15px; } .extensions img { - width: var(--ext-height); + width: calc(var(--ext-height) - 2px); height: var(--ext-height); position: relative; top: -5px; margin: 0 -5px; - mask-image: radial-gradient(rgba(0,0,0,1),rgba(0,0,0,1),rgba(0,0,0,1), rgba(0,0,0,1), rgba(0,0,0,1), rgba(0,0,0,0), rgba(0,0,0,0), rgba(0,0,0,0)); } .content { diff --git a/www/extensions/root/static/keuknet-client/installer.exe b/www/extensions/root/static/keuknet-client/installer.exe deleted file mode 100755 index abab37b..0000000 Binary files a/www/extensions/root/static/keuknet-client/installer.exe and /dev/null differ diff --git a/www/extensions/root/static/keuknet-client/installer.py b/www/extensions/root/static/keuknet-client/installer.py deleted file mode 100755 index d1e28f2..0000000 --- a/www/extensions/root/static/keuknet-client/installer.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# compile on linux with `sudo wine pyinstaller --onefile installer.py` -# cant run properly in wine!! -import os -import sys -from sys import exit - -# Config values -HOST = "keuk.net" -DOWNLOAD_ARGS = "--no-progress-meter --fail" -OTHER_DESKTOP_FILES_DIR = "/usr/share/applications" -OTHER_BINARIES_DIR = "/usr/bin" -OTHER_DEPENDENCIES = { - 'wg-quick': "WireGuard-tools", - 'xdg-mime': "XDG", - 'curl': "cURL", - 'chmod': "chmod" -} - -# Constants -WINDOWS = os.name == 'nt' -OTHER = not WINDOWS - - -# Initialization -if WINDOWS: - import ctypes - # Privileges - if not ctypes.windll.shell32.IsUserAnAdmin() == 1: - ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1) - exit(1) - # Confirmation - os.system("mshta \"javascript:alert('Before continuing please save all open files.');close()\"") -if OTHER: - # Privileges - if not os.geteuid() == 0: - print("Not running as root, cancelling install...") - exit(1) - # Confirmation - input(f"This script will install:\n'keuknet.desktop' to '{OTHER_DESKTOP_FILES_DIR}'\n'keuknet.py' to '{OTHER_BINARIES_DIR}'\nPress 'CTRL+C' to cancel or 'Enter/Return' to continue.") - - -# Preparation -if WINDOWS: - os.system("mkdir \"%TEMP%\\keuknetinstaller\"") - os.system(f"curl -o \"%TEMP%\\keuknetinstaller\\wireguard.exe\" https://download.wireguard.com/windows-client/wireguard-installer.exe {DOWNLOAD_ARGS}") - os.system(f"curl -o \"%TEMP%\\keuknetinstaller\\keuknet.exe\" https://{HOST}/keuknet-client/keuknet.exe {DOWNLOAD_ARGS}") - os.system("mkdir \"%PROGRAMFILES%\\KeukNet\"") -if OTHER: - if not os.path.isdir(OTHER_DESKTOP_FILES_DIR) or not os.path.isdir(OTHER_BINARIES_DIR): - print("Unusual file-system lay-out detected. Is your OS UNIX-compliant?\nIf not please edit this script to use the correct paths.") - exit(1) - for cmd, name in OTHER_DEPENDENCIES.items(): - if os.system(f"man {cmd} >/dev/null") != 0: - print(f"{name} not available at '{cmd}'. Please install a package that provides {cmd}.") - exit(1) - - -# Installation -if WINDOWS: - os.system("%TEMP%\\keuknetinstaller\\wireguard.exe && taskkill /IM wireguard.exe /F") - os.system("move \"%TEMP%\\keuknetinstaller\\keuknet.exe\" \"%PROGRAMFILES%\\KeukNet\\keuknet.exe\"") - os.system("reg add HKCR\\keuknet /ve /d \"URL:KeukNet\" /f") - os.system("reg add HKCR\\keuknet /v \"URL Protocol\" /d "" /f") - os.system("reg add HKCR\\keuknet\\shell /f") - os.system("reg add HKCR\\keuknet\\shell\\open /f") - os.system("reg add HKCR\\keuknet\\shell\\open\\command /d \"\\\"C:\\Program Files\\KeukNet\\keuknet.exe\\\" \"%1\"\" /f") -if OTHER: - os.system(f"curl -o \"{OTHER_DESKTOP_FILES_DIR}/keuknet.desktop\" https:/{HOST}/keuknet-client/keuknet.desktop {DOWNLOAD_ARGS}") - os.system(f"curl -o \"{OTHER_BINARIES_DIR}/keuknet.py\" https:/{HOST}/keuknet-client/keuknet.py {DOWNLOAD_ARGS}") - os.system(f"chmod +x \"{OTHER_BINARIES_DIR}/keuknet.py\"") - os.system("xdg-mime default keuknet.desktop x-scheme-handler/keuknet") - - -# Finalization -if WINDOWS: - os.system("shutdown /g /f /t 3 /d p:4:2 /c \"Rebooting in 3 sec to finish KeukNet installation\"") -if OTHER: - print("Successfully installed the KeukNet client.") diff --git a/www/extensions/root/static/keuknet-client/keuknet.desktop b/www/extensions/root/static/keuknet-client/keuknet.desktop deleted file mode 100644 index 894222d..0000000 --- a/www/extensions/root/static/keuknet-client/keuknet.desktop +++ /dev/null @@ -1,7 +0,0 @@ -[Desktop Entry] -Type=Application -Name=KeukNet Connection Handler -Exec=/usr/bin/keuknet.py %u -Terminal=true -StartupNotify=false -MimeType=x-scheme-handler/keuknet; diff --git a/www/extensions/root/static/keuknet-client/keuknet.exe b/www/extensions/root/static/keuknet-client/keuknet.exe deleted file mode 100755 index 43d1491..0000000 Binary files a/www/extensions/root/static/keuknet-client/keuknet.exe and /dev/null differ diff --git a/www/extensions/root/static/keuknet-client/keuknet.py b/www/extensions/root/static/keuknet-client/keuknet.py deleted file mode 100644 index 4263c34..0000000 --- a/www/extensions/root/static/keuknet-client/keuknet.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# compile on linux with `sudo wine pyinstaller --onefile keuknet.py` -# cant run properly in wine!! -import os -import sys -import subprocess -from sys import argv as args -from sys import exit - -DEBUG = True -HOST = "keuk.net" -DOWNLOAD_ARGS = "--no-progress-meter --fail" - -# Constants -WINDOWS = os.name == 'nt' -OTHER = not WINDOWS - -if OTHER and not os.geteuid() == 0: - import notify2 - notify2.init("KeukNet client") - def notify(content): - notify2.Notification('KeukNet',content).show() -else: - import builtins as __builtin__ - def notify(content): - __builtin__.print(content) - - -def print(*args): - if DEBUG: - for arg in args: - notify(str(arg)) - else: - pass - -def run(command): - try: - subprocess.run(command, shell=True, check=True, capture_output=True) - except subprocess.CalledProcessError as err: - print(f"error when running command'{command}'!") - if WINDOWS: - os.system(f"echo \"{err} {err.stderr.decode('utf8')}\" >> \"%TEMP%\\keuknet.log\"") - if OTHER: - os.system(f"echo \"{err}: {err.stderr.decode('utf8')}\" >>/tmp/keuknet.log") - exit(1) - - -def admin(): - """Obtain admin privileges - """ - if WINDOWS: - import ctypes - if not ctypes.windll.shell32.IsUserAnAdmin() == 1: - ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1) - exit(0) - if OTHER: - if not os.geteuid() == 0: - print("Script not started as root. Running sudo..") - run(f"sudo {sys.executable} {' '.join(sys.argv)}") - exit(0) - -if WINDOWS: - os.system(f"echo \">{args}\" >> \"%TEMP%\\keuknet.log\"") - -# Check if valid uri -if not args[-1].startswith("keuknet://"): - print("Called with incorrect or missing protocol") - exit(1) - -args = args[-1] -command = args.split("keuknet://",1)[1] -instruction, args = command.split("=") if "=" in command else [command,""] -instruction = instruction.split("&")[0] if "&" in instruction else instruction -instruction = instruction.split(";")[0] if ";" in instruction else instruction -args = args.split("&")[0] if "&" in args else args -args = args.split(";")[0] if ";" in args else args -args = args.split("/")[0] if "/" in args else args -print(command, instruction, args) - -if WINDOWS: - os.system(f"echo \">{instruction}!!{args}\" >> \"%TEMP%\\keuknet.log\"") - -match instruction: - case "install": - notify("Installing new profile...") - admin() - if WINDOWS: - os.system("del /F \"%PROGRAMFILES%\\WireGuard\\Data\\Configurations\\keuknet.conf.dpapi\"") - run(f"curl -o \"%PROGRAMFILES%\\WireGuard\\Data\\Configurations\\keuknet.conf\" \"https://{HOST}/profile/getconf?{args}\" {DOWNLOAD_ARGS}") - if OTHER: - run(f"curl -o \"/etc/wireguard/keuknet.conf\" https://{HOST}/profile/getconf?{args} {DOWNLOAD_ARGS}") - case "load": - notify("Enabling keuknet...") - admin() - if WINDOWS: - run("\"%PROGRAMFILES%\\WireGuard\\wireguard.exe\" /installtunnelservice \"%PROGRAMFILES%\\WireGuard\\Data\\Configurations\\keuknet.conf.dpapi\"") - if OTHER: - run("wg-quick up keuknet") - case "unload": - notify("Disabling keuknet...") - admin() - if WINDOWS: - run("\"%PROGRAMFILES%\\WireGuard\\wireguard.exe\" /uninstalltunnelservice keuknet >nul") - if OTHER: - run("wg-quick down keuknet") - case _: - print("Invalid instruction") diff --git a/www/extensions/root/static/keuknet.png b/www/extensions/root/static/keuknet.png index 658bf82..31c36b9 100644 Binary files a/www/extensions/root/static/keuknet.png and b/www/extensions/root/static/keuknet.png differ diff --git a/www/extensions/root/static/pfp.js b/www/extensions/root/static/pfp.js new file mode 100644 index 0000000..85072c0 --- /dev/null +++ b/www/extensions/root/static/pfp.js @@ -0,0 +1,178 @@ +var box = document.getElementById('avatar'); +var inputs = document.getElementById('inputs'); +var http = new XMLHttpRequest(); + +var hidden = [ + 'size','radius','backgroundType','backgroundRotation', + 'translateX','translateY','clip','randomizeIds', + 'beardProbability','earringsProbability','frecklesProbability','glassesProbability', + 'hairAccessoriesProbability' +]; + +var options; +var schema; + +function values_to_args() { + let args = ''; + for (i in options) { + let option = options[i]; + let name = option.name; + let value = option.value; + + if (value.startsWith('#')) { + value = value.substring(1); + }; + + if (option.type == 'checkbox') { + value = option.checked.toString(); + }; + + if (value == '000000' || value == '' || value == 'false') { + continue; + }; + + if (option.tagName == 'SELECT') { + if (value != '') { + args += name+'Probability=100&'; + }; + }; + + if (value != null) { + args += name+'='+value+'&'; + }; + } + return args; +} + +function args_to_values(args) { + args = args.split('&'); + let args_map = {}; + for (var i in args) { + let [key, value] = args[i].split('='); + args_map[key] = value; + }; + + for (k in args_map) { + let v = args_map[k]; + + if (k.endsWith('Probability')) { + delete args_map[k]; + }; + if (k.endsWith('Color')) { + args_map[k] = '#'+v; + }; + }; + + for (i in options) { + let option = options[i]; + let name = option.name; + + if (args_map.hasOwnProperty(name)) { + option.value = args_map[name]; + }; + + if (name == 'flip' && option.value == 'true') { + option.checked = true; + }; + }; +} + +function save() { + window.location.href = window.location.href+'?'+values_to_args(); +} + +function update() { + let args = values_to_args(); + http.open('GET','https://api.keukeiland.nl/dicebear/7.x/lorelei/svg?'+args, true); + http.onload = () => { + box.innerHTML = http.responseText; + }; + http.send(); +} + +function init() { + for (var k in schema) { + var v = schema[k]; + let input = document.createElement("input"); + let label = document.createElement("label"); + + if (k.endsWith('Probability')) { + v.type = 'boolean'; + }; + + switch (v.type) { + case 'string': { + break; + } + case 'boolean': { + input.setAttribute('type', 'checkbox'); + input.setAttribute('value', 'true'); + break; + } + case 'integer': { + input.setAttribute('type', 'range'); + input.setAttribute('min', v.minimum); + input.setAttribute('max', v.maximum); + input.setAttribute('value', v.default); + break; + } + case 'array': { + if (v.items.pattern == "^(transparent|[a-fA-F0-9]{6})$") { + input.setAttribute('type', 'color'); + } + else if (v.items.type == 'integer') { + input.setAttribute('type', 'range'); + input.setAttribute('min', v.minimum); + input.setAttribute('max', v.maximum); + input.setAttribute('value', v.default); + } + else if (v.items.type == 'string') { + input = document.createElement('select'); + + let el = document.createElement('option'); + el.setAttribute('value', 'false'); + el.innerHTML = ''; + input.appendChild(el); + + for (var i in v.items.enum) { + let item = v.items.enum[i]; + let el = document.createElement('option'); + el.setAttribute('value', item); + el.innerHTML = item; + input.appendChild(el); + } + } + break; + } + } + + let row = inputs.insertRow(-1); + + let name = k.replace(/([A-Z])/g, " $1"); + label.innerHTML = name.toLowerCase(); + input.setAttribute('class', 'avatar-options'); + input.setAttribute('name', k); + row.insertCell(-1).innerHTML = label.outerHTML; + row.insertCell(-1).innerHTML = input.outerHTML; + } + + options = Array.from(document.getElementsByClassName('avatar-options')); + options.forEach(el => el.onchange = update); +} + +let xhr = new XMLHttpRequest(); +xhr.open('GET', 'https://api.keukeiland.nl/dicebear/7.x/lorelei/schema.json', true); +xhr.onload = () => { + schema = JSON.parse(xhr.responseText).properties; + + for (var i in hidden) { + delete schema[hidden[i]]; + }; + + init(); + + args_to_values(document.getElementById('old_code').innerHTML); + + update(); +} +xhr.send(); diff --git a/www/extensions/root/static/robots.txt b/www/extensions/root/static/robots.txt deleted file mode 100644 index c2aab7e..0000000 --- a/www/extensions/root/static/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: / \ No newline at end of file diff --git a/www/extensions/root/user.html b/www/extensions/root/user.html index 4979c15..3fb66e8 100644 --- a/www/extensions/root/user.html +++ b/www/extensions/root/user.html @@ -3,8 +3,8 @@ {% block body %}

Hello {{user.name}}!

- -

^ Your pfp (can be changed later, still being worked on)

+
+ Edit

You registered at {{user.regdate}}

diff --git a/www/index.js b/www/index.js index 7e1804f..d5cc3e3 100644 --- a/www/index.js +++ b/www/index.js @@ -11,6 +11,7 @@ var data, wg_config exports.init = function (global) { ({data,wg_config,texts,nj,config} = global) nj.addGlobal('dicebear_host', config.dicebear_host) + nj.addGlobal('client_location', config.client_location) root = new ((require(`./extensions/root/index.js`))(global.Extension))(global, `${__dirname}/extensions/root/`, `${__dirname}/../data/root/`) @@ -36,10 +37,9 @@ exports.main = function (req, res) { req.context = {...req.args} req.context.extensions = extensions req.context.location = location - req.context.connected = req.ip.startsWith(wg_config.ip_scope) // Authenticate using user&pass, else using ip - data.authenticate(req.headers.authorization, req.ip, wg_config.ip_scope, function (user, err) { + data.authenticate(req.headers.authorization, req.ip, wg_config.subnet, function (user, err) { req.context.user = user if (err) req.context.auth_err = err if (user && user.is_admin) req.context.extensions = {...req.context.extensions, ...admin_extensions} diff --git a/www/templates/includes/extensions.html b/www/templates/includes/extensions.html index cddc6dd..0abe0ef 100644 --- a/www/templates/includes/extensions.html +++ b/www/templates/includes/extensions.html @@ -5,6 +5,6 @@ {{user.name}} - + diff --git a/www/templates/includes/header.html b/www/templates/includes/header.html index 1423a87..233c6e3 100644 --- a/www/templates/includes/header.html +++ b/www/templates/includes/header.html @@ -9,8 +9,7 @@ {% if user %} Logout {% else %} - Register - Login + Register / Login {% endif %}