diff --git a/.gitignore b/.gitignore
index 518c515..c47285e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,6 @@ build/
# testing folders
/certs
+
+# IDE settings
+/.vscode
diff --git a/classes/db.js b/classes/db.js
deleted file mode 100644
index b9a119a..0000000
--- a/classes/db.js
+++ /dev/null
@@ -1,100 +0,0 @@
-module.exports = class DB {
- special_tables = [
- 'user',
- 'db_table_versions'
- ]
-
- constructor(db, prefix) {
- this.db = db
- this.prefix = '_'+prefix+'_'
- }
-
- __formatTableName = (name) => {
- if (this.special_tables.includes(name)) {
- return name
- }
- else if (name.startsWith('_')) {
- return name
- }
- return this.prefix + name
- }
-
- // Table modifications
- createTable = (table, columns) => {
- table = this.__formatTableName(table)
- columns = columns.join(', ')
- this.db.run(`CREATE TABLE IF NOT EXISTS ${table} (${columns})`, [], (err) => {
- if (err) throw new Error(`Failed creating new table ${table}. ${err}`)
- })
- }
-
- dropTable = (table) => {
- table = this.__formatTableName(table)
- this.db.run(`DROP TABLE ${table}`, [], (err) => {
- if (err) throw new Error(`Failed dropping table ${table}. ${err}`)
- })
- }
-
- // Column modifications
- addColumn = (table, column, callback) => {
- table = this.__formatTableName(table)
- this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column}`, [], (err) => {
- callback(err)
- })
- }
-
- dropColumn = (table, column, callback) => {
- table = this.__formatTableName(table)
- this.db.run(`ALTER TABLE ${table} DROP COLUMN ${column}`, [], (err) => {
- callback(err)
- })
- }
-
- renameColumn = (table, old_column, new_column, callback) => {
- table = this.__formatTableName(table)
- this.db.run(`ALTER TABLE ${table} RENAME COLUMN ${old_column} to ${new_column}`, [], (err) => {
- callback(err)
- })
- }
-
- // Standard functions
- select = (table, columns, where, order_by, params, callback) => {
- table = this.__formatTableName(table)
- columns = columns.join(', ')
- // Construct query
- let query = `SELECT ${columns} FROM ${table}`
- if (where) query += ` WHERE ${where}`
- if (order_by) query += ` ORDER BY ${order_by}`
- // Get result
- this.db.all(query, params, (err, data) => {
- callback(err, data)
- })
- }
-
- insert = (table, columns, values, callback) => {
- table = this.__formatTableName(table)
- let columns_str = columns.join(', ')
- let values_str = `$${columns.join(', $')}`
- // Run query
- this.db.run(`INSERT INTO ${table} (${columns_str}) VALUES (${values_str})`, values, (err) => {
- callback(err)
- })
- }
-
- update = (table, columns, where, values, callback) => {
- table = this.__formatTableName(table)
- columns = columns.join(', ')
- // Run query
- this.db.run(`UPDATE ${table} SET ${columns} WHERE ${where}`, values, (err) => {
- callback(err)
- })
- }
-
- delete = (table, where, values, callback) => {
- table = this.__formatTableName(table)
- // Run query
- this.db.run(`DELETE FROM ${table} WHERE ${where}`, values, (err) => {
- callback(err)
- })
- }
-}
diff --git a/classes/extension.js b/classes/extension.js
deleted file mode 100644
index e2320d5..0000000
--- a/classes/extension.js
+++ /dev/null
@@ -1,152 +0,0 @@
-module.exports = class Extension {
- admin_only = false
- tables = false
- dependencies = []
-
- constructor(global, path) {
- if (this.constructor == Extension) {
- throw new Error("Abstract classes can't be instantiated.");
- }
- // Runs after child has been constructed
- setTimeout(() => {
- // Init dependencies
- for (const dep of this.dependencies) {
- if (dep != 'fetch') {
- this[dep] = global[dep]
- }
- else {
- this['fetch'] = new global.microfetch.Fetch(path)
- }
- }
- // Init db
- if (this.tables) {
- // create interface
- this.db = new global.DB(global.db(), this.name)
- // init tables
- new ((require(`${path}tables`))(global.Tables))(global.db(), this.db, this.name)
- }
- }, 0)
- }
-
- /**
- * @param {Array} path The current path being visited
- * @returns true if the path requires being logged in, else false
- */
- requires_login(path) {
- return true
- }
-
- requires_admin(path) {
- return this.admin_only
- }
-
- handle_req(req, res) {
- req.context.extension = this
- return this.handle(req, res)
- }
-
- handle(req, res) {
- throw new Error(`Method 'handle()' must be implemented.`);
- }
-
- return(res, err, location, err_code=500) {
- if (err) {
- res.writeHead(err_code)
- return res.end()
- }
- let code = 200
- let args = {}
-
- if (location) {
- code = 307
- args['Location'] = location
- }
-
- res.writeHead(code, args)
- return res.end()
- }
-
- return_text(req, res, item) {
- req.context.__render_item = this.texts[item]
- this.nj.renderString(
- '{% extends "layout.html" %}{% block body %}{{__render_item |safe}}{% endblock %}',
- req.context, (err, data) => {
- if (err) {
- res.writeHead(500)
- return res.end()
- }
- res.writeHead(200, this.content['html'])
- return res.end(data)
- })
- }
-
- return_html(req, res, item, err, err_code=500, success_code=200, headers=null) {
- if (err) {
- res.writeHead(err_code)
- return res.end()
- }
-
- headers = {...this.content['html'], ...headers}
-
- this.nj.render(this.name+'/'+item+'.html', req.context, (err, data) => {
- if (err) {
- res.writeHead(err_code)
- return res.end()
- }
- res.writeHead(success_code, headers)
- return res.end(data)
- })
- }
-
- return_file(res, file) {
- this.fetch.file(file, (data,filetype,err) => {
- if (err) {
- res.writeHead(404)
- res.end()
- return
- }
- res.writeHead(200, this.content[filetype])
- res.end(data)
- })
- }
-
- return_data(res, data, err, headers, err_code=404) {
- if (err) {
- res.writeHead(err_code)
- return res.end()
- }
- let args = {"Content-Type": "text/plain charset utf-8"}
-
- if (headers) {
- args = headers
- }
-
- 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/classes/tables.js b/classes/tables.js
deleted file mode 100644
index 7912d8f..0000000
--- a/classes/tables.js
+++ /dev/null
@@ -1,60 +0,0 @@
-module.exports = class Tables {
- tables = {}
-
- constructor(raw_db, db, prefix) {
- if (this.constructor == Tables) {
- throw new Error("Abstract classes can't be instantiated.");
- }
- this.db = db
-
- // Runs after child has been constructed
- setTimeout(() => {
- raw_db.serialize(()=> {
- db.select('db_table_versions', ['table_id','version'], null, null, [], (err, data) => {
- if (!data) { // Should only occur on this one missing and root called first
- this['db_table_versions']['0']()
- }
- else {
- let table_versions = {}
- for (const row of data) {
- table_versions[row.table_id] = row.version
- }
-
- for (var [table, latest] of Object.entries(this.tables)) {
- let table_id = `_${prefix}_${table}`
- if (!Object.hasOwn(table_versions, table_id)) {
- console.log(`Adding table ${table_id}`)
- try {
- this[table][0]()
- }
- catch {
- throw new Error(`Missing ${table} version 0`)
- }
- table_versions[table_id] = 0
- raw_db.run("INSERT INTO db_table_versions (table_id, version) VALUES ($table, $version)", [table_id, table_versions[table_id]], (err) => {
- if (err) throw new Error(`Failed adding table version identifier. ${err}`)
- console.log(`Added table ${table_id}`)
- })
- }
- while (table_versions[table_id] < latest) {
- console.log(`Upgrading table ${table_id} to ${table_versions[table_id]+1}`)
- try {
- this[table][table_versions[table_id] +1]()
- }
- catch {
- throw new Error(`Missing ${table} version ${table_versions[table_id] +1}`)
- }
-
- table_versions[table_id]++
- raw_db.run("UPDATE db_table_versions SET version=$version WHERE table_id=$table", [table_versions[table_id], table_id], (err) => {
- if (err) throw new Error(`Failed updating table version identifier. ${err}`)
- console.log(`Upgraded table ${table_id} to ${table_versions[table_id]}`)
- })
- }
- }
- }
- })
- })
- }, 0)
- }
-}
\ No newline at end of file
diff --git a/config/.gitignore b/config/.gitignore
index 497148d..98c6536 100644
--- a/config/.gitignore
+++ b/config/.gitignore
@@ -1,3 +1,3 @@
*
-!*.example
+!*.example.*
!.gitignore
diff --git a/config/config.example.ts b/config/config.example.ts
new file mode 100644
index 0000000..e905891
--- /dev/null
+++ b/config/config.example.ts
@@ -0,0 +1,32 @@
+import { tmpdir } from 'os'
+
+export default {
+ /** Whether the server is running behind an nginx instance */
+ nginx: false,
+ /** The domain of the instance. */
+ domain: "example.com",
+ /** The interface to host on. Defaults to '::' (all interfaces) */
+ host: "::",
+ /** The port to listen on for HTTP traffic. Defaults to 80 */
+ http_port: 80,
+ /** The salt to use for encrypting the passwords. */
+ salt: "PASSWORD SALT HERE!!",
+ /** Location of the temporary directory. Defaults to your system's default temp dir */
+ tmp_dir: tmpdir(),
+ /** The location where the dicebear instance is hosted */
+ dicebear_host: "https://api.dicebear.com/7.x/lorelei/svg",
+ /** The location where the client files are hosted */
+ client_location: "https://github.com/keukeiland/keuknet-client/releases/latest/download/",
+ /** Whether connections are logged */
+ logging: false,
+
+ // Below options are only required when running standalone
+ /** The port to listen on for HTTPS traffic. Defaults to 443 */
+ https_port: 443,
+ /** The path to your HTTPS certificate-set's private key. */
+ private_key_path: `${import.meta.dirname}/../certs/privkey.pem`,
+ /** The path to your HTTPS certificate-set's certificate. */
+ server_cert_path: `${import.meta.dirname}/../certs/cert.pem`,
+ /** The path to your HTTPS certificate-set's CA-chain. */
+ ca_cert_path: `${import.meta.dirname}/../certs/ca.pem`,
+}
diff --git a/config/config.js.example b/config/config.js.example
deleted file mode 100644
index 6cd4ef2..0000000
--- a/config/config.js.example
+++ /dev/null
@@ -1,30 +0,0 @@
-const {tmpdir} = require('os')
-
-/** Whether the server is running behind an nginx instance */
-exports.nginx = false
-/** The domain of the instance. */
-exports.domain = "example.com"
-/** The interface to host on. Defaults to '::' (all interfaces) */
-exports.host = "::"
-/** The port to listen on for HTTP traffic. Defaults to 80 */
-exports.http_port = 80
-/** The salt to use for encrypting the passwords. */
-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 = "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
-
-// Below options are only required when running standalone
-/** The port to listen on for HTTPS traffic. Defaults to 443 */
-exports.https_port = 443
-/** The path to your HTTPS certificate-set's private key. */
-exports.private_key_path = `${__dirname}/../certs/privkey.pem`
-/** The path to your HTTPS certificate-set's certificate. */
-exports.server_cert_path = `${__dirname}/../certs/cert.pem`
-/** The path to your HTTPS certificate-set's CA-chain. */
-exports.ca_cert_path = `${__dirname}/../certs/ca.pem`
diff --git a/config/texts.example.ts b/config/texts.example.ts
new file mode 100644
index 0000000..3ed357a
--- /dev/null
+++ b/config/texts.example.ts
@@ -0,0 +1,17 @@
+export default {
+ index: `
+
Welcome to KeukNet!
+
+ If you're new here we recommend reading the getting started guide immediately after registering.
+ It can be found top-left next to the logo.
+ Registering and logging in can be done at the top-right.
+
+
+ For help or questions please contact @fizitzfux on Discord
+ or join our Discord server .
+
+
+ Please do be aware of the fact that this is still very much in development and a lot of stuff will be improved with time.
+
+ `,
+}
diff --git a/config/texts.js.example b/config/texts.js.example
deleted file mode 100644
index 64ac400..0000000
--- a/config/texts.js.example
+++ /dev/null
@@ -1,15 +0,0 @@
-exports.index = `
-Welcome to KeukNet!
-
- If you're new here we recommend reading the getting started guide immediately after registering.
- It can be found top-left next to the logo.
- Registering and logging in can be done at the top-right.
-
-
- For help or questions please contact @fizitzfux on Discord
- or join our Discord server .
-
-
- Please do be aware of the fact that this is still very much in development and a lot of stuff will be improved with time.
-
-`
diff --git a/config/wireguard.example.ts b/config/wireguard.example.ts
new file mode 100644
index 0000000..9085b01
--- /dev/null
+++ b/config/wireguard.example.ts
@@ -0,0 +1,10 @@
+export default {
+ subnet: "fdbe:1234:abcd:1::",
+ subnet_mask: "/96",
+ dns_server: "fdbe:1234:abcd:1::1, 1.1.1.1",
+ endpoint: "example.com",
+ port: "51820",
+ mtu: "1420",
+ privkey: "PRIVATE KEY HERE!!", //generate with `wg genkey`
+ pubkey: "PUBLIC KEY HERE!!", //generate with `echo | wg pubkey`
+}
diff --git a/config/wireguard.js.example b/config/wireguard.js.example
deleted file mode 100644
index 1e77970..0000000
--- a/config/wireguard.js.example
+++ /dev/null
@@ -1,8 +0,0 @@
-exports.subnet = "fdbe:1234:abcd:1::"
-exports.subnet_mask = "/96"
-exports.dns_server = "fdbe:1234:abcd:1::1, 1.1.1.1"
-exports.endpoint = "example.com"
-exports.port = "51820"
-exports.mtu = "1420"
-exports.privkey = "INVALID KEY" //generate with `wg genkey`
-exports.pubkey = "" //generate with `echo | wg pubkey`
diff --git a/global.js b/global.js
deleted file mode 100644
index 39627fd..0000000
--- a/global.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* import external modules */
-const nj = require('nunjucks').configure(['www/templates','www/extensions'])
-
-/* import custom modules */
-const microfetch = require('./modules/microfetch')
-const Extension = require('./classes/extension')
-const Tables = require('./classes/tables')
-const fetch = require('./modules/fetch')
-const data = require('./modules/data')
-const log = require('./modules/log')
-const db = require('./classes/db')
-
-/* exports */
-exports.content = {
- html:{"Content-Type": "text/html"},
- ascii:{"Content-Type": "text/plain charset us-ascii"},
- txt:{"Content-Type": "text/plain charset utf-8"},
- json:{"Content-Type": "application/json"},
- ico:{"Content-Type": "image/x-icon", "Cache-Control": "private, max-age=3600"},
- css:{"Content-Type": "text/css", "Cache-Control": "private, max-age=3600"},
- gif:{"Content-Type": "image/gif", "Cache-Control": "private, max-age=3600"},
- jpg:{"Content-Type": "image/jpeg", "Cache-Control": "private, max-age=3600"},
- js:{"Content-Type": "text/javascript", "Cache-Control": "private, max-age=3600"},
- json:{"Content-Type": "application/json"},
- png:{"Content-Type": "image/png", "Cache-Control": "private, max-age=3600"},
- md:{"Content-Type": "text/x-markdown"},
- xml:{"Content-Type": "application/xml"},
- svg:{"Content-Type": "image/svg+xml", "Cache-Control": "private, max-age=3600"},
- webmanifest:{"Content-Type": "application/manifest+json", "Cache-Control": "private, max-age=3600"},
- mp3:{"Content-Type": "audio/mpeg", "Cache-Control": "private, max-age=3600"},
- exe:{"Content-Type": "application/vnd.microsoft.portable-executable", "Cache-Control": "private, max-age=3600"},
- py:{"Content-Type": "text/x-python", "Cache-Control": "private, max-age=3600"}
-}
-
-exports.nj = nj
-exports.microfetch = microfetch
-exports.Extension = Extension
-exports.Tables = Tables
-exports.fetch = fetch
-exports.data = data
-exports.log = log
-exports.DB = db
-
-exports.db = () => {return data.db()}
diff --git a/index.js b/index.js
deleted file mode 100644
index 7230bce..0000000
--- a/index.js
+++ /dev/null
@@ -1,162 +0,0 @@
-/* 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/.
- */
-
-const cookie = require('cookie')
-
-// enable use of dotenv
-require('dotenv').config()
-
-// import config values
-let config = require('./config/config')
-let wg_config = require('./config/wireguard')
-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
-const {fetch, data, log} = global
-
-// get request handler
-const handle = require('./www/index')
-
-// set up modules
-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)
- log.status("Database initialized")
- // set up request handler
- handle.init(Object.freeze(global))
-})
-
-// handle all requests for both HTTPS and HTTP/2 or HTTP/nginx
-const requestListener = function (req, res) {
- if (process.env.DEV) {
- req.headers.host = config.domain
- req.ip = process.env.IP
- }
-
- 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']
-
- // 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
- [req.path, args] = req.url.split('?')
-
- // split arguments into key:value pairs
- req.args = {}
- if (args) {
- for (arg of args.split('&')) {
- arg = arg.split('=')
- req.args[arg[0]] = arg[1]
- // allow authentication using argument auth=
- if (req.args.auth) req.headers.authorization ??= "Basic " + req.args.auth
- }
- delete args
- }
-
- // split url into path items
- req.path = req.path.split('/').slice(1)
-
- // log the request
- log.con(req)
-
- // wait for all data if posting
- if (req.method == 'POST') {
- buffer = []
- req.on('data', function(data) {
- buffer.push(data)
- })
- req.on('end', function() {
- req.data = {raw:Buffer.concat(buffer).toString()}
- req.data.raw.split('&').forEach(function (i) {
- [k,v] = i.split('=')
- if (k && v) {
- req.data[k] = decodeURIComponent(v).replace(/\+/g,' ')
- }
- })
- req.post_data = req.data.post_data
- // forward the request to the handler
- handle.main(req, res)
- })
- }
- // other methods continue
- else {
- // forward the request to the handler
- handle.main(req, res)
- }
-}
-
-// Redirect requests to HTTPS
-const httpsRedirect = function (req, res) {
- res.writeHead(307, {"Location": `https://${req.headers.host}${req.url}`})
- res.end()
-}
-
-
-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)
- )
- }
-}
-
-startServer(true, !config.nginx)
-
\ No newline at end of file
diff --git a/modules/data.js b/modules/data.js
deleted file mode 100644
index 2df2fd2..0000000
--- a/modules/data.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/* 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/.
- */
-const crypto = require('crypto')
-const sqlite3 = require('sqlite3').verbose()
-
-var db
-var salt
-exports.init = function(path, saltq, callback) {
- db = new sqlite3.Database(path+'db.sqlite')
- salt = saltq
- return callback(undefined)
-}
-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"))
- }
- // decode authentication string
- data = new Buffer.from(auth.slice(6), 'base64').toString('utf-8')
- // check if both name and password
- if (data.startsWith(':') || data.endsWith(':')) {
- return callback(undefined, undefined, new Error("Missing name or password"))
- }
- [name, password] = data.split(":")
-
- // hash password
- password = __hash_pw(password)
- if (!password)
- return callback(undefined, undefined, new Error("Missing name or password"))
-
- return callback(name, password)
-}
-
-function __exists(name, callback) {
- // check if name already exists
- db.get("SELECT EXISTS(SELECT 1 FROM user WHERE name=$name)", name, function (err, result) {
- return callback(!!Object.values(result)[0], 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,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) {
- 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)
- }
- // 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/fetch.js b/modules/fetch.js
deleted file mode 100644
index 6042ef6..0000000
--- a/modules/fetch.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* 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/.
- */
-
-// import external libraries
-const fs = require('fs').promises
-
-// define global variables
-var fileCache = {}
-var root = __dirname
-
-// binary file-types
-const bin_exts = ['png','jpg','mp3','exe']
-
-
-exports.init = function (path) {
- root = path
-}
-
-exports.file = function (file, callback) {
- // load from cache if available
- if (fileCache[file]) {
- return callback(fileCache[file])
- }
- console.log(`\x1b[34m>> Caching [${file}]\x1b[0m`)
- // get file-type and if it's binary or text
- filetype = file.slice(file.lastIndexOf('.')+1)
- encoding = bin_exts.includes(filetype) ? undefined : 'utf8'
- // read the file
- fs.readFile(root+file, encoding)
- // cache and return the data
- .then(data => {
- fileCache[file] = data
- return callback(data)
- })
- // error if file can't be read
- .catch(err => {
- console.error(`\x1b[31m>> Could not read [${file}]\x1b[0m`)
- return callback(undefined, err)
- })
-}
-
-exports.key = function (location, callback) {
- fs.readFile(location, "utf8")
- .then(data => {
- callback(data)
- })
- .catch(err => {
- callback(undefined, err)
- })
-}
\ No newline at end of file
diff --git a/modules/log.js b/modules/log.js
deleted file mode 100644
index 82e58d8..0000000
--- a/modules/log.js
+++ /dev/null
@@ -1,64 +0,0 @@
-var we_logging
-exports.init = function(we_log) {
- we_logging = we_log
-}
-
-function __mask_ip(ip) {
- let tmp = ""
- // if IPv4
- if (ip.includes('.')) {
- // strip 4to6 prefix
- ip = ip.substring(ip.lastIndexOf(':')+1,ip.length)
- // mask ip
- ip.split('.').forEach(function (quad, index) {
- quad = quad.padStart(3,"0")
- if (index <= 2) tmp += quad + "."
- if (index == 2) tmp += "*"
- })
- }
- else {
- // mask ip
- ip.split(':').forEach(function (quad, index) {
- quad = quad.padStart(4,"0")
- if (index <= 3) tmp += quad + ":"
- if (index == 3) tmp += "*"
- })
- }
- return tmp
-}
-
-function __mask_url(url) {
- return url.split('?')[0]
-}
-
-exports.con = function(req) {
- if (we_logging) {
- ip = __mask_ip(req.ip)
- url = __mask_url(req.url)
- console.log(
- `\x1b[32m [${ip}]=>'${req.method} ${url}
- HTTP/${req.httpVersion} ${(req.headers['user-agent'] ?? "NULL").split(" ",1)[0]} ${req.headers.authorization? "auth" : "noauth"}\x1b[0m`
- )
- }
-}
-
-exports.con_err = function(req) {
- if (we_logging) {
- ip = __mask_ip(req.ip)
- console.log(
- `\x1b[35m DEN[${ip}]: '${req.headers.host}'\x1b[0m`
- )
- }
-}
-
-exports.status = function(msg) {
- console.log(`\x1b[34m>> ${msg}\x1b[0m`)
-}
-
-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/modules/microfetch.js b/modules/microfetch.js
deleted file mode 100644
index 54459a6..0000000
--- a/modules/microfetch.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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/.
- */
-
-const {readFile} = require('fs').promises
-class Fetch {
- constructor (root) {
- this.bin_exts = ['png','jpg','mp3']
- this.cache = {}
- this.root = root+'/static/'
- }
-
- file (file, callback) {
- // load from cache if available
- if (this.cache[file]) {
- return callback(this.cache[file])
- }
- // get file-type and if it's binary or text
- var filetype = file.slice(file.lastIndexOf('.')+1)
- var encoding = this.bin_exts.includes(filetype) ? undefined : 'utf8'
-
- let location = file.at(0) == '/' ? file : this.root+file
-
- // read the file
- readFile(location, encoding)
- // cache and return the data
- .then(data => {
- this.cache[file] = data
- return callback(data, filetype)
- })
- // error if file can't be read
- .catch(err => {
- return callback(undefined, undefined, err)
- })
- }
-}
-exports.Fetch = Fetch
diff --git a/package-lock.json b/package-lock.json
index 6ce3c33..62b0a58 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,21 +1,435 @@
{
"name": "keuknet",
- "version": "2.4.1",
+ "version": "3.0.0-beta-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "keuknet",
- "version": "2.4.1",
+ "version": "3.0.0-beta-1",
"license": "MPL-2.0",
"dependencies": {
- "cookie": "^0.6.0",
+ "cookie": "^1.0.2",
"dotenv": "^16.4.5",
+ "knex": "^3.1.0",
"nunjucks": "^3.2.4",
- "sqlite3": "^5.1.6"
+ "sqlite3": "^5.1.6",
+ "typescript": "^5.7.3"
},
"devDependencies": {
- "nodemon": "^3.1.0"
+ "@types/cookie": "^0.6.0",
+ "@types/knex": "^0.15.2",
+ "@types/node": "^22.10.7",
+ "@types/nunjucks": "^3.2.6",
+ "tsx": "^4.19.2"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+ "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+ "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+ "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+ "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+ "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+ "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+ "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+ "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+ "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+ "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+ "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+ "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+ "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+ "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+ "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+ "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+ "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+ "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+ "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+ "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+ "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+ "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+ "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+ "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@gar/promisify": {
@@ -61,6 +475,48 @@
"node": ">= 6"
}
},
+ "node_modules/@types/bluebird": {
+ "version": "3.5.42",
+ "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.42.tgz",
+ "integrity": "sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/knex": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@types/knex/-/knex-0.15.2.tgz",
+ "integrity": "sha512-mw8OT8v+FK0SsgDdmio2XSkEM/yLD7ybFtiqW7I65EDTlr2aZtG+p9FhryErpNJDJ2FEXgQhe3JVBG0Gh7YbvQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/bluebird": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/nunjucks": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.2.6.tgz",
+ "integrity": "sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/a-sync-waterfall": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
@@ -88,9 +544,9 @@
}
},
"node_modules/agentkeepalive": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
- "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -124,20 +580,6 @@
"node": ">=8"
}
},
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "devOptional": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
@@ -170,8 +612,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "devOptional": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -193,19 +635,6 @@
],
"license": "MIT"
},
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@@ -230,26 +659,13 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "devOptional": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -304,31 +720,6 @@
"node": ">= 10"
}
},
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@@ -358,21 +749,27 @@
"color-support": "bin.js"
}
},
+ "node_modules/colorette": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
+ "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==",
+ "license": "MIT"
+ },
"node_modules/commander": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
- "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"license": "MIT",
"engines": {
- "node": ">= 6"
+ "node": ">=14"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "devOptional": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true
},
"node_modules/console-control-strings": {
"version": "1.1.0",
@@ -382,19 +779,18 @@
"optional": true
},
"node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">=18"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
@@ -449,9 +845,9 @@
}
},
"node_modules/dotenv": {
- "version": "16.4.5",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
- "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+ "version": "16.4.7",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -503,6 +899,64 @@
"license": "MIT",
"optional": true
},
+ "node_modules/esbuild": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+ "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.23.1",
+ "@esbuild/android-arm": "0.23.1",
+ "@esbuild/android-arm64": "0.23.1",
+ "@esbuild/android-x64": "0.23.1",
+ "@esbuild/darwin-arm64": "0.23.1",
+ "@esbuild/darwin-x64": "0.23.1",
+ "@esbuild/freebsd-arm64": "0.23.1",
+ "@esbuild/freebsd-x64": "0.23.1",
+ "@esbuild/linux-arm": "0.23.1",
+ "@esbuild/linux-arm64": "0.23.1",
+ "@esbuild/linux-ia32": "0.23.1",
+ "@esbuild/linux-loong64": "0.23.1",
+ "@esbuild/linux-mips64el": "0.23.1",
+ "@esbuild/linux-ppc64": "0.23.1",
+ "@esbuild/linux-riscv64": "0.23.1",
+ "@esbuild/linux-s390x": "0.23.1",
+ "@esbuild/linux-x64": "0.23.1",
+ "@esbuild/netbsd-x64": "0.23.1",
+ "@esbuild/openbsd-arm64": "0.23.1",
+ "@esbuild/openbsd-x64": "0.23.1",
+ "@esbuild/sunos-x64": "0.23.1",
+ "@esbuild/win32-arm64": "0.23.1",
+ "@esbuild/win32-ia32": "0.23.1",
+ "@esbuild/win32-x64": "0.23.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/esm": {
+ "version": "3.2.25",
+ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+ "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -518,19 +972,6 @@
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT"
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -571,6 +1012,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gauge": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
@@ -592,6 +1042,34 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
+ "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/getopts": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz",
+ "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==",
+ "license": "MIT"
+ },
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@@ -602,6 +1080,7 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"optional": true,
"dependencies": {
@@ -619,19 +1098,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "devOptional": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -639,16 +1105,6 @@
"license": "ISC",
"optional": true
},
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
@@ -656,6 +1112,18 @@
"license": "ISC",
"optional": true
},
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
@@ -735,13 +1203,6 @@
],
"license": "BSD-3-Clause"
},
- "node_modules/ignore-by-default": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
- "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -793,6 +1254,15 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
+ "node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
@@ -807,27 +1277,19 @@
"node": ">= 12"
}
},
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "devOptional": true,
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
- "binary-extensions": "^2.0.0"
+ "hasown": "^2.0.2"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-fullwidth-code-point": {
@@ -840,19 +1302,6 @@
"node": ">=8"
}
},
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-lambda": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
@@ -860,16 +1309,6 @@
"license": "MIT",
"optional": true
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -884,6 +1323,63 @@
"license": "MIT",
"optional": true
},
+ "node_modules/knex": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz",
+ "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==",
+ "license": "MIT",
+ "dependencies": {
+ "colorette": "2.0.19",
+ "commander": "^10.0.0",
+ "debug": "4.3.4",
+ "escalade": "^3.1.1",
+ "esm": "^3.2.25",
+ "get-package-type": "^0.1.0",
+ "getopts": "2.3.0",
+ "interpret": "^2.2.0",
+ "lodash": "^4.17.21",
+ "pg-connection-string": "2.6.2",
+ "rechoir": "^0.8.0",
+ "resolve-from": "^5.0.0",
+ "tarn": "^3.0.2",
+ "tildify": "2.0.0"
+ },
+ "bin": {
+ "knex": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "mysql": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "pg-native": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ },
+ "tedious": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -941,8 +1437,8 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "devOptional": true,
"license": "ISC",
+ "optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -1076,19 +1572,18 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/napi-build-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
- "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"optional": true,
"engines": {
@@ -1096,9 +1591,9 @@
}
},
"node_modules/node-abi": {
- "version": "3.62.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
- "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
+ "version": "3.73.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.73.0.tgz",
+ "integrity": "sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
@@ -1108,13 +1603,10 @@
}
},
"node_modules/node-addon-api": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
- "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
- "license": "MIT",
- "engines": {
- "node": "^16 || ^18 || >= 20"
- }
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "license": "MIT"
},
"node_modules/node-gyp": {
"version": "8.4.1",
@@ -1141,35 +1633,6 @@
"node": ">= 10.12.0"
}
},
- "node_modules/nodemon": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
- "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.5.2",
- "debug": "^4",
- "ignore-by-default": "^1.0.1",
- "minimatch": "^3.1.2",
- "pstree.remy": "^1.1.8",
- "semver": "^7.5.3",
- "simple-update-notifier": "^2.0.0",
- "supports-color": "^5.5.0",
- "touch": "^3.1.0",
- "undefsafe": "^2.0.5"
- },
- "bin": {
- "nodemon": "bin/nodemon.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nodemon"
- }
- },
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -1186,16 +1649,6 @@
"node": ">=6"
}
},
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/npmlog": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
@@ -1238,6 +1691,15 @@
}
}
},
+ "node_modules/nunjucks/node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1273,23 +1735,22 @@
"node": ">=0.10.0"
}
},
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
+ "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==",
+ "license": "MIT"
},
"node_modules/prebuild-install": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
- "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
@@ -1297,7 +1758,7 @@
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^1.0.1",
+ "napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
@@ -1333,17 +1794,10 @@
"node": ">=10"
}
},
- "node_modules/pstree.remy": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
- "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/pump": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
- "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+ "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
@@ -1379,17 +1833,55 @@
"node": ">= 6"
}
},
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "devOptional": true,
+ "node_modules/rechoir": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
+ "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
"license": "MIT",
"dependencies": {
- "picomatch": "^2.2.1"
+ "resolve": "^1.20.0"
},
"engines": {
- "node": ">=8.10.0"
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/retry": {
@@ -1406,6 +1898,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"optional": true,
"dependencies": {
@@ -1446,9 +1939,9 @@
"optional": true
},
"node_modules/semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -1516,19 +2009,6 @@
"simple-concat": "^1.0.0"
}
},
- "node_modules/simple-update-notifier": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
- "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@@ -1660,17 +2140,16 @@
"node": ">=0.10.0"
}
},
- "node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
"engines": {
- "node": ">=4"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tar": {
@@ -1691,9 +2170,9 @@
}
},
"node_modules/tar-fs": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
- "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
+ "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
@@ -1733,27 +2212,42 @@
"node": ">=8"
}
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "devOptional": true,
+ "node_modules/tarn": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
+ "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/tildify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
+ "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==",
"license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
"engines": {
- "node": ">=8.0"
+ "node": ">=8"
}
},
- "node_modules/touch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
- "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "node_modules/tsx": {
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
+ "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.23.0",
+ "get-tsconfig": "^4.7.5"
+ },
"bin": {
- "nodetouch": "bin/nodetouch.js"
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
}
},
"node_modules/tunnel-agent": {
@@ -1768,10 +2262,23 @@
"node": "*"
}
},
- "node_modules/undefsafe": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
- "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "node_modules/typescript": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
diff --git a/package.json b/package.json
index a33361c..0df0837 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,22 @@
{
"name": "keuknet",
- "version": "2.4.1",
+ "version": "3.0.0-beta-1",
"description": "A webserver and client program for easily managing a WireGuard-network in a client-server setting.",
- "main": "index.js",
+ "main": "src/index.ts",
+ "type": "module",
"scripts": {
- "start": "node index.js",
- "dev": "sudo npx nodemon index.js -e . --ignore ./data/"
+ "start": "tsx src/index.ts",
+ "dev": "sudo tsx watch --exclude './data/**/*' --clear-screen=false ./src/index.ts"
},
"author": "Keukeiland, Fizitzfux",
"license": "MPL-2.0",
"dependencies": {
- "cookie": "^0.6.0",
+ "cookie": "^1.0.2",
"dotenv": "^16.4.5",
+ "knex": "^3.1.0",
"nunjucks": "^3.2.4",
- "sqlite3": "^5.1.6"
+ "sqlite3": "^5.1.6",
+ "typescript": "^5.7.3"
},
"repository": {
"type": "git",
@@ -24,6 +27,10 @@
},
"homepage": "https://keuk.net/",
"devDependencies": {
- "nodemon": "^3.1.0"
+ "@types/cookie": "^0.6.0",
+ "@types/knex": "^0.15.2",
+ "@types/node": "^22.10.7",
+ "@types/nunjucks": "^3.2.6",
+ "tsx": "^4.19.2"
}
}
diff --git a/src/classes/extension.ts b/src/classes/extension.ts
new file mode 100644
index 0000000..7eefdfe
--- /dev/null
+++ b/src/classes/extension.ts
@@ -0,0 +1,228 @@
+import { Environment } from "nunjucks"
+import { Tables } from "./tables.ts"
+import { unpack } from "../util.ts"
+
+export abstract class ExtensionBase implements Extension {
+ admin_only = false
+ tables = false
+ initialized_deps: DependencyMap = new DependencyMapImpl()
+ name: Extension['name'] = "default_name"
+ title: Extension['title'] = "Default Title"
+
+ static async init(inst: ExtensionBase, context: InitContext): Promise {
+ let global: any = context.modules
+ let path = context.path
+ let knex = context.knex
+
+ inst.initialized_deps = new DependencyMapImpl(global, context)
+
+ // Init db
+ if (inst.tables) {
+ // init tables
+ let tables = new ((await import(`${path}tables`)).default)(knex, inst.initialized_deps.get('Knex'), inst.name) as Tables
+ let result = await tables.migrate()
+ return result
+ }
+ }
+
+ init: Extension['init'] = (context) => {
+ return ExtensionBase.init(this, context)
+ }
+
+ /**
+ * @returns true if the path requires being logged in, else false
+ */
+ requires_login: Extension['requires_login'] = (path) => {
+ return true
+ }
+
+ requires_admin: Extension['requires_admin'] = (path) => {
+ return this.admin_only
+ }
+
+ handle_req: Extension['handle_req'] = async (ctx: Context) => {
+ ctx.context.extension = this as unknown as Extension
+ return await this.handle(ctx)
+ }
+
+ abstract handle: Extension['handle']
+
+ return: Extension['return'] = (ctx, err, location, err_code=500) => {
+ const {res} = ctx
+ if (err) {
+ res.writeHead(err_code)
+ return res.end()
+ }
+ let code = 200
+ let args: any = {}
+
+ if (location) {
+ code = 307
+ args['Location'] = location
+ }
+
+ res.writeHead(code, args)
+ return res.end()
+ }
+
+ return_text: Extension['return_text'] = (ctx, item) => {
+ const {req, res} = ctx
+ let [texts, nj, content]: [any, Environment, any] = this.initialized_deps.massGet('texts', 'nj', 'content')
+
+ ctx.context.__render_item = texts[item]
+ nj.renderString(
+ '{% extends "layout.html" %}{% block body %}{{__render_item |safe}}{% endblock %}',
+ ctx.context, (err: Error | null, data: FileData) => {
+ if (err) {
+ res.writeHead(500)
+ return res.end()
+ }
+ res.writeHead(200, content.HTML)
+
+ if (data !== null)
+ return res.end(data)
+ else
+ return res.end()
+ })
+ }
+
+ return_html: Extension['return_html'] = (ctx, item, err, err_code=500, success_code=200, headers=undefined) => {
+ const {req, res} = ctx
+ let [nj, content] = this.get_dependencies('nj', 'content')
+
+ if (err) {
+ res.writeHead(err_code)
+ return res.end()
+ }
+
+ headers = {...content.HTML, ...headers}
+
+ nj.render(this.name+'/'+item+'.html', ctx.context, (err: null|Error, data: FileData) => {
+ if (err) {
+ res.writeHead(err_code)
+ return res.end()
+ }
+ res.writeHead(success_code, headers)
+
+ if (data !== null)
+ return res.end(data)
+ else
+ return res.end()
+ })
+ }
+
+ return_file: Extension['return_file'] = async (ctx, file) => {
+ const {res} = ctx
+ let [fetch, content]: [Fetch, ContentType] = this.get_dependencies('Fetch', 'content')
+
+ const [result, err] = await fetch.file(file).then(unpack<[string, string]>)
+ if (err) {
+ res.writeHead(404)
+ res.end()
+ return
+ }
+
+ const [data, filetype] = result
+
+ // @ts-ignore 7053
+ res.writeHead(200, content[filetype])
+ res.end(data)
+ }
+
+ return_data: Extension['return_data'] = (ctx, data, err, headers, err_code=404) => {
+ const {res} = ctx
+
+ if (err) {
+ res.writeHead(err_code)
+ return res.end()
+ }
+ let args: any = {"Content-Type": "text/plain charset utf-8"}
+
+ if (headers)
+ args = headers
+
+ res.writeHead(200, args)
+ return res.end(data)
+ }
+
+ set_cookie: Extension['set_cookie'] = (key, value, secure=false) => {
+ let [cookie] = this.get_dependencies('cookie')
+
+ if (secure)
+ return cookie.serialize(
+ key,
+ value, {
+ secure: true,
+ httpOnly: true
+ }
+ )
+ else
+ return cookie.serialize(
+ key,
+ value
+ )
+ }
+
+ del_cookie: Extension['del_cookie'] = (key) => {
+ let [cookie] = this.get_dependencies('cookie')
+
+ return cookie.serialize(
+ key,
+ '', {
+ expires: new Date(1)
+ }
+ )
+ }
+
+ get_dependencies: Extension['get_dependencies'] = (...args) => this.initialized_deps.massGet(...args)
+}
+
+class DependencyMapImpl implements DependencyMap {
+ private global: any
+ private context: InitContext | {}
+ private map = new Map()
+
+ constructor(global?: any, context?: InitContext) {
+ if (global !== undefined && context !== undefined) {
+ this.global = global
+ this.context = context
+ }
+ else {
+ this.global = {}
+ this.context = {}
+ }
+ }
+
+ forEach = this.map.forEach
+ has = this.map.has
+
+ set(key: string, value: any): this {
+ if (!this.map.has(key))
+ this.map.set(key, value)
+ return this
+ }
+
+ get(key: string) {
+ if (!this.map.has(key)) {
+ // Assumes type of not instantiated modules to be `Function`
+ let dep: any
+ if (typeof this.global[key] === typeof Function) {
+ dep = new this.global[key]
+ dep.init(this.context)
+ }
+ else
+ dep = this.global[key]
+
+ this.map.set(key, dep)
+ }
+ return this.map.get(key)
+ }
+
+ massGet(...items: S): VariableSizeArray {
+ let result = [] as VariableSizeArray
+ items.forEach((v, _, __) =>
+ result.push(this.get(v))
+ )
+ return result
+ }
+}
diff --git a/src/classes/tables.ts b/src/classes/tables.ts
new file mode 100644
index 0000000..4c01992
--- /dev/null
+++ b/src/classes/tables.ts
@@ -0,0 +1,123 @@
+import { Knex as rawKnex } from "knex"
+import { Knex } from "../modules.ts"
+
+export abstract class Tables {
+ raw_knex: rawKnex
+ knex: Knex
+ prefix: string
+
+ constructor(raw_knex: rawKnex, knex: Knex, prefix: string) {
+ this.raw_knex = raw_knex
+ this.knex = knex
+ this.prefix = prefix
+ }
+
+ migrate(): Promise {
+ return new Promise(async (resolve, reject) => {
+ let versions = this.versions(new Map())
+ let migrations = this.migrations(this.knex, new Map())
+
+ // Serialize??
+ this.raw_knex('db_table_versions')
+ .select('table_id', 'version')
+ .then(async (data) => {
+ let current_versions = new Map()
+ for (const row of data) {
+ current_versions.set(row.table_id, row.version)
+ }
+
+ for (var [table, latest] of versions) {
+ let table_id: TableId | string = `_${this.prefix}_${table}`
+
+ if (!current_versions.has(table_id)) {
+ console.log(`Adding table ${table_id}`)
+
+ if (!migrations.has(table))
+ return reject(`Missing entry in migrations for table '${table_id}'`)
+ let migration = migrations.get(table) as MigrationRecord
+ if (!migration[0])
+ return reject(`Missing migration entry 0 for table '${table}'`)
+ await migration[0]()
+
+ current_versions.set(table_id, 0)
+ this.raw_knex('db_table_versions')
+ .insert({
+ table_id,
+ version: current_versions.get(table_id),
+ })
+ .then(
+ () => {
+ console.log(`Added table ${table_id}`)
+ }, (err) => {
+ if (err) return reject(`Failed adding table version identifier. ${err}`)
+ }
+ )
+ }
+
+ let current_version: number
+ while (true) {
+ current_version = current_versions.get(table_id) ?? -1
+ if (current_version < 0 || current_version == latest) break
+ let new_version = current_version +1
+
+ console.log(`Upgrading table ${table_id} from ${current_version} to ${new_version}`)
+ try {
+ let migration = Tables.getMigration(migrations, table, new_version)
+ if (migration instanceof Error) return reject(migration)
+ await migration()
+ }
+ catch (err) {
+ console.error(err)
+ break
+ }
+
+ current_versions.set(table_id, new_version)
+
+ this.raw_knex('db_table_versions')
+ .update({
+ version: new_version,
+ })
+ .whereIn('table_id', [table_id])
+ .then(
+ () => {
+ console.log(`Upgraded table ${table_id} to ${new_version}`)
+ },
+ (err) => {
+ return reject(`Failed updating table version identifier. ${err}`)
+ }
+ )
+ }
+ }
+ })
+ resolve()
+ })
+ }
+
+ private static getMigration(migrations: MigrationMap, table: string, version: number): Migration | Error {
+ if (migrations.has(table)) {
+ let migration = migrations.get(table) as MigrationRecord
+ let result = migration[version]
+ if (!(result === undefined)) {
+ return result as Migration
+ }
+ else
+ return new Error(`Missing migration entry ${version} for table '${table}'`)
+ }
+ else
+ return new Error(`Missing entry in migrations for table '${table}'`)
+ }
+
+ versions(versions: VersionMap): VersionMap {
+ return versions
+ }
+ migrations(knex: Knex, migrations: MigrationMap): MigrationMap {
+ return migrations
+ }
+}
+
+export type Migration = Function
+export type MigrationRecord = Record
+export type MigrationMap = Map
+export type VersionMap = Map
+
+type TableId = `${string}.${string}`
diff --git a/www/extensions/admin/index.html b/src/extensions/admin/index.html
similarity index 100%
rename from www/extensions/admin/index.html
rename to src/extensions/admin/index.html
diff --git a/src/extensions/admin/index.ts b/src/extensions/admin/index.ts
new file mode 100644
index 0000000..b547f58
--- /dev/null
+++ b/src/extensions/admin/index.ts
@@ -0,0 +1,11 @@
+import { ExtensionBase } from "../../modules.ts"
+
+export default class extends ExtensionBase {
+ override name = 'admin'
+ override title = 'Admin'
+ override admin_only = true
+
+ override handle: Extension['handle'] = (ctx) => {
+ this.return_html(ctx, 'index')
+ }
+}
diff --git a/www/extensions/chat/index.html b/src/extensions/chat/index.html
similarity index 100%
rename from www/extensions/chat/index.html
rename to src/extensions/chat/index.html
diff --git a/src/extensions/chat/index.ts b/src/extensions/chat/index.ts
new file mode 100644
index 0000000..6876236
--- /dev/null
+++ b/src/extensions/chat/index.ts
@@ -0,0 +1,60 @@
+import { ExtensionBase } from "../../modules.ts"
+
+export default class extends ExtensionBase {
+ override name = 'chat'
+ override title = 'Chat'
+
+ messages: {user: {name: any, pfp_code: any}, time: any, content: any}[] = [{
+ user: {name:'SYSTEM',pfp_code:'seed=SYSTEM'},
+ time:(new Date()).toLocaleTimeString('en-US', {hour12: false}),
+ content: 'Welcome to the chatroom!'
+ }]
+ last_got_id: {[user_id: number]: number} = {}
+
+ override handle: Extension['handle'] = (ctx) => {
+ var location = ctx.path.shift()
+
+ const user_id = ctx.context.user?.id as number
+
+ if (!location) {
+ if (ctx.data && ctx.data.form.message) {
+ var message = ctx.data.form.message.substring(0,255)
+ var now = (new Date()).toLocaleTimeString('en-US', {hour12: false})
+ this.messages.push({
+ user: {
+ name: ctx.context.user?.name,
+ pfp_code: ctx.context.user?.pfp_code,
+ },
+ time: now,
+ content: message,
+ })
+ }
+ ctx.context.chat = this.messages
+ this.last_got_id[user_id] = this.messages.length
+ return this.return_html(ctx, 'index')
+ }
+ else if (location == 'getnew') {
+ var part = this.last_got_id.hasOwnProperty(user_id) ? this.last_got_id[user_id] : 0
+ this.last_got_id[user_id] = this.messages.length
+ return this.return_data(ctx, `{"messages":${JSON.stringify(this.messages.slice(part))}}`)
+ }
+ else if (location == 'postmessage') {
+ if (ctx.data && ctx.data.form.message) {
+ var message = ctx.data.form.message.substring(0,255)
+ var now = (new Date()).toLocaleTimeString('en-US', {hour12: false})
+ this.messages.push({
+ user: {
+ name: ctx.context.user?.name,
+ pfp_code: ctx.context.user?.pfp_code,
+ },
+ time: now,
+ content: message,
+ })
+ }
+ return this.return(ctx)
+ }
+ else {
+ return this.return_file(ctx, location)
+ }
+ }
+}
diff --git a/www/extensions/chat/static/index.css b/src/extensions/chat/static/index.css
similarity index 100%
rename from www/extensions/chat/static/index.css
rename to src/extensions/chat/static/index.css
diff --git a/www/extensions/nothing/index.html b/src/extensions/nothing/index.html
similarity index 100%
rename from www/extensions/nothing/index.html
rename to src/extensions/nothing/index.html
diff --git a/src/extensions/nothing/index.ts b/src/extensions/nothing/index.ts
new file mode 100644
index 0000000..1af0a29
--- /dev/null
+++ b/src/extensions/nothing/index.ts
@@ -0,0 +1,10 @@
+import { ExtensionBase } from "../../modules.ts"
+
+export default class extends ExtensionBase {
+ override name = 'nothing'
+ override title = 'Nothing'
+
+ override handle: Extension['handle'] = (ctx) => {
+ this.return_html(ctx, 'index')
+ }
+}
diff --git a/www/extensions/profile/devices/android.html b/src/extensions/profile/devices/android.html
similarity index 100%
rename from www/extensions/profile/devices/android.html
rename to src/extensions/profile/devices/android.html
diff --git a/www/extensions/profile/devices/ios.html b/src/extensions/profile/devices/ios.html
similarity index 100%
rename from www/extensions/profile/devices/ios.html
rename to src/extensions/profile/devices/ios.html
diff --git a/www/extensions/profile/devices/linux.html b/src/extensions/profile/devices/linux.html
similarity index 100%
rename from www/extensions/profile/devices/linux.html
rename to src/extensions/profile/devices/linux.html
diff --git a/www/extensions/profile/devices/macos.html b/src/extensions/profile/devices/macos.html
similarity index 100%
rename from www/extensions/profile/devices/macos.html
rename to src/extensions/profile/devices/macos.html
diff --git a/www/extensions/profile/devices/windows.html b/src/extensions/profile/devices/windows.html
similarity index 100%
rename from www/extensions/profile/devices/windows.html
rename to src/extensions/profile/devices/windows.html
diff --git a/www/extensions/profile/edit.html b/src/extensions/profile/edit.html
similarity index 100%
rename from www/extensions/profile/edit.html
rename to src/extensions/profile/edit.html
diff --git a/www/extensions/profile/index.html b/src/extensions/profile/index.html
similarity index 100%
rename from www/extensions/profile/index.html
rename to src/extensions/profile/index.html
diff --git a/src/extensions/profile/index.ts b/src/extensions/profile/index.ts
new file mode 100644
index 0000000..3a76bc2
--- /dev/null
+++ b/src/extensions/profile/index.ts
@@ -0,0 +1,146 @@
+import crypto from 'crypto'
+import { ExtensionBase, Knex } from '../../modules.ts'
+import { unpack } from '../../util.ts'
+
+export default class extends ExtensionBase {
+ override name = 'profile'
+ override title = 'Network'
+ override tables = true
+ wg: any = null
+ wg_config: any = null
+
+
+ override init: Extension['init'] = async (context) => {
+ let config = context.modules.config
+ let data_path = context.data_path
+ this.wg_config = context.modules.wg_config
+
+ this.wg = await import('./wireguard.js')
+ this.wg.init(data_path, this.wg_config, config.tmp_dir)
+
+ return ExtensionBase.init(this, context)
+ }
+
+ override requires_login: Extension['requires_login'] = (path) => {
+ if (path.at(0) == 'getconf') {
+ return false
+ }
+ return true
+ }
+
+ override handle: Extension['handle'] = async (ctx) => {
+ let [knex]: [Knex] = this.get_dependencies('Knex')
+ var location = ctx.path.shift()
+
+ switch (location) {
+ case '':
+ case undefined: {
+ const [profiles, err] = await knex.query('_device')
+ .select('*')
+ .where('user_id', ctx.context.user?.id)
+ .then(unpack)
+
+ ctx.context.profiles = profiles
+ ctx.context.connected_ip = ctx.ip.startsWith(this.wg_config.subnet) ? ctx.ip : false
+ return this.return_html(ctx, 'index', err)
+ }
+ case 'delete': {
+ // Check ownership
+ let user_owns = await this.owns(ctx.context.user, ctx.args.get('uuid'))
+ if (!user_owns)
+ return this.return(ctx, new Error(), undefined, 404)
+
+ // Delete db entry
+ await knex.query('_device').delete().where('uuid', ctx.args.get('uuid'))
+
+ // Delete wireguard profile
+ this.wg.remove(ctx.args.get('uuid'), () => {
+ return this.return(ctx, undefined, location='/profile')
+ })
+ break
+ }
+ case 'add': {
+ // Get uuid
+ let uuid = crypto.randomUUID()
+
+ // Get IP suffix
+ let [data, err] = await knex.query('_device')
+ .max('id')
+ .then(unpack)
+ let id = (data[0]['max(`id`)'] ?? 0) +2
+
+ // Register wireguard link
+ this.wg.create(uuid, id, async (ip: string, err: Error) => {
+ if (err) return this.return(ctx, err)
+ // Insert in db
+ await knex.query('_device').insert({user_id: ctx.context.user?.id, uuid, ip} as never)
+
+ return this.return(ctx, undefined, location='/profile')
+ })
+ break
+ }
+ case 'getconf': {
+ // Get uuid
+ let uuid = ctx.args.get('uuid')
+
+ // Get config
+ this.wg.getConfig(uuid, async (data: FileData, err: Error) => {
+ if (err)
+ return this.return(ctx, new Error(), undefined, 404)
+
+ // Mark as installed
+ await knex.query('_device')
+ .update({installed: true} as never)
+ .where('uuid', uuid)
+
+ return this.return_data(ctx, data, undefined, {"Content-Type": "text/plain charset utf-8", "Content-Disposition": `attachment; filename="keuknet.conf"`})
+ })
+ break
+ }
+ case 'install': {
+ ctx.context.device = ctx.args.get('device')
+ ctx.context.uuid = ctx.args.get('uuid')
+ return this.return_html(ctx, 'install')
+ }
+ case 'rename': {
+ if (ctx.data) {
+ // Check ownership
+ let user_owns = await this.owns(ctx.context.user, ctx.args.get('uuid'))
+ if (!user_owns)
+ return this.return(ctx, new Error(), undefined, 404)
+
+ // Change name
+ await knex.query('_device')
+ .update({name: ctx.data.form.post_data} as never)
+ .where('uuid', ctx.args.get('uuid'))
+
+ return this.return(ctx, undefined, location='/profile')
+ }
+ else {
+ ctx.context = {...ctx.context, item:"new name",action:ctx.req.url,destination:"/profile"}
+ this.return_html(ctx, 'edit')
+ }
+ break
+ }
+ default: {
+ return this.return_file(ctx, location)
+ }
+ }
+ }
+
+ owns = async (user?: User, uuid?: string) => {
+ let [knex]: [Knex] = this.get_dependencies('Knex')
+
+ if (!uuid)
+ return false
+
+ const [data, err] = await knex.query('_device')
+ .select('id')
+ .where('user_id', user?.id)
+ .andWhere('uuid', uuid)
+ .first()
+ .then(unpack)
+
+ return !!data
+ }
+}
diff --git a/www/extensions/profile/install.html b/src/extensions/profile/install.html
similarity index 100%
rename from www/extensions/profile/install.html
rename to src/extensions/profile/install.html
diff --git a/www/extensions/profile/static/index.css b/src/extensions/profile/static/index.css
similarity index 100%
rename from www/extensions/profile/static/index.css
rename to src/extensions/profile/static/index.css
diff --git a/src/extensions/profile/tables.ts b/src/extensions/profile/tables.ts
new file mode 100644
index 0000000..c92609b
--- /dev/null
+++ b/src/extensions/profile/tables.ts
@@ -0,0 +1,28 @@
+import { MigrationMap, Tables, VersionMap } from '../../classes/tables.ts'
+import { Knex } from '../../modules.ts'
+
+export default class extends Tables {
+ override versions(versions: VersionMap) {
+ versions.set('device', 0)
+
+ return versions
+ }
+
+ override migrations(knex: Knex, migrations: MigrationMap) {
+ migrations.set('device', {
+ 0: async ()=>{
+ await knex.schema().createTable('_device', (table) => {
+ table.increments('id').primary()
+ table.integer('user_id').notNullable()
+ table.foreign('user_id', 'fk_user_id').references('_root_user.id')
+ table.string('name')
+ table.uuid('uuid').notNullable()
+ table.string('ip').notNullable()
+ table.boolean('installed').notNullable().defaultTo(false).checkIn(['0','1'])
+ })
+ }
+ })
+
+ return migrations
+ }
+}
diff --git a/www/extensions/profile/wireguard.js b/src/extensions/profile/wireguard.js
similarity index 94%
rename from www/extensions/profile/wireguard.js
rename to src/extensions/profile/wireguard.js
index 2d03b6e..8b1ae51 100644
--- a/www/extensions/profile/wireguard.js
+++ b/src/extensions/profile/wireguard.js
@@ -1,6 +1,6 @@
-const { exec } = require('child_process')
-const fs = require('fs')
-const readline = require('readline')
+import { exec } from 'child_process'
+import fs from 'fs'
+import readline from 'readline'
var tmp_dir = ""
var configs_dir = ""
@@ -8,7 +8,7 @@ var file_lines = []
var configs = {}
var config = {}
-exports.init = function (path, wg_config, tmp_path) {
+export function init(path, wg_config, tmp_path) {
config = wg_config
configs_dir = path
tmp_dir = tmp_path
@@ -104,10 +104,10 @@ function __save_config() {
* @param {Function} callback the callback to call when done
*/
function __remove(uuid, callback) {
- loc = configs[uuid]
+ let loc = configs[uuid]
if (!loc) return callback()
// remove from server config
- amount = loc.end - loc.start +1
+ let amount = loc.end - loc.start +1
file_lines.splice(loc.start, amount)
__save_config()
// delete config file if still exists
@@ -153,7 +153,7 @@ function __add(uuid, pubkey, prekey, privkey, ip, callback) {
})
}
-exports.create = function (uuid, ip, callback) {
+export function create(uuid, ip, callback) {
ip = config.subnet + ip.toString(16)
exec("wg genkey", function (err, priv, stderr) {
@@ -173,13 +173,13 @@ exports.create = function (uuid, ip, callback) {
})
}
-exports.delete = function (uuid, callback) {
+export function remove(uuid, callback) {
__remove(uuid, function () {
return callback()
})
}
-exports.getConfig = function (uuid, callback) {
+export function getConfig(uuid, callback) {
fs.readFile(`${configs_dir+uuid}.conf`, 'utf8', function (err, data) {
if (err) return callback(undefined, err)
fs.unlink(`${configs_dir+uuid}.conf`, function (err) {
diff --git a/www/extensions/root/content/getting-started.html b/src/extensions/root/content/getting-started.html
similarity index 100%
rename from www/extensions/root/content/getting-started.html
rename to src/extensions/root/content/getting-started.html
diff --git a/www/extensions/root/index.html b/src/extensions/root/index.html
similarity index 100%
rename from www/extensions/root/index.html
rename to src/extensions/root/index.html
diff --git a/src/extensions/root/index.ts b/src/extensions/root/index.ts
new file mode 100644
index 0000000..ee737e1
--- /dev/null
+++ b/src/extensions/root/index.ts
@@ -0,0 +1,246 @@
+import crypto from 'crypto'
+import { ExtensionBase, Knex } from "../../modules.ts"
+import { readdirSync } from "fs"
+import { unpack } from '../../util.ts'
+
+
+export default class extends ExtensionBase implements RootExtension {
+ override name = 'root'
+ override title = 'Home'
+ override tables = true
+
+ favicons: string[] = []
+ favicons_path: string
+
+ ip_scope: string
+ salt: string
+
+
+ override init = (context: InitContext) => {
+ let modules = context.modules
+ let data_path = context.data_path
+ this.ip_scope = context.modules.wg_config.ip_scope
+ this.salt = context.modules.config.salt
+
+ this.favicons_path = data_path+'favicons/'
+ try {
+ this.favicons.push(...readdirSync(this.favicons_path))
+ } catch {
+ (new modules.Log as Log).err(`No favicons found in '${this.favicons_path}'`)
+ }
+
+ return ExtensionBase.init(this, context)
+ }
+
+ override requires_login: Extension['requires_login'] = (path) => {
+ if (path.at(0) == '_') {
+ return true
+ }
+ return false
+ }
+
+ override handle: Extension['handle'] = async (ctx) => {
+ var location = ctx.path.shift()
+ let [knex]: [Knex] = this.get_dependencies('Knex')
+
+ switch (location) {
+ case '':
+ case undefined: {
+ if (!ctx.context.user) {
+ return this.return_text(ctx, 'index')
+ }
+ return this.return_html(ctx, 'user')
+ }
+ case 'login': {
+ // If user not logged in
+ if (!ctx.context.user) {
+ // Attempt
+ if (ctx.data) {
+ let form = ctx.data.form
+ // Login
+ if (form.login) {
+ let auth = '';
+ if (form.username && form.password) {
+ auth = Buffer.from(form.username+":"+form.password).toString('base64')
+ }
+ return this.return_html(ctx, 'login', undefined, 500, 303, {
+ "Location": "/login",
+ "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true)
+ })
+ }
+ // Register
+ else if (form.register) {
+ this.addUser(form.username, form.password, (err?: Error) => {
+ // if invalid credentials
+ if (err) {
+ ctx.context.auth_err = err
+ return this.return_html(ctx, 'login')
+ }
+ // success
+ else {
+ let auth = Buffer.from(form.username+":"+form.password).toString('base64')
+ return this.return_html(ctx, 'login', undefined, 500, 303, {
+ "Location": "/",
+ "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true)
+ })
+ }
+ })
+ return
+ }
+ }
+ // First load
+ return this.return_html(ctx, 'login', undefined, 500, 200, {
+ "Set-Cookie": this.del_cookie('auth')
+ })
+ }
+ // if logged in
+ ctx.res.writeHead(307, {"Location": "/"})
+ ctx.res.end()
+ return
+ }
+ case 'logout': {
+ if (ctx.context.user) {
+ // log user out and redirect
+ ctx.res.writeHead(307, {
+ "Location": "/",
+ "Set-Cookie": this.del_cookie('auth')
+ })
+ ctx.res.end()
+ return
+ }
+ // if user is logged out
+ ctx.res.writeHead(307, {"Location": "/"})
+ ctx.res.end()
+ return
+ }
+ case '_': {
+ if (ctx.context.user) {
+ var item = ctx.path.shift()
+ switch (item) {
+ case 'pfp': {
+ var args = ctx.req.url.split('?').at(1)
+ if (args) {
+ try {
+ args = decodeURIComponent(args)
+ } catch {}
+
+ const head: [number, {}] = await knex.query('user')
+ .update('pfp_code', args)
+ .where('id', ctx.context.user.id)
+ .then(
+ () => [307, {"Location": "/"}],
+ () => [500, {}]
+ )
+
+ ctx.res.writeHead(...head)
+ ctx.res.end()
+ return
+ }
+ else
+ return this.return_html(ctx, 'pfp')
+ }
+ }
+ }
+ break
+ }
+ default: {
+ // Templated html
+ if (location.startsWith('~'))
+ return this.return_html(ctx, 'content/'+location.split('~')[1], undefined, 404)
+ // Favicon
+ else if (this.favicons.includes(location))
+ return this.return_file(ctx, this.favicons_path+location)
+ // File
+ else
+ return this.return_file(ctx, location)
+ }
+ }
+ }
+
+ authenticate: RootExtension['authenticate'] = async (auth, ip, subnet) => {
+ let [knex]: [Knex] = this.get_dependencies('Knex')
+
+ if (auth) {
+ // Try to get name and password
+ const val = this.decrypt_auth(auth)
+ if (val instanceof Error)
+ return val
+
+ const [name, password] = val
+
+ // Auth using name and password
+ const [user, err] = await knex
+ .query('user')
+ .select('*')
+ .where('name', name)
+ .first()
+ .then(unpack)
+
+ if (user && password == user.password)
+ return user
+ else
+ return new Error('Wrong name or password')
+ }
+ else if (ip.startsWith(subnet)) {
+ // Try using IP-address if no name and password
+ const user = await knex
+ .query({u: 'user', p: '_profile_device'})
+ .select('u.*')
+ .join('_profile_device', 'u.id', '=', 'p.user_id')
+ .where('p.ip', ip)
+ .first()
+
+ return user
+ }
+ }
+
+ private decrypt_auth(auth: BasicAuth): [name: string, password: string] | Error {
+ // decode authentication string
+ let data = Buffer.from(auth.slice(6), 'base64').toString('utf-8')
+
+ // get name and password
+ let [name, password] = data.split(":", 2)
+ if (!name || !password) {
+ return new Error("Missing name or password")
+ }
+
+ // hash password
+ password = this.hash_pw(password)
+
+ return [name, password]
+ }
+
+ private hash_pw(password: string): string {
+ return crypto.pbkdf2Sync(password, this.salt, 10000, 128, 'sha512').toString('base64')
+ }
+
+ addUser(name: User['name'], password: User['password'], callback: (err?: Error) => void) {
+ let [knex]: [Knex] = this.get_dependencies('Knex')
+
+ password = this.hash_pw(password)
+ // Check if username is already taken
+ this.exists(name, (exists, err) => {
+ if (err) return callback(err)
+ if (exists) return callback(new Error("Username already taken"))
+ // add user to db
+ knex.query('user')
+ // @ts-expect-error
+ .insert({name, password, pfp_code: `seed=${name}`})
+ .then(() => callback(), (err) => callback(err))
+ })
+ }
+
+ private exists(name: User['name'], callback: (exists: boolean, err?: Error) => void): void {
+ let [knex]: [Knex] = this.get_dependencies('Knex')
+
+ // check if name already exists
+ knex.query('user')
+ .select('id')
+ .where('name', name)
+ .then((value) => {
+ callback(!!value.length)
+ }, (err) => {
+ callback(false, err)
+ })
+ }
+}
diff --git a/www/extensions/root/login.html b/src/extensions/root/login.html
similarity index 100%
rename from www/extensions/root/login.html
rename to src/extensions/root/login.html
diff --git a/www/extensions/root/pfp.html b/src/extensions/root/pfp.html
similarity index 100%
rename from www/extensions/root/pfp.html
rename to src/extensions/root/pfp.html
diff --git a/www/extensions/root/static/favicon.ico b/src/extensions/root/static/favicon.ico
similarity index 100%
rename from www/extensions/root/static/favicon.ico
rename to src/extensions/root/static/favicon.ico
diff --git a/www/extensions/root/static/index.css b/src/extensions/root/static/index.css
similarity index 100%
rename from www/extensions/root/static/index.css
rename to src/extensions/root/static/index.css
diff --git a/www/extensions/root/static/keuknet.png b/src/extensions/root/static/keuknet.png
similarity index 100%
rename from www/extensions/root/static/keuknet.png
rename to src/extensions/root/static/keuknet.png
diff --git a/www/extensions/root/static/pfp.js b/src/extensions/root/static/pfp.js
similarity index 100%
rename from www/extensions/root/static/pfp.js
rename to src/extensions/root/static/pfp.js
diff --git a/www/extensions/root/static/wallpaper.jpg b/src/extensions/root/static/wallpaper.jpg
similarity index 100%
rename from www/extensions/root/static/wallpaper.jpg
rename to src/extensions/root/static/wallpaper.jpg
diff --git a/src/extensions/root/tables.ts b/src/extensions/root/tables.ts
new file mode 100644
index 0000000..3430619
--- /dev/null
+++ b/src/extensions/root/tables.ts
@@ -0,0 +1,33 @@
+import { MigrationMap, Tables, VersionMap } from '../../classes/tables.ts'
+import { Knex } from '../../modules.ts'
+
+export default class extends Tables {
+ override versions(versions: VersionMap) {
+ versions.set('user', 1)
+
+ return versions
+ }
+
+ override migrations(knex: Knex, migrations: MigrationMap) {
+ migrations.set('user', {
+ 0: async ()=>{
+ await knex.schema()
+ .createTable('user', (table) => {
+ table.increments('id').primary()
+ table.string('name').notNullable().unique()
+ table.string('password').notNullable()
+ table.timestamp('registration_date').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP'))
+ table.boolean('is_admin').notNullable().defaultTo(false).checkIn(['0','1'])
+ })
+ },
+ 1: async ()=>{
+ await knex.schema()
+ .alterTable('user', (table) => {
+ table.string('pfp_code')
+ })
+ },
+ })
+
+ return migrations
+ }
+}
diff --git a/www/extensions/root/user.html b/src/extensions/root/user.html
similarity index 100%
rename from www/extensions/root/user.html
rename to src/extensions/root/user.html
diff --git a/src/extman.ts b/src/extman.ts
new file mode 100644
index 0000000..e337e46
--- /dev/null
+++ b/src/extman.ts
@@ -0,0 +1,19 @@
+import { Knex } from "knex"
+
+export async function load(modules: any, namespace: string, knex: Knex): Promise {
+ let context: InitContext = {
+ modules,
+ path: `${import.meta.dirname}/extensions/${namespace}/`,
+ data_path: `${import.meta.dirname}/../data/${namespace}/`,
+ name: namespace,
+ knex,
+ }
+
+ let ext = new (await import(`./extensions/${namespace}/index`)).default as Extension
+ let status = ext.init(context)
+
+ if (status instanceof Promise)
+ status.catch(err => console.error(`Failed initializing [${namespace}]: ${err}`))
+
+ return ext
+}
diff --git a/src/handle.ts b/src/handle.ts
new file mode 100644
index 0000000..952f9af
--- /dev/null
+++ b/src/handle.ts
@@ -0,0 +1,86 @@
+import { load } from "./extman.ts"
+import Log from "./modules/log.ts"
+import { unpack } from "./util.ts"
+
+let log = new Log(true)
+
+export default class implements Handle {
+ extensions_list = [
+ 'profile','nothing','admin','chat'
+ ]
+ root: RootExtension
+ wg_config: any
+ extensions = new Map()
+ admin_extensions = new Map()
+
+ constructor(modules: any) {
+ let config = modules.config
+ this.wg_config = modules.wg_config
+ let nj: Environment = modules.nj
+ nj.addGlobal('dicebear_host', config.dicebear_host)
+ nj.addGlobal('client_location', config.client_location)
+ }
+
+ init: Handle['init'] = async (modules, knex) => {
+ this.root = await load(modules, 'root', knex) as RootExtension
+
+ for (const path of this.extensions_list) {
+ try {
+ this.extensions.set(path, await load(modules, path, knex) as Extension)
+ } catch (err: any) {
+ log.err(`Unable to load extension '${path}':\n\t${err.message}\n${err.stack}`)
+ }
+ }
+
+ this.extensions.forEach((extension, name, m) => {
+ if (extension.admin_only) {
+ this.admin_extensions.set(name, extension)
+ this.extensions.delete(name)
+ }
+ })
+ }
+
+ main: Handle['main'] = async (partial_ctx: PartialContext) => {
+ let location = partial_ctx.path.shift() ?? ''
+
+ // set request context
+ let ctx: Context = {
+ ...partial_ctx,
+ context: {
+ ...partial_ctx.args,
+ extensions: this.extensions,
+ location,
+ }
+ }
+
+ // Authenticate using user&pass, else using ip
+ const [user, err] = await this.root.authenticate(ctx.req.headers.authorization as BasicAuth|undefined, ctx.ip, this.wg_config.subnet).then(unpack)
+
+ ctx.context.user = user
+ ctx.context.auth_err = err
+
+ if (user && user.is_admin)
+ ctx.context.extensions = {...ctx.context.extensions, ...this.admin_extensions}
+
+ // Extension
+ const selected_extension = ctx.context.extensions.get(location)
+ if (selected_extension) {
+ // If login required
+ if (!user && selected_extension.requires_login(ctx.path)) {
+ ctx.res.writeHead(307, {Location: "/login"})
+ ctx.res.end()
+ }
+ else if (user && !user.is_admin && selected_extension.requires_admin(ctx.path)) {
+ ctx.res.writeHead(307, {Location: "/"})
+ ctx.res.end()
+ }
+ else
+ selected_extension.handle_req(ctx)
+ }
+ // Root extension
+ else {
+ ctx.path.unshift(location)
+ this.root.handle_req(ctx)
+ }
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..867101d
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,211 @@
+import http2 from 'http2'
+import http1, { IncomingMessage, ServerResponse } from 'http'
+import Knex from 'knex'
+import dotenv from 'dotenv'
+import {cookie, config, Log } from './modules.ts'
+import * as modules from './modules.ts'
+import Handle from './handle.ts'
+
+// enable use of dotenv
+dotenv.config()
+
+// init database
+const knex = Knex({
+ client: 'sqlite3',
+ connection: {
+ filename: `${import.meta.dirname}/../data/db.sqlite`
+ },
+ /**
+ * `__` => selects `__`
+ * `_` => selects `__`
+ * `` => selects ``
+ */
+ wrapIdentifier(value, origImpl, queryContext) {
+ if (queryContext !== undefined && 'prefix' in queryContext) {
+ if (value.startsWith('_')) {
+ if (!value.substring(1).includes('_')) {
+ value = `_${queryContext.prefix}${value}`
+ }
+ }
+ }
+ return origImpl(value)
+ },
+})
+
+// prepare database
+if (!await knex.schema.hasTable('db_table_versions')) {
+ await knex.schema
+ .createTable('db_table_versions', (table) => {
+ table.string('table_id').notNullable().unique()
+ table.integer('version').notNullable().defaultTo(1)
+ })
+}
+// get request handler
+const handle = new Handle(modules)
+handle.init(modules, knex)
+
+// set up logging
+const log = new Log(config.logging)
+
+// handle all requests for both HTTPS and HTTP/2 or HTTP/nginx
+async function requestListener(req: Http2ServerRequest, res: Http2ServerResponse) {
+ let ip: Context['ip']
+ let cookies: Context['cookies']
+ let args: Context['args'] = new Map()
+ let path: Context['path']
+ let data: Context['data']
+
+
+ if (process.env.DEV) {
+ req.headers.host = config.domain
+ ip = process.env.IP || '0.0.0.0'
+ }
+
+ cookies = cookie.parse(req.headers.cookie || '')
+ // get authorization info
+ req.headers.authorization ??= cookies.auth
+ // get requested host, HTTP/<=1.1 uses host, HTTP/>=2 uses :authority
+ req.headers.host ??= req.headers[':authority']
+
+ // If standalone
+ if (!config.nginx) {
+ // get requesting IP
+ ip ??= req.socket?.remoteAddress || req.connection?.remoteAddress || '0.0.0.0'
+
+ // 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
+ ip = req.headers['x-real-ip'] as string || '0.0.0.0'
+ }
+
+ {
+ // separate url arguments from the url itself
+ let [raw_path = req.url, raw_args = ''] = req.url.split('?', 2)
+
+ // split arguments into key:value pairs
+ if (raw_args != '') {
+ for (let arg of raw_args.split('&')) {
+ let [key, value] = arg.split('=', 2)
+ if (!(key === undefined || value === undefined))
+ args.set(key, value)
+ }
+ }
+
+ // split url into path items
+ path = raw_path.split('/').slice(1)
+ }
+
+ // wait for all data if posting/putting
+ if (req.method == 'POST' || req.method == 'PUT') {
+ data = await new Promise((resolve) => {
+ let buffer: Buffer[] = []
+ req.on('data', function(data: Buffer) {
+ buffer.push(data)
+ })
+ req.on('end', function() {
+ let bytes = Buffer.concat(buffer as readonly Uint8Array[])
+ data = {
+ bytes,
+ raw: bytes.toString(),
+ form: {}
+ }
+ data.raw.split('&').forEach((i: string) => {
+ let [k,v] = i.split('=')
+ if (!!k && !!v) {
+ // @ts-ignore
+ data.form[k] = decodeURIComponent(v).replace(/\+/g,' ')
+ }
+ })
+ resolve(data)
+ })
+ })
+ }
+
+ let ctx: PartialContext = {
+ req,
+ res,
+ path,
+ args,
+ cookies,
+ ip,
+ data,
+ }
+ // log the request
+ log.con(req, ctx)
+ // forward the request to the handler
+ handle.main(ctx)
+}
+
+function requestListenerCompat(req: IncomingMessage, res: ServerResponse) {
+ const new_req = {
+ authority: req.headers.host ?? '',
+ scheme: new URL(req.url ?? '').protocol,
+ ...req,
+ } as unknown as Http2ServerRequest
+
+ const new_res = {
+ ...res,
+ } as unknown as Http2ServerResponse
+
+ return requestListener(new_req, new_res)
+}
+
+// Redirect requests to HTTPS
+function httpsRedirect(req: IncomingMessage, res: ServerResponse) {
+ res.writeHead(307, {"Location": `https://${req.headers.host}${req.url}`})
+ res.end()
+}
+
+
+async function startServer(http_enabled: boolean, https_enabled: boolean) {
+ if (https_enabled) {
+ let key = async (location: string) => {
+ return new Promise(async (resolve, reject) => {
+ (await import('fs')).promises.readFile(location, "utf8")
+ .then(resolve)
+ .catch((err: Error) => {
+ log.err(`Failed fetching key at: '${location}'`)
+ resolve(undefined)
+ })
+ })
+ }
+
+ log.status("Fetching encryption keys")
+
+ const private_key = await key(config.private_key_path)
+ const certificate = await key(config.server_cert_path)
+ const certificate_authority = await key(config.ca_cert_path)
+
+ log.status("Encryption keys fetched")
+
+ // Start server
+ http2.createSecureServer({
+ key: private_key,
+ cert: certificate,
+ ca: certificate_authority,
+ allowHTTP1: true,
+ }, requestListener)
+ .listen(
+ config.https_port,
+ config.host,
+ () => log.serverStart("https", config.domain, config.host, config.https_port)
+ )
+ }
+ if (http_enabled) {
+ // Start server
+ http1.createServer(
+ https_enabled ? httpsRedirect : requestListenerCompat
+ ).listen(
+ config.http_port,
+ config.host,
+ () => log.serverStart("http", config.domain, config.host, config.http_port)
+ )
+ }
+}
+
+startServer(true, !config.nginx)
diff --git a/src/modules.ts b/src/modules.ts
new file mode 100644
index 0000000..c417952
--- /dev/null
+++ b/src/modules.ts
@@ -0,0 +1,16 @@
+import nunjucks from 'nunjucks'
+export const nj: Environment = nunjucks.configure([
+ `${import.meta.dirname}/templates`,
+ `${import.meta.dirname}/extensions`
+])
+
+export * as cookie from 'cookie'
+export {ExtensionBase} from './classes/extension.ts'
+export {Tables} from './classes/tables.ts'
+export {default as Knex} from './modules/knex.ts'
+export {default as Log} from './modules/log.ts'
+export {default as content} from './modules/content_type.ts'
+export {default as Fetch} from './modules/fetch.ts'
+export {default as config} from '../config/config.ts'
+export {default as wg_config} from '../config/wireguard.ts'
+export {default as texts} from '../config/texts.ts'
diff --git a/src/modules/content_type.ts b/src/modules/content_type.ts
new file mode 100644
index 0000000..1299b52
--- /dev/null
+++ b/src/modules/content_type.ts
@@ -0,0 +1,29 @@
+// Source: https://stackoverflow.com/a/51398471/15181929
+export default (class implements Module {
+ static readonly HTML = {"Content-Type": "text/html"}
+ static readonly ASCII = {"Content-Type": "text/plain charset us-ascii"}
+ static readonly TXT = {"Content-Type": "text/plain charset utf-8"}
+ static readonly JSON = {"Content-Type": "application/json"}
+ static readonly ICO = {"Content-Type": "image/x-icon", "Cache-Control": "private, max-age=3600"}
+ static readonly CSS = {"Content-Type": "text/css", "Cache-Control": "private, max-age=3600"}
+ static readonly GIF = {"Content-Type": "image/gif", "Cache-Control": "private, max-age=3600"}
+ static readonly JPG = {"Content-Type": "image/jpeg", "Cache-Control": "private, max-age=3600"}
+ static readonly JS = {"Content-Type": "text/javascript", "Cache-Control": "private, max-age=3600"}
+ static readonly PNG = {"Content-Type": "image/png", "Cache-Control": "private, max-age=3600"}
+ static readonly MD = {"Content-Type": "text/x-markdown"}
+ static readonly XML = {"Content-Type": "application/xml"}
+ static readonly SVG = {"Content-Type": "image/svg+xml", "Cache-Control": "private, max-age=3600"}
+ static readonly WEBMANIFEST = {"Content-Type": "application/manifest+json", "Cache-Control": "private, max-age=3600"}
+ static readonly MP3 = {"Content-Type": "audio/mpeg", "Cache-Control": "private, max-age=3600"}
+ static readonly EXE = {"Content-Type": "application/vnd.microsoft.portable-executable", "Cache-Control": "private, max-age=3600"}
+ static readonly PY = {"Content-Type": "text/x-python", "Cache-Control": "private, max-age=3600"}
+
+ // Force singleton
+ private constructor(private readonly key: string, public readonly value: any) {
+ }
+
+ init: Module['init'] = (_context) => {
+ return [true]
+ }
+
+}) as ContentType
diff --git a/src/modules/fetch.ts b/src/modules/fetch.ts
new file mode 100644
index 0000000..4d014dc
--- /dev/null
+++ b/src/modules/fetch.ts
@@ -0,0 +1,46 @@
+import { readFile } from "fs/promises"
+import * as path from "path"
+import { unpack } from "../util.ts"
+
+export default class implements Module, Fetch {
+ /** File extensions of binary filetypes */
+ private readonly binary_file_name_extensions: Set = new Set(['png','jpg','mp3'])
+ /** Caches processed files */
+ private cache: Map = new Map()
+ private root: string
+
+
+ init: Module['init'] = (context) => {
+ this.root = path.join(context.path, "/static/")
+ return [true]
+ }
+
+ file: Fetch['file'] = async (file_path) => {
+ // Ensure path is absolute
+ if (!path.isAbsolute(file_path))
+ file_path = path.join(this.root, file_path)
+
+ file_path = path.normalize(file_path)
+ let filetype = path.extname(file_path).substring(1)
+
+ // load from cache if available
+ if (this.cache.has(file_path))
+ return [this.cache.get(file_path) as string, filetype]
+
+ // read the file
+ const [raw_data, err] = await readFile(file_path).then(unpack, unpack)
+ if (err)
+ return err
+
+ // cache and return the data
+ let data: FileData = (()=>{
+ if (this.binary_file_name_extensions.has(filetype))
+ return raw_data as unknown as string
+ else
+ return raw_data.toString('utf8')
+ })()
+
+ this.cache.set(file_path, data)
+ return [data, filetype]
+ }
+}
diff --git a/src/modules/knex.ts b/src/modules/knex.ts
new file mode 100644
index 0000000..0bc9519
--- /dev/null
+++ b/src/modules/knex.ts
@@ -0,0 +1,24 @@
+import { Knex } from "knex"
+
+export default class implements Module {
+ private knex: Knex
+ private prefix: string
+
+ init: Module['init'] = (context: InitContext) => {
+ this.knex = context.knex
+ this.prefix = context.name
+ return [true]
+ }
+
+ raw = (value: any) => {
+ return this.knex.raw(value)
+ }
+
+ query = (table: string | object) => {
+ return this.knex(table as never).queryContext({prefix: this.prefix})
+ }
+
+ schema = () => {
+ return this.knex.schema.queryContext({prefix: this.prefix})
+ }
+}
diff --git a/src/modules/log.ts b/src/modules/log.ts
new file mode 100644
index 0000000..4dce31e
--- /dev/null
+++ b/src/modules/log.ts
@@ -0,0 +1,66 @@
+export default class implements Log {
+ we_logging = false;
+
+ constructor(we_log: boolean) {
+ this.we_logging = we_log
+ }
+
+ private mask_ip(ip: string) {
+ let tmp = ""
+ // if IPv4
+ if (ip.includes('.')) {
+ // strip 4to6 prefix
+ ip = ip.substring(ip.lastIndexOf(':') + 1, ip.length)
+ // mask ip
+ ip.split('.').forEach(function (quad: string, index: number) {
+ quad = quad.padStart(3, "0")
+ if (index <= 2) tmp += quad + "."
+ if (index == 2) tmp += "*"
+ })
+ }
+ else {
+ // mask ip
+ ip.split(':').forEach(function (quad: string, index: number) {
+ quad = quad.padStart(4, "0")
+ if (index <= 3) tmp += quad + ":"
+ if (index == 3) tmp += "*"
+ })
+ }
+ return tmp
+ }
+
+ private mask_url(url: string) {
+ return url.split('?')[0]
+ }
+
+ con(req: Http2ServerRequest, ctx: Context | PartialContext) {
+ if (this.we_logging) {
+ let ip = this.mask_ip(ctx.ip || '')
+ let url = this.mask_url(req.url)
+ console.log(
+ `\x1b[32m [${ip}]=>'${req.method} ${url}
+ HTTP/${req.httpVersion} ${(req.headers['user-agent'] ?? "NULL").split(" ", 1)[0]} ${req.headers.authorization ? "auth" : "noauth"}\x1b[0m`
+ )
+ }
+ }
+
+ con_err(req: Http2ServerRequest) {
+ if (this.we_logging) {
+ console.log(
+ `\x1b[35m DENIED: '${req.headers.host}'\x1b[0m`
+ )
+ }
+ }
+
+ status(msg: any) {
+ console.log(`\x1b[34m>> ${msg}\x1b[0m`)
+ }
+
+ err(err: any) {
+ console.log(`\x1b[31m>> ${err}\x1b[0m`)
+ }
+
+ serverStart(type: string, domain: any, host: any, port: any) {
+ console.log(`\x1b[1m${type.toUpperCase()} server running on ${type}://${domain}:${port}, interface '${host}'\n\x1b[0m`)
+ }
+}
diff --git a/www/templates/extension.html b/src/templates/extension.html
similarity index 100%
rename from www/templates/extension.html
rename to src/templates/extension.html
diff --git a/www/templates/includes/description.html b/src/templates/includes/description.html
similarity index 100%
rename from www/templates/includes/description.html
rename to src/templates/includes/description.html
diff --git a/www/templates/includes/extensions.html b/src/templates/includes/extensions.html
similarity index 100%
rename from www/templates/includes/extensions.html
rename to src/templates/includes/extensions.html
diff --git a/www/templates/includes/favicons.html b/src/templates/includes/favicons.html
similarity index 100%
rename from www/templates/includes/favicons.html
rename to src/templates/includes/favicons.html
diff --git a/www/templates/includes/header.html b/src/templates/includes/header.html
similarity index 100%
rename from www/templates/includes/header.html
rename to src/templates/includes/header.html
diff --git a/www/templates/layout.html b/src/templates/layout.html
similarity index 100%
rename from www/templates/layout.html
rename to src/templates/layout.html
diff --git a/src/util.ts b/src/util.ts
new file mode 100644
index 0000000..3ffa7c4
--- /dev/null
+++ b/src/util.ts
@@ -0,0 +1,4 @@
+export function unpack(val: S): [S, undefined] | [undefined, Error] {
+ if (val instanceof Error) return [undefined, val]
+ else return [val, undefined]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..726adc2
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "outDir": "./build",
+ "allowJs": true,
+ "target": "ESNext",
+ "moduleResolution": "nodenext",
+ "module": "NodeNext",
+ "diagnostics": true,
+ // "alwaysStrict": true,
+ // "strict": true,
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "noImplicitOverride": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "strictNullChecks": true,
+ "typeRoots": ["./node_modules/@types", "./types/**/*"],
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ },
+ "ts-node": {
+ "files": true,
+ },
+ "exclude": ["./build"],
+}
diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts
new file mode 100644
index 0000000..f96fd08
--- /dev/null
+++ b/types/classes/extension.d.ts
@@ -0,0 +1,46 @@
+declare interface Extension {
+ admin_only: boolean
+ tables: boolean
+
+ name: string
+ title: string
+
+ init(context: InitContext): void | Promise
+
+ requires_login(path: string[]): boolean
+
+ requires_admin(path: string[]): boolean
+
+ handle_req(ctx: Context): Promise
+
+ handle(ctx: Context): void | Error | Promise
+
+ return(ctx: Context, err?: Error, location?: string, err_code?: number): void
+
+ return_text(ctx: Context, item: string): void
+
+ return_html(ctx: Context, item: string, err?: Error, err_code?: number, success_code?: number, headers?: any): void
+
+ return_file(ctx: Context, file: string): void
+
+ return_data(ctx: Context, data: any, err?: Error, headers?: {}, err_code?: number): void
+
+ set_cookie(key: string, value: any, secure?: boolean): string
+
+ del_cookie(key: string): string
+
+ get_dependencies: DependencyMap['massGet']
+}
+
+declare interface RootExtension extends Extension {
+ authenticate(auth: BasicAuth|undefined, ip: string, subnet: string): Promise
+ addUser(name: User['name'], password: User['password'], callback: (err?: Error) => void): void
+}
+
+declare interface DependencyMap {
+ forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void
+ has(key: K): boolean
+ set(key: string, value: any): void
+ get(key: string): any
+ massGet(...items: T): { [K in keyof T]: any }
+}
\ No newline at end of file
diff --git a/types/global.d.ts b/types/global.d.ts
new file mode 100644
index 0000000..ade90e8
--- /dev/null
+++ b/types/global.d.ts
@@ -0,0 +1,66 @@
+declare type Http2ServerRequest = import('http2').Http2ServerRequest
+declare type Http2ServerResponse = import('http2').Http2ServerResponse
+declare type Environment = import('nunjucks').Environment
+declare type Database = import('sqlite3').Database
+
+declare type HttpHeader = {
+ "Content-Type"?: string,
+ "Cache-Control"?: string
+}
+
+declare type BasicAuth = `Basic ${string}`
+
+declare type FileData = string | null
+
+declare type Context = {
+ req: Http2ServerRequest
+ res: Http2ServerResponse
+
+ ip: string
+ cookies: Record
+ path: string[]
+ args: Map
+ data?: {bytes: Buffer, raw: string, form: any}
+ context: {
+ user?: User
+ extensions: Map
+ location: string
+ [any: string]: any
+ }
+}
+
+declare type PartialContext = {
+ req: Http2ServerRequest
+ res: Http2ServerResponse
+
+ ip: string
+ cookies: Record
+ path: string[]
+ args: Map
+ data?: {bytes: Buffer, raw: string, form: any}
+}
+
+declare type User = {
+ id: number,
+ name: string,
+ password: string,
+ regdate: Date,
+ is_admin: boolean,
+ pfp_code: string
+}
+
+declare type InitContext = {
+ modules: any,
+ path: string,
+ data_path: string,
+ name: string,
+ knex: import('knex').Knex,
+}
+
+declare type ResultStatus = [Okay: false, Error: Error] | [Okay: true]
+
+declare type VariableSizeArray = { [K in keyof S]: T }
+
+declare interface Module {
+ init(context: InitContext): ResultStatus
+}
diff --git a/types/handle.d.ts b/types/handle.d.ts
new file mode 100644
index 0000000..6980ca0
--- /dev/null
+++ b/types/handle.d.ts
@@ -0,0 +1,4 @@
+declare interface Handle {
+ init(modules: any, knex: import('knex').Knex): Promise
+ main(ctx: PartialContext): void,
+}
diff --git a/types/modules/content_type.d.ts b/types/modules/content_type.d.ts
new file mode 100644
index 0000000..7781b49
--- /dev/null
+++ b/types/modules/content_type.d.ts
@@ -0,0 +1,19 @@
+declare interface ContentType {
+ readonly HTML: HttpHeader
+ readonly ASCII: HttpHeader
+ readonly TXT: HttpHeader
+ readonly JSON: HttpHeader
+ readonly ICO: HttpHeader
+ readonly CSS: HttpHeader
+ readonly GIF: HttpHeader
+ readonly JPG: HttpHeader
+ readonly JS: HttpHeader
+ readonly PNG: HttpHeader
+ readonly MD: HttpHeader
+ readonly XML : HttpHeader
+ readonly SVG: HttpHeader
+ readonly WEBMANIFEST: HttpHeader
+ readonly MP3: HttpHeader
+ readonly EXE: HttpHeader
+ readonly PY: HttpHeader
+}
diff --git a/types/modules/fetch.d.ts b/types/modules/fetch.d.ts
new file mode 100644
index 0000000..d493efa
--- /dev/null
+++ b/types/modules/fetch.d.ts
@@ -0,0 +1,3 @@
+declare interface Fetch {
+ file(file_path: string): Promise<[FileData, string] | Error>
+}
diff --git a/types/modules/log.d.ts b/types/modules/log.d.ts
new file mode 100644
index 0000000..e0e75c9
--- /dev/null
+++ b/types/modules/log.d.ts
@@ -0,0 +1,7 @@
+declare interface Log {
+ con(req: any, ctx: any): void,
+ con_err(req: any): void,
+ status(msg: string): void,
+ err(err: string): void,
+ serverStart(type: string, domain: string, host: string, port: number): void
+}
diff --git a/www/extensions/admin/index.js b/www/extensions/admin/index.js
deleted file mode 100644
index 34c32e6..0000000
--- a/www/extensions/admin/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = (Extension) => {return class extends Extension {
- name = 'admin'
- title = 'Admin'
- admin_only = true
- dependencies = ['data','content','nj']
-
- handle(req, res) {
- this.return_html(req, res, 'index')
- }
-}}
diff --git a/www/extensions/chat/index.js b/www/extensions/chat/index.js
deleted file mode 100644
index 4af2c90..0000000
--- a/www/extensions/chat/index.js
+++ /dev/null
@@ -1,53 +0,0 @@
-module.exports = (Extension) => {return class extends Extension {
- name = 'chat'
- title = 'Chat'
- dependencies = ['content','nj','fetch']
- messages = [{
- user: {name:'SYSTEM',pfp_code:'seed=SYSTEM'},
- time:(new Date()).toLocaleTimeString('en-US', {hour12: false}),
- content: 'Welcome to the chatroom!'
- }]
- last_got_id = {}
-
- requires_login(path) {
- return true
- }
-
- handle(req, res) {
- var location = req.path.shift()
- if (!location) {
- if (req.data && req.data.message) {
- var message = req.data.message.substring(0,255)
- var now = (new Date()).toLocaleTimeString('en-US', {hour12: false})
- this.messages.push({
- user: {name:req.context.user.name, pfp_code:req.context.user.pfp_code},
- time: now,
- content: message
- })
- }
- req.context.chat = this.messages
- this.last_got_id[req.context.user.id] = this.messages.length
- return this.return_html(req, res, 'index')
- }
- else if (location == 'getnew') {
- var part = this.last_got_id.hasOwnProperty(req.context.user.id) ? this.last_got_id[req.context.user.id] : 0
- this.last_got_id[req.context.user.id] = this.messages.length
- return this.return_data(res, `{"messages":${JSON.stringify(this.messages.slice(part))}}`)
- }
- else if (location == 'postmessage') {
- if (req.data && req.data.message) {
- var message = req.data.message.substring(0,255)
- var now = (new Date()).toLocaleTimeString('en-US', {hour12: false})
- this.messages.push({
- user: {name:req.context.user.name, pfp_code:req.context.user.pfp_code},
- time: now,
- content: message
- })
- }
- return this.return(res)
- }
- else {
- return this.return_file(res, location)
- }
- }
-}}
diff --git a/www/extensions/nothing/index.js b/www/extensions/nothing/index.js
deleted file mode 100644
index 304fd05..0000000
--- a/www/extensions/nothing/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = (Extension) => {return class extends Extension {
- name = 'nothing'
- title = 'Nothing'
- dependencies = ['content','nj']
-
- handle(req, res) {
- this.return_html(req, res, 'index')
- }
-}}
diff --git a/www/extensions/profile/index.js b/www/extensions/profile/index.js
deleted file mode 100644
index 8c9ad5c..0000000
--- a/www/extensions/profile/index.js
+++ /dev/null
@@ -1,114 +0,0 @@
-module.exports = (Extension) => {return class extends Extension {
- name = 'profile'
- title = 'Network'
- tables = true
- 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
- }
-
-
- requires_login(path) {
- if (path.at(0) == 'getconf') {
- return false
- }
- return true
- }
-
- handle(req, res) {
- var location = req.path.shift()
- switch (location) {
- case '':
- 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
- }
- case 'delete': {
- // Check ownership
- this.owns(req.context.user, req.args.uuid, (user_owns) => {
- if (!user_owns) return this.return(res, true, 404)
- // Delete db entry
- this.db.delete('device', 'uuid=$uuid', [req.args.uuid], (err) => {
- // Delete wireguard profile
- this.wg.delete(req.args.uuid, () => {
- return this.return(res, err, location='/profile')
- })
- })
- })
- break
- }
- case 'add': {
- // Get uuid
- let uuid = this.crypto.randomUUID()
- // Get IP suffix
- this.db.select('device', ['MAX(id)'], null, null, [], (err, data) => {
- let id = data[0]['MAX(id)'] +2
- // Register wireguard link
- this.wg.create(uuid, id, (ip, err) => {
- if (err) return callback(err)
- // Insert in db
- this.db.insert('device', ['user_id','uuid','ip'], [req.context.user.id, uuid, ip], (err) => {
- return this.return(res, err, location='/profile')
- })
- })
- })
- break
- }
- case 'getconf': {
- // Get uuid
- let uuid = Object.keys(req.args)[0]
- // Get config
- this.wg.getConfig(uuid, (data, err) => {
- if (err) return this.return(res, true, 404)
- // Mark as installed
- this.db.update('device', ['installed=TRUE'], 'uuid=$uuid', [uuid], (err) => {
- return this.return_data(res, data, err, {"Content-Type": "text/plain charset utf-8", "Content-Disposition": `attachment; filename="keuknet.conf"`})
- })
- })
- break
- }
- case 'install': {
- req.context.host = req.headers.host
- return this.return_html(req, res, 'install')
- break
- }
- case 'rename': {
- if (req.data) {
- // Check ownership
- this.owns(req.context.user, req.args.uuid, (user_owns) => {
- if (!user_owns) return this.return(res, true, 404)
- // Change name
- this.db.update('device', ['name=$name'], 'uuid=$uuid', [req.post_data,req.args.uuid], (err) => {
- return this.return(res, err, location='/profile')
- })
- })
- }
- else {
- req.context = {item:"new name",action:req.url,destination:"/profile"}
- this.return_html(req, res, 'edit')
- }
- break
- }
- default: {
- return this.return_file(res, location)
- }
- }
- }
-
- owns = (user, uuid, callback) => {
- if (!uuid) return callback(undefined)
- this.db.select('device', ['1'], 'user_id=$id AND uuid=$uuid', null, [user.id, uuid], (err, data) => {
- return callback(data[0] ? Object.hasOwn(data[0], '1') : false)
- })
- }
-}}
diff --git a/www/extensions/profile/tables.js b/www/extensions/profile/tables.js
deleted file mode 100644
index be8cb98..0000000
--- a/www/extensions/profile/tables.js
+++ /dev/null
@@ -1,19 +0,0 @@
-module.exports = (Tables) => {return class extends Tables {
- tables = {
- 'device': 0
- }
-
- device = {
- 0:()=>{
- this.db.createTable('device', [
- 'id INTEGER PRIMARY KEY AUTOINCREMENT',
- 'user_id INTEGER NOT NULL',
- 'name VARCHAR',
- 'uuid CHAR(36) NOT NULL',
- 'ip VARCHAR NOT NULL',
- 'installed BOOLEAN NOT NULL DEFAULT FALSE CHECK (installed IN (0,1))',
- 'CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES user(id)'
- ])
- }
- }
-}}
diff --git a/www/extensions/root/index.js b/www/extensions/root/index.js
deleted file mode 100644
index 77573d6..0000000
--- a/www/extensions/root/index.js
+++ /dev/null
@@ -1,134 +0,0 @@
-module.exports = (Extension) => {return class extends Extension {
- name = 'root'
- title = 'Home'
- tables = true
- dependencies = ['content','nj','fetch','data','texts','cookie']
- favicons = []
- favicons_path
-
- constructor (global, path, data_path) {
- super(global, path)
-
- this.favicons_path = data_path+'favicons/'
- let fs = require('fs')
- try {
- this.favicons.push(...fs.readdirSync(this.favicons_path))
- } catch {
- global.log.err(new Error(`No favicons found in '${this.favicons_path}'`))
- }
- }
-
-
- requires_login(path) {
- if (path.at(0) == '_') {
- return true
- }
- return false
- }
-
- handle(req, res) {
- var location = req.path.shift()
- switch (location) {
- case '':
- case undefined: {
- if (!req.context.user) {
- return this.return_text(req, res, 'index')
- }
- return this.return_html(req, res, 'user')
- }
- case 'login': {
- // If user not logged in
- if (!req.context.user) {
- // 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)
- })
- }
- })
- }
- }
- // 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": "/"})
- return res.end()
- }
- case 'logout': {
- if (req.context.user) {
- // log user out and redirect
- res.writeHead(307, {
- "Location": "/",
- "Set-Cookie": this.del_cookie('auth')
- })
- 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
- }
- else {
- return this.return_html(req, res, 'pfp', null)
- }
- }
- }
- break
- }
- default: {
- // Templated html
- if (location.startsWith('~')) {
- return this.return_html(req, res, 'content/'+location.split('~')[1], null, 404)
- }
- // Favicon
- else if (this.favicons.includes(location)) {
- return this.return_file(res, this.favicons_path+location)
- }
- // File
- else {
- return this.return_file(res, location)
- }
- }
- }
- }
-}}
diff --git a/www/extensions/root/tables.js b/www/extensions/root/tables.js
deleted file mode 100644
index 5613cb7..0000000
--- a/www/extensions/root/tables.js
+++ /dev/null
@@ -1,38 +0,0 @@
-module.exports = (Tables) => {return class extends Tables {
- tables = {
- 'user': 1,
- 'db_table_versions': 0
- }
-
- user = {
- 0:()=>{
- this.db.createTable('user', [
- 'id INTEGER PRIMARY KEY AUTOINCREMENT',
- 'name VARCHAR NOT NULL',
- 'password VARCHAR NOT NULL',
- 'regdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL',
- 'is_admin BOOLEAN NOT NULL DEFAULT FALSE CHECK (is_admin IN (0,1))',
- ])
- },
- 1:()=>{
- this.db.select('user', ['id','name'], null, null, [], (err, data) => {
- if(err)console.log(err)
- this.db.addColumn('user', 'pfp_code TEXT', (err) => {
- if(err)console.log(err)
- for (const user of data) {
- this.db.update('user', ['pfp_code=$name'], 'id=$id', [`seed=${user.name}`, user.id], (err)=>{if(err)console.log(err)})
- }
- })
- })
- }
- }
- db_table_versions = {
- 0:()=>{
- this.db.createTable('db_table_versions', [
- 'id INTEGER PRIMARY KEY AUTOINCREMENT',
- 'table_id VARCHAR NOT NULL',
- 'version INTEGER NOT NULL DEFAULT 1'
- ])
- }
- }
-}}
diff --git a/www/extensions/servers/addserver.html b/www/extensions/servers/addserver.html
deleted file mode 100644
index 4e004b2..0000000
--- a/www/extensions/servers/addserver.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends "extension.html" %}
-
-{% block body %}
-
- Add new server
-
-
-{% endblock %}
diff --git a/www/extensions/servers/index.js b/www/extensions/servers/index.js
deleted file mode 100644
index 706a2b6..0000000
--- a/www/extensions/servers/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-module.exports = (Extension) => {return class extends Extension {
- name = 'servers'
- title = 'Servers'
- tables = true
- dependencies = ['content','nj','fetch']
-
- requires_admin(path) {
- if (path.at(0) == "addserver") {
- return true
- }
- return false
- }
-
- handle(req, res) {
- var location = req.path.shift()
-
- if (!location) {
- if (req.args.server) {
- this.db.select('server', ['*'], 'id=$id', null, [req.args.server], (err, server) => {
- req.context.server = server[0]
- this.return_html(req, res, 'server', err ?? !server[0], 404)
- })
- return
- }
- this.db.select('server', ['*'], null, null, [], (err, servers) => {
- req.context.servers = servers
- this.return_html(req, res, 'serverlist', err)
- })
- return
- }
- if (location == "addserver") {
- if (req.data) {
- if (req.data.name && req.data.admin_id && req.data.description && req.data.ip) {
- this.db.insert('server', ['name','admin_id','description','ip','url'], [req.data.name,req.data.admin_id,req.data.description,req.data.ip,req.data.url], (err) => {
- this.return(res, err)
- })
- return
- }
- }
- return this.return_html(req, res, 'addserver')
- }
- else {
- return this.return_file(res, location)
- }
- }
-}}
diff --git a/www/extensions/servers/server.html b/www/extensions/servers/server.html
deleted file mode 100644
index fda25bb..0000000
--- a/www/extensions/servers/server.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{% extends "extension.html" %}
-
-{% block head %}
-
-{% endblock %}
-
-{% block body %}
-
- {% if server.url %}
-
{{server.name}}
- {% else %}
-
{{server.name}}
- {% endif %}
-
IP: {{server.ip}}
-
{{server.description | safe}}
-
-{% endblock %}
diff --git a/www/extensions/servers/serverlist.html b/www/extensions/servers/serverlist.html
deleted file mode 100644
index 2a576c6..0000000
--- a/www/extensions/servers/serverlist.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{% extends "extension.html" %}
-
-{% block head %}
-
-{% endblock %}
-
-{% block body %}
- {% if user.is_admin %}
-
- {% endif %}
-
-
- {% for s in servers | reverse %}
-
- {% if s.url %}
-
{{s.name}}
- {% else %}
-
{{s.name}}
- {% endif %}
-
IP: {{s.ip}}
-
- {{s.description | striptags() | truncate(128, true, "")}}
- ...
-
-
- {% else %}
-
This page is currently under development and will be available soon!
-
No servers have been added yet, please check back later.
- {% endfor %}
-
-{% endblock %}
diff --git a/www/extensions/servers/static/index.css b/www/extensions/servers/static/index.css
deleted file mode 100644
index 647fd51..0000000
--- a/www/extensions/servers/static/index.css
+++ /dev/null
@@ -1,54 +0,0 @@
-.content {
- padding: var(--margin-normal);
-}
-
-/* General server item */
-.content .server > p:nth-child(1) {
- font-weight: bold;
- font-size: larger;
- margin-bottom: 0;
-}
-.content .server > p:nth-child(1) > a {
- text-decoration: none;
- color: var(--color-link)
-}
-
-/* Serverlist */
-.content > .serverlist {
- width: 100%;
- display: flex;
- flex-wrap: wrap;
-}
-.content > .serverlist > .server {
- width: 50%;
- height: auto;
- overflow: hidden;
- box-sizing: border-box;
- border-bottom: 1px dashed black;
- padding: var(--margin-normal);
-}
-.content > .serverlist > .server:nth-child(2n -1) {
- border-right: 1px dashed black;
-}
-.content > .serverlist > .server > p:nth-child(1) {
- margin-top: var(--margin-small);
-}
-.content > .serverlist > .server > p:nth-child(2) {
- margin-top: 0;
- margin-left: 5px;
- font-size: 15px;
-}
-.content > .serverlist > .server > p:nth-child(3) {
- margin-bottom: var(--margin-small);
-}
-.content > .serverlist > .server > p:nth-child(3) > a {
- font-size: 18px;
- font-weight: bold;
- text-decoration: none;
- color: var(--color-link);
- margin-top: auto;
- margin-bottom: 0;
- bottom: 0;
- top: auto;
- height: auto;
-}
diff --git a/www/extensions/servers/tables.js b/www/extensions/servers/tables.js
deleted file mode 100644
index 012c60f..0000000
--- a/www/extensions/servers/tables.js
+++ /dev/null
@@ -1,19 +0,0 @@
-module.exports = (Tables) => {return class extends Tables {
- tables = {
- 'server': 0
- }
-
- server = {
- 0:()=>{
- this.db.createTable('server', [
- 'id INTEGER PRIMARY KEY AUTOINCREMENT',
- 'admin_id INTEGER NOT NULL',
- 'name VARCHAR NOT NULL',
- 'description TEXT NOT NULL',
- 'ip VARCHAR NOT NULL',
- 'url VARCHAR',
- 'CONSTRAINT fk_admin_id FOREIGN KEY (admin_id) REFERENCES user(id)'
- ])
- }
- }
-}}
diff --git a/www/index.js b/www/index.js
deleted file mode 100644
index d5cc3e3..0000000
--- a/www/index.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/* 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/.
- */
-var extensions = [
- 'profile','servers','nothing','admin','chat'
-]
-
-var root
-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/`)
-
- var extension_indices = {}
- for (path of extensions) {
- let ext = new ((require(`./extensions/${path}/index.js`))(global.Extension))(global, `${__dirname}/extensions/${path}/`, `${__dirname}/../data/${path}/`)
- extension_indices[path] = ext
- }
- extensions = extension_indices
- admin_extensions = {}
- for (const ext of Object.keys(extensions)) {
- if (extensions[ext].admin_only) {
- admin_extensions[ext] = extensions[ext]
- delete extensions[ext]
- }
- }
-}
-
-exports.main = function (req, res) {
- var location = req.path.shift()
-
- // set request context
- req.context = {...req.args}
- req.context.extensions = extensions
- req.context.location = location
-
- // Authenticate using user&pass, else using ip
- 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}
-
- // Extension
- if (location in req.context.extensions) {
- ext = req.context.extensions[location]
- // If login required
- if (!user && ext.requires_login(req.path)) {
- res.writeHead(307, {Location: "/login"})
- res.end()
- return
- }
- if (user && !user.is_admin && ext.requires_admin(req.path)) {
- res.writeHead(307, {Location: "/"})
- res.end()
- return
- }
- ext.handle_req(req, res)
- }
- // Root extension
- else {
- req.path.unshift(location)
- root.handle_req(req, res)
- }
- })
-}