From 41d1068f681846542dbd2cffc8defd4d00b2d176 Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Sat, 25 May 2024 12:45:17 +0000 Subject: [PATCH 01/14] start typescript migration --- classes/extension.js | 6 +- global.js | 52 -------- global.ts | 85 +++++++++++++ index.js => index.ts | 82 ++++++------- modules/log.js | 64 ---------- modules/log.ts | 106 +++++++++++++++++ package-lock.json | 237 ++++++++++++++++++++++++++++++++++++- package.json | 13 +- tsconfig.json | 21 ++++ types/global.d.ts | 2 + www/{index.js => index.ts} | 53 +++++---- 11 files changed, 533 insertions(+), 188 deletions(-) delete mode 100644 global.js create mode 100644 global.ts rename index.js => index.ts (69%) delete mode 100644 modules/log.js create mode 100644 modules/log.ts create mode 100644 tsconfig.json create mode 100644 types/global.d.ts rename www/{index.js => index.ts} (52%) diff --git a/classes/extension.js b/classes/extension.js index e2320d5..eff5424 100644 --- a/classes/extension.js +++ b/classes/extension.js @@ -40,7 +40,11 @@ module.exports = class Extension { return this.admin_only } - handle_req(req, res) { + handle_req(ctx) { + let {req, res} = ctx + delete ctx.req + delete ctx.res + req = {...req, ...ctx} req.context.extension = this return this.handle(req, res) } diff --git a/global.js b/global.js deleted file mode 100644 index bb9ecc2..0000000 --- a/global.js +++ /dev/null @@ -1,52 +0,0 @@ -/* import external modules */ -const nj = require('nunjucks').configure(['www/templates','www/pages','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.favicons = [ - "android-chrome-36x36.png","android-chrome-48x48.png","android-chrome-72x72.png","android-chrome-96x96.png","android-chrome-144x144.png","android-chrome-192x192.png","android-chrome-256x256.png", - "apple-touch-icon.png","apple-touch-icon-57x57.png","apple-touch-icon-60x60.png","apple-touch-icon-72x72.png","apple-touch-icon-76x76.png","apple-touch-icon-114x114.png","apple-touch-icon-120x120.png","apple-touch-icon-144x144.png","apple-touch-icon-152x152.png","apple-touch-icon-180x180.png", - "apple-touch-icon-precomposed.png","apple-touch-icon-57x57-precomposed.png","apple-touch-icon-60x60-precomposed.png","apple-touch-icon-72x72-precomposed.png","apple-touch-icon-76x76-precomposed.png","apple-touch-icon-114x114-precomposed.png","apple-touch-icon-120x120-precomposed.png","apple-touch-icon-144x144-precomposed.png","apple-touch-icon-152x152-precomposed.png","apple-touch-icon-180x180-precomposed.png", - "favicon.ico","favicon-16x16.png","favicon-32x32.png","favicon-194x194.png", - "mstile-70x70.png","mstile-144x144.png","mstile-150x150.png","mstile-310x150.png","mstile-310x310.png", - "browserconfig.xml","safari-pinned-tab.svg","site.webmanifest" -] - -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/global.ts b/global.ts new file mode 100644 index 0000000..b87dccb --- /dev/null +++ b/global.ts @@ -0,0 +1,85 @@ +import type * as NJ from 'nunjucks' + +declare global { + namespace Global { + export const content: {} + export const favicons: [] + + /* import external modules */ + export const nj: NJ.Environment + + export const microfetch: any + export const Extension: any + export const Tables: any + export const fetch: any + export const data: any + export const DB: any + + export function db(): void + + export namespace Log { + + } + } + + // export * as cookie from 'cookie' + + // export const Log + + // export * as config from './config/config' + // export * as wg_config from './config/wireguard' + // export * as texts from './config/texts' +} + +namespace Global { + export const 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"}, + 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"} + } + export const favicons = [ + "android-chrome-36x36.png","android-chrome-48x48.png","android-chrome-72x72.png","android-chrome-96x96.png","android-chrome-144x144.png","android-chrome-192x192.png","android-chrome-256x256.png", + "apple-touch-icon.png","apple-touch-icon-57x57.png","apple-touch-icon-60x60.png","apple-touch-icon-72x72.png","apple-touch-icon-76x76.png","apple-touch-icon-114x114.png","apple-touch-icon-120x120.png","apple-touch-icon-144x144.png","apple-touch-icon-152x152.png","apple-touch-icon-180x180.png", + "apple-touch-icon-precomposed.png","apple-touch-icon-57x57-precomposed.png","apple-touch-icon-60x60-precomposed.png","apple-touch-icon-72x72-precomposed.png","apple-touch-icon-76x76-precomposed.png","apple-touch-icon-114x114-precomposed.png","apple-touch-icon-120x120-precomposed.png","apple-touch-icon-144x144-precomposed.png","apple-touch-icon-152x152-precomposed.png","apple-touch-icon-180x180-precomposed.png", + "favicon.ico","favicon-16x16.png","favicon-32x32.png","favicon-194x194.png", + "mstile-70x70.png","mstile-144x144.png","mstile-150x150.png","mstile-310x150.png","mstile-310x310.png", + "browserconfig.xml","safari-pinned-tab.svg","site.webmanifest" + ] + + /* import external modules */ + export const nj: NJ.Environment = require('nunjucks').configure(['www/templates','www/pages','www/extensions']) + + export const microfetch: any = require('./modules/microfetch') + export const Extension: any = require('./classes/extension') + export const Tables: any = require('./classes/tables') + export const fetch: any = require('./modules/fetch') + export const data: any = require('./modules/data') + export const DB: any = require('./classes/db') + + export function db(): void {return data.db()} + +} + +export * as cookie from 'cookie' + +// export {Log} from './modules/log' + +export * as config from './config/config' +export * as wg_config from './config/wireguard' +export * as texts from './config/texts' + +export {Global} diff --git a/index.js b/index.ts similarity index 69% rename from index.js rename to index.ts index 7230bce..1873a8b 100644 --- a/index.js +++ b/index.ts @@ -3,55 +3,57 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const cookie = require('cookie') +interface Handler { + init(global: any): null, + main(ctx: any): null +} + // 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 +import {cookie, config} from './global' +import * as global from './global' +const {fetch, data} = Global +// import {Log} from './modules/log' -// get request handler -const handle = require('./www/index') +// get request handler +const handle: Handler = require('./www/index') // set up modules -log.init(config.logging) +Global.Log +const log: Global.Log = new Global.Log(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.init(global) }) // handle all requests for both HTTPS and HTTP/2 or HTTP/nginx -const requestListener = function (req, res) { +const requestListener = function (req: Http2ServerRequest, res: Http2ServerResponse) { + let ctx: any = { + req, + res + } if (process.env.DEV) { req.headers.host = config.domain - req.ip = process.env.IP + ctx.ip = process.env.IP } - req.cookies = cookie.parse(req.headers.cookie || '') + ctx.cookies = cookie.parse(req.headers.cookie || '') // get authorization info - req.headers.authorization ??= req.cookies.auth + req.headers.authorization ??= ctx.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 + ctx.ip ??= req.socket?.remoteAddress || req.connection?.remoteAddress // if request is not for any domain served here, act like server isn't here if (req.headers.host != config.domain) { @@ -61,53 +63,53 @@ const requestListener = function (req, res) { // If behind NGINX } else { // get requesting IP - req.ip = req.headers['x-real-ip'] || '0.0.0.0' + ctx.ip = req.headers['x-real-ip'] || '0.0.0.0' } + let args: string // separate url arguments from the url itself - [req.path, args] = req.url.split('?') + [ctx.path, args] = req.url.split('?') // split arguments into key:value pairs - req.args = {} + ctx.args = {} if (args) { - for (arg of args.split('&')) { - arg = arg.split('=') - req.args[arg[0]] = arg[1] + for (let arg of args.split('&')) { + let [key, value] = arg.split('=') + ctx.args[key] = value // allow authentication using argument auth= - if (req.args.auth) req.headers.authorization ??= "Basic " + req.args.auth + if (ctx.args.auth) req.headers.authorization ??= "Basic " + ctx.args.auth } - delete args } // split url into path items - req.path = req.path.split('/').slice(1) + ctx.path = ctx.path.split('/').slice(1) // log the request - log.con(req) + log.con(req, ctx) // wait for all data if posting if (req.method == 'POST') { - buffer = [] - req.on('data', function(data) { + let buffer: Buffer[] = [] + req.on('data', function(data: Buffer) { 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('=') + ctx.data = {raw:Buffer.concat(buffer).toString()} + ctx.data.raw.split('&').forEach(function (i: string) { + let [k,v] = i.split('=') if (k && v) { - req.data[k] = decodeURIComponent(v).replace(/\+/g,' ') + ctx.data[k] = decodeURIComponent(v).replace(/\+/g,' ') } }) - req.post_data = req.data.post_data + ctx.post_data = ctx.data.post_data // forward the request to the handler - handle.main(req, res) + handle.main(ctx) }) } // other methods continue else { // forward the request to the handler - handle.main(req, res) + handle.main(ctx) } } 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/log.ts b/modules/log.ts new file mode 100644 index 0000000..7fadce7 --- /dev/null +++ b/modules/log.ts @@ -0,0 +1,106 @@ +import { Http2ServerRequest } from "http2"; + +declare global { + namespace Global { + export 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 + } + + export class Log { + we_logging: boolean + + constructor(we_log: boolean) + + __mask_ip(ip: string): string + __mask_url(url: string): string + + con(req: Http2ServerRequest, ctx: { ip: any }): void + con_err(req: Http2ServerRequest): void + status(msg: string): void + err(err: string): void + serverStart(type: string, domain: string, host: string, port: number): void + } + } +} + +namespace Global { + export 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 + } + + export class Log { + we_logging = false; + + constructor(we_log: boolean) { + this.we_logging = we_log + } + + __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 + } + + __mask_url(url: string) { + return url.split('?')[0] + } + + con(req: { url: any; method: any; httpVersion: any; headers: { [x: string]: any; authorization: any } }, ctx: { ip: any }) { + 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: { ip: any; headers: { host: any } }) { + if (this.we_logging) { + let ip = this.__mask_ip(req.ip) + console.log( + `\x1b[35m DEN[${ip}]: '${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/package-lock.json b/package-lock.json index 6ce3c33..6c4818f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,28 @@ "cookie": "^0.6.0", "dotenv": "^16.4.5", "nunjucks": "^3.2.4", - "sqlite3": "^5.1.6" + "sqlite3": "^5.1.6", + "typescript": "^5.4.5" }, "devDependencies": { - "nodemon": "^3.1.0" + "@types/cookie": "^0.6.0", + "@types/node": "^20.12.12", + "@types/nunjucks": "^3.2.6", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" } }, "node_modules/@gar/promisify": { @@ -25,6 +43,34 @@ "license": "MIT", "optional": true }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -61,6 +107,58 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "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/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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", @@ -74,6 +172,29 @@ "license": "ISC", "optional": true }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -160,6 +281,13 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -390,6 +518,13 @@ "node": ">= 0.6" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -448,6 +583,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -897,6 +1042,13 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -1756,6 +1908,50 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1768,6 +1964,19 @@ "node": "*" } }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -1775,6 +1984,13 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -1801,6 +2017,13 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1838,6 +2061,16 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } } } } diff --git a/package.json b/package.json index a33361c..4e44ec2 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "A webserver and client program for easily managing a WireGuard-network in a client-server setting.", "main": "index.js", "scripts": { - "start": "node index.js", - "dev": "sudo npx nodemon index.js -e . --ignore ./data/" + "start": "npx tsc && node build/index.js", + "dev": "sudo npx nodemon index.ts -e . --ignore ./data/ --exec 'ts-node'" }, "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", @@ -13,7 +13,8 @@ "cookie": "^0.6.0", "dotenv": "^16.4.5", "nunjucks": "^3.2.4", - "sqlite3": "^5.1.6" + "sqlite3": "^5.1.6", + "typescript": "^5.4.5" }, "repository": { "type": "git", @@ -24,6 +25,10 @@ }, "homepage": "https://keuk.net/", "devDependencies": { - "nodemon": "^3.1.0" + "@types/cookie": "^0.6.0", + "@types/node": "^20.12.12", + "@types/nunjucks": "^3.2.6", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2e452b4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "outDir": "./build", + "allowJs": true, + // "target": "es5" + "moduleResolution": "Node16", + "module": "Node16", + "noImplicitThis": true, + // "types": [ + // "./global.d.ts", + // "node", + // "nunjucks", + // "cookie" + // ] + // "declaration": true, + // "declarationDir": "./types" + "typeRoots": ["./node_modules/@types", "types"] + }, + // "include": ["./**/*"], + "exclude": ["./build"] +} diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 0000000..f9064a3 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,2 @@ +declare type Http2ServerRequest = import('http2').Http2ServerRequest +declare type Http2ServerResponse = import('http2').Http2ServerResponse diff --git a/www/index.js b/www/index.ts similarity index 52% rename from www/index.js rename to www/index.ts index d5cc3e3..06244d9 100644 --- a/www/index.js +++ b/www/index.ts @@ -1,22 +1,22 @@ -/* 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 = [ +import { Http2ServerResponse } from "http2" + +var extensions_list = [ 'profile','servers','nothing','admin','chat' ] +const {data, nj} = Global var root -var data, wg_config -exports.init = function (global) { - ({data,wg_config,texts,nj,config} = global) +var wg_config, extensions, admin_extensions +exports.init = function (global: any) { + let texts, config + ({wg_config,texts,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) { + for (const path of extensions_list) { let ext = new ((require(`./extensions/${path}/index.js`))(global.Extension))(global, `${__dirname}/extensions/${path}/`, `${__dirname}/../data/${path}/`) extension_indices[path] = ext } @@ -29,41 +29,44 @@ exports.init = function (global) { } } } +// import type { Http2ServerRequest } from "./../global.d" + +exports.main = function (ctx) { + var {req, res}: {req: Http2ServerRequest, res: Http2ServerResponse} = ctx -exports.main = function (req, res) { - var location = req.path.shift() + var location = ctx.path.shift() // set request context - req.context = {...req.args} - req.context.extensions = extensions - req.context.location = location + ctx.context = {...ctx.args} + ctx.context.extensions = extensions + ctx.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} + data.authenticate(req.headers.authorization, ctx.ip, wg_config.subnet, function (user, err) { + ctx.context.user = user + if (err) ctx.context.auth_err = err + if (user && user.is_admin) ctx.context.extensions = {...ctx.context.extensions, ...admin_extensions} // Extension - if (location in req.context.extensions) { - ext = req.context.extensions[location] + if (location in ctx.context.extensions) { + let ext = ctx.context.extensions[location] // If login required - if (!user && ext.requires_login(req.path)) { + if (!user && ext.requires_login(ctx.path)) { res.writeHead(307, {Location: "/login"}) res.end() return } - if (user && !user.is_admin && ext.requires_admin(req.path)) { + if (user && !user.is_admin && ext.requires_admin(ctx.path)) { res.writeHead(307, {Location: "/"}) res.end() return } - ext.handle_req(req, res) + ext.handle_req(ctx) } // Root extension else { - req.path.unshift(location) - root.handle_req(req, res) + ctx.path.unshift(location) + root.handle_req(ctx) } }) } From 8652f1ec7668220540ae42f04607d25a4ded758b Mon Sep 17 00:00:00 2001 From: jippie13 <66255957+fizitzfux@users.noreply.github.com> Date: Sun, 26 May 2024 14:27:22 +0200 Subject: [PATCH 02/14] Merge latest to typescript branch --- global.js | 44 ------------------------------- global.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 44 deletions(-) delete mode 100644 global.js create mode 100644 global.ts 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/global.ts b/global.ts new file mode 100644 index 0000000..65a636f --- /dev/null +++ b/global.ts @@ -0,0 +1,77 @@ +import type * as NJ from 'nunjucks' + +declare global { + namespace Global { + export const content: {} + export const favicons: [] + + /* import external modules */ + export const nj: NJ.Environment + + export const microfetch: any + export const Extension: any + export const Tables: any + export const fetch: any + export const data: any + export const DB: any + + export function db(): void + + export namespace Log { + + } + } + + // export * as cookie from 'cookie' + + // export const Log + + // export * as config from './config/config' + // export * as wg_config from './config/wireguard' + // export * as texts from './config/texts' +} + +namespace Global { + export const 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"}, + 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"} + } + + /* import external modules */ + export const nj: NJ.Environment = require('nunjucks').configure(['www/templates','www/pages','www/extensions']) + + export const microfetch: any = require('./modules/microfetch') + export const Extension: any = require('./classes/extension') + export const Tables: any = require('./classes/tables') + export const fetch: any = require('./modules/fetch') + export const data: any = require('./modules/data') + export const DB: any = require('./classes/db') + + export function db(): void {return data.db()} + +} + +export * as cookie from 'cookie' + +// export {Log} from './modules/log' + +export * as config from './config/config' +export * as wg_config from './config/wireguard' +export * as texts from './config/texts' + +export {Global} From 697f6a0abf2d567a69becdfd620ffe4c0a0813da Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Sun, 26 May 2024 12:31:10 +0000 Subject: [PATCH 03/14] Revert "Merge latest to typescript branch" This reverts commit 8652f1ec7668220540ae42f04607d25a4ded758b. --- global.js | 44 +++++++++++++++++++++++++++++++ global.ts | 77 ------------------------------------------------------- 2 files changed, 44 insertions(+), 77 deletions(-) create mode 100644 global.js delete mode 100644 global.ts diff --git a/global.js b/global.js new file mode 100644 index 0000000..39627fd --- /dev/null +++ b/global.js @@ -0,0 +1,44 @@ +/* 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/global.ts b/global.ts deleted file mode 100644 index 65a636f..0000000 --- a/global.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type * as NJ from 'nunjucks' - -declare global { - namespace Global { - export const content: {} - export const favicons: [] - - /* import external modules */ - export const nj: NJ.Environment - - export const microfetch: any - export const Extension: any - export const Tables: any - export const fetch: any - export const data: any - export const DB: any - - export function db(): void - - export namespace Log { - - } - } - - // export * as cookie from 'cookie' - - // export const Log - - // export * as config from './config/config' - // export * as wg_config from './config/wireguard' - // export * as texts from './config/texts' -} - -namespace Global { - export const 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"}, - 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"} - } - - /* import external modules */ - export const nj: NJ.Environment = require('nunjucks').configure(['www/templates','www/pages','www/extensions']) - - export const microfetch: any = require('./modules/microfetch') - export const Extension: any = require('./classes/extension') - export const Tables: any = require('./classes/tables') - export const fetch: any = require('./modules/fetch') - export const data: any = require('./modules/data') - export const DB: any = require('./classes/db') - - export function db(): void {return data.db()} - -} - -export * as cookie from 'cookie' - -// export {Log} from './modules/log' - -export * as config from './config/config' -export * as wg_config from './config/wireguard' -export * as texts from './config/texts' - -export {Global} From ac1e69a725dd1fd20b9647b1d479df7254a62498 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:06:35 +0000 Subject: [PATCH 04/14] Bump nodemon from 3.1.0 to 3.1.2 Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ce3c33..4325518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "sqlite3": "^5.1.6" }, "devDependencies": { - "nodemon": "^3.1.0" + "nodemon": "^3.1.2" } }, "node_modules/@gar/promisify": { @@ -1142,11 +1142,10 @@ } }, "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==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.2.tgz", + "integrity": "sha512-/Ib/kloefDy+N0iRTxIUzyGcdW9lzlnca2Jsa5w73bs3npXjg+WInmiX6VY13mIb6SykkthYX/U5t0ukryGqBw==", "dev": true, - "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", diff --git a/package.json b/package.json index a33361c..2c7d1d0 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,6 @@ }, "homepage": "https://keuk.net/", "devDependencies": { - "nodemon": "^3.1.0" + "nodemon": "^3.1.2" } } From 3ac5ed3dad03af5e5b6b439d101d539d4e68697e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:42:12 +0000 Subject: [PATCH 05/14] Bump nodemon from 3.1.2 to 3.1.3 Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.1.2...v3.1.3) --- updated-dependencies: - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4325518..5cca4b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "sqlite3": "^5.1.6" }, "devDependencies": { - "nodemon": "^3.1.2" + "nodemon": "^3.1.3" } }, "node_modules/@gar/promisify": { @@ -1142,9 +1142,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.2.tgz", - "integrity": "sha512-/Ib/kloefDy+N0iRTxIUzyGcdW9lzlnca2Jsa5w73bs3npXjg+WInmiX6VY13mIb6SykkthYX/U5t0ukryGqBw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz", + "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==", "dev": true, "dependencies": { "chokidar": "^3.5.2", diff --git a/package.json b/package.json index 2c7d1d0..bc445cf 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,6 @@ }, "homepage": "https://keuk.net/", "devDependencies": { - "nodemon": "^3.1.2" + "nodemon": "^3.1.3" } } From 5331f439c9e330690191f731361f8adaf2380be6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:43:46 +0000 Subject: [PATCH 06/14] Bump nodemon from 3.1.3 to 3.1.4 Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.1.3...v3.1.4) --- updated-dependencies: - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5cca4b2..b3af686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "sqlite3": "^5.1.6" }, "devDependencies": { - "nodemon": "^3.1.3" + "nodemon": "^3.1.4" } }, "node_modules/@gar/promisify": { @@ -1142,9 +1142,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz", - "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, "dependencies": { "chokidar": "^3.5.2", diff --git a/package.json b/package.json index bc445cf..4ff46e0 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,6 @@ }, "homepage": "https://keuk.net/", "devDependencies": { - "nodemon": "^3.1.3" + "nodemon": "^3.1.4" } } From 7df59dac406b540437c24b61b2a4e152a3bdd9ea Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Sat, 3 Aug 2024 08:58:30 +0000 Subject: [PATCH 07/14] BIG COMMIT - converted all core files to TypeScript - changed config extensions for syntax highlighting - reformatted config files --- classes/db.js | 100 -------- classes/extension.js | 156 ------------ config/.gitignore | 2 +- config/config.example.ts | 32 +++ config/config.js.example | 30 --- config/texts.example.ts | 17 ++ config/texts.js.example | 15 -- config/wireguard.example.ts | 10 + config/wireguard.js.example | 8 - global.ts | 76 ------ index.ts | 164 ------------ modules/data.js | 95 ------- modules/fetch.js | 52 ---- modules/log.ts | 106 -------- modules/microfetch.js | 38 --- package.json | 6 +- src/classes/db.ts | 87 +++++++ src/classes/extension.ts | 194 +++++++++++++++ {classes => src/classes}/tables.js | 2 +- {www => src}/extensions/admin/index.html | 0 src/extensions/admin/index.js | 16 ++ {www => src}/extensions/chat/index.html | 0 src/extensions/chat/index.js | 61 +++++ {www => src}/extensions/chat/static/index.css | 0 {www => src}/extensions/nothing/index.html | 0 src/extensions/nothing/index.js | 15 ++ .../extensions/profile/devices/android.html | 0 .../extensions/profile/devices/ios.html | 0 .../extensions/profile/devices/linux.html | 0 .../extensions/profile/devices/macos.html | 0 .../extensions/profile/devices/windows.html | 0 {www => src}/extensions/profile/edit.html | 0 {www => src}/extensions/profile/index.html | 0 src/extensions/profile/index.js | 124 ++++++++++ {www => src}/extensions/profile/install.html | 0 .../extensions/profile/static/index.css | 0 {www => src}/extensions/profile/tables.js | 0 {www => src}/extensions/profile/wireguard.js | 4 +- .../root/content/getting-started.html | 0 {www => src}/extensions/root/index.html | 0 src/extensions/root/index.ts | 233 ++++++++++++++++++ {www => src}/extensions/root/login.html | 0 {www => src}/extensions/root/pfp.html | 0 .../extensions/root/static/favicon.ico | Bin {www => src}/extensions/root/static/index.css | 0 .../extensions/root/static/keuknet.png | Bin {www => src}/extensions/root/static/pfp.js | 0 .../extensions/root/static/wallpaper.jpg | Bin {www => src}/extensions/root/tables.js | 0 {www => src}/extensions/root/user.html | 0 src/extman.ts | 17 ++ src/handle.ts | 78 ++++++ src/index.ts | 170 +++++++++++++ src/modules.ts | 16 ++ src/modules/content_type.ts | 25 ++ src/modules/fetch.ts | 42 ++++ src/modules/log.ts | 66 +++++ {www => src}/templates/extension.html | 0 .../templates/includes/description.html | 0 .../templates/includes/extensions.html | 0 {www => src}/templates/includes/favicons.html | 0 {www => src}/templates/includes/header.html | 0 {www => src}/templates/layout.html | 0 tsconfig.json | 21 +- types/classes/db.d.ts | 25 ++ types/classes/extension.d.ts | 41 +++ types/global.d.ts | 41 +++ types/handle.d.ts | 3 + types/modules/content_type.d.ts | 19 ++ types/modules/fetch.d.ts | 3 + types/modules/log.d.ts | 7 + www/extensions/admin/index.js | 10 - www/extensions/chat/index.js | 53 ---- www/extensions/nothing/index.js | 9 - www/extensions/profile/index.js | 114 --------- www/extensions/root/index.js | 134 ---------- www/extensions/servers/addserver.html | 20 -- www/extensions/servers/index.js | 46 ---- www/extensions/servers/server.html | 17 -- www/extensions/servers/serverlist.html | 33 --- www/extensions/servers/static/index.css | 54 ---- www/extensions/servers/tables.js | 19 -- www/index.ts | 72 ------ 83 files changed, 1358 insertions(+), 1440 deletions(-) delete mode 100644 classes/db.js delete mode 100644 classes/extension.js create mode 100644 config/config.example.ts delete mode 100644 config/config.js.example create mode 100644 config/texts.example.ts delete mode 100644 config/texts.js.example create mode 100644 config/wireguard.example.ts delete mode 100644 config/wireguard.js.example delete mode 100644 global.ts delete mode 100644 index.ts delete mode 100644 modules/data.js delete mode 100644 modules/fetch.js delete mode 100644 modules/log.ts delete mode 100644 modules/microfetch.js create mode 100644 src/classes/db.ts create mode 100644 src/classes/extension.ts rename {classes => src/classes}/tables.js (98%) rename {www => src}/extensions/admin/index.html (100%) create mode 100644 src/extensions/admin/index.js rename {www => src}/extensions/chat/index.html (100%) create mode 100644 src/extensions/chat/index.js rename {www => src}/extensions/chat/static/index.css (100%) rename {www => src}/extensions/nothing/index.html (100%) create mode 100644 src/extensions/nothing/index.js rename {www => src}/extensions/profile/devices/android.html (100%) rename {www => src}/extensions/profile/devices/ios.html (100%) rename {www => src}/extensions/profile/devices/linux.html (100%) rename {www => src}/extensions/profile/devices/macos.html (100%) rename {www => src}/extensions/profile/devices/windows.html (100%) rename {www => src}/extensions/profile/edit.html (100%) rename {www => src}/extensions/profile/index.html (100%) create mode 100644 src/extensions/profile/index.js rename {www => src}/extensions/profile/install.html (100%) rename {www => src}/extensions/profile/static/index.css (100%) rename {www => src}/extensions/profile/tables.js (100%) rename {www => src}/extensions/profile/wireguard.js (98%) rename {www => src}/extensions/root/content/getting-started.html (100%) rename {www => src}/extensions/root/index.html (100%) create mode 100644 src/extensions/root/index.ts rename {www => src}/extensions/root/login.html (100%) rename {www => src}/extensions/root/pfp.html (100%) rename {www => src}/extensions/root/static/favicon.ico (100%) rename {www => src}/extensions/root/static/index.css (100%) rename {www => src}/extensions/root/static/keuknet.png (100%) rename {www => src}/extensions/root/static/pfp.js (100%) rename {www => src}/extensions/root/static/wallpaper.jpg (100%) rename {www => src}/extensions/root/tables.js (100%) rename {www => src}/extensions/root/user.html (100%) create mode 100644 src/extman.ts create mode 100644 src/handle.ts create mode 100644 src/index.ts create mode 100644 src/modules.ts create mode 100644 src/modules/content_type.ts create mode 100644 src/modules/fetch.ts create mode 100644 src/modules/log.ts rename {www => src}/templates/extension.html (100%) rename {www => src}/templates/includes/description.html (100%) rename {www => src}/templates/includes/extensions.html (100%) rename {www => src}/templates/includes/favicons.html (100%) rename {www => src}/templates/includes/header.html (100%) rename {www => src}/templates/layout.html (100%) create mode 100644 types/classes/db.d.ts create mode 100644 types/classes/extension.d.ts create mode 100644 types/handle.d.ts create mode 100644 types/modules/content_type.d.ts create mode 100644 types/modules/fetch.d.ts create mode 100644 types/modules/log.d.ts delete mode 100644 www/extensions/admin/index.js delete mode 100644 www/extensions/chat/index.js delete mode 100644 www/extensions/nothing/index.js delete mode 100644 www/extensions/profile/index.js delete mode 100644 www/extensions/root/index.js delete mode 100644 www/extensions/servers/addserver.html delete mode 100644 www/extensions/servers/index.js delete mode 100644 www/extensions/servers/server.html delete mode 100644 www/extensions/servers/serverlist.html delete mode 100644 www/extensions/servers/static/index.css delete mode 100644 www/extensions/servers/tables.js delete mode 100644 www/index.ts 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 eff5424..0000000 --- a/classes/extension.js +++ /dev/null @@ -1,156 +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(ctx) { - let {req, res} = ctx - delete ctx.req - delete ctx.res - req = {...req, ...ctx} - 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/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..86d3066 --- /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: `${__dirname}/../certs/privkey.pem`, + /** The path to your HTTPS certificate-set's certificate. */ + server_cert_path: `${__dirname}/../certs/cert.pem`, + /** The path to your HTTPS certificate-set's CA-chain. */ + ca_cert_path: `${__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.ts b/global.ts deleted file mode 100644 index af43e88..0000000 --- a/global.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type * as NJ from 'nunjucks' - -declare global { - namespace Global { - export const content: {} - - /* import external modules */ - export const nj: NJ.Environment - - export const microfetch: any - export const Extension: any - export const Tables: any - export const fetch: any - export const data: any - export const DB: any - - export function db(): void - - export namespace Log { - - } - } - - // export * as cookie from 'cookie' - - // export const Log - - // export * as config from './config/config' - // export * as wg_config from './config/wireguard' - // export * as texts from './config/texts' -} - -namespace Global { - export const 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"}, - 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"} - } - - /* import external modules */ - export const nj: NJ.Environment = require('nunjucks').configure(['www/templates','www/pages','www/extensions']) - - export const microfetch: any = require('./modules/microfetch') - export const Extension: any = require('./classes/extension') - export const Tables: any = require('./classes/tables') - export const fetch: any = require('./modules/fetch') - export const data: any = require('./modules/data') - export const DB: any = require('./classes/db') - - export function db(): void {return data.db()} - -} - -export * as cookie from 'cookie' - -// export {Log} from './modules/log' - -export * as config from './config/config' -export * as wg_config from './config/wireguard' -export * as texts from './config/texts' - -export {Global} diff --git a/index.ts b/index.ts deleted file mode 100644 index 1873a8b..0000000 --- a/index.ts +++ /dev/null @@ -1,164 +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/. - */ - -interface Handler { - init(global: any): null, - main(ctx: any): null -} - - -// enable use of dotenv -require('dotenv').config() - -// set up global context -import {cookie, config} from './global' -import * as global from './global' -const {fetch, data} = Global -// import {Log} from './modules/log' - - -// get request handler -const handle: Handler = require('./www/index') -// set up modules -Global.Log -const log: Global.Log = new Global.Log(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(global) -}) - -// handle all requests for both HTTPS and HTTP/2 or HTTP/nginx -const requestListener = function (req: Http2ServerRequest, res: Http2ServerResponse) { - let ctx: any = { - req, - res - } - if (process.env.DEV) { - req.headers.host = config.domain - ctx.ip = process.env.IP - } - - ctx.cookies = cookie.parse(req.headers.cookie || '') - // get authorization info - req.headers.authorization ??= ctx.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 - ctx.ip ??= req.socket?.remoteAddress || req.connection?.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 - ctx.ip = req.headers['x-real-ip'] || '0.0.0.0' - } - - let args: string - // separate url arguments from the url itself - [ctx.path, args] = req.url.split('?') - - // split arguments into key:value pairs - ctx.args = {} - if (args) { - for (let arg of args.split('&')) { - let [key, value] = arg.split('=') - ctx.args[key] = value - // allow authentication using argument auth= - if (ctx.args.auth) req.headers.authorization ??= "Basic " + ctx.args.auth - } - } - - // split url into path items - ctx.path = ctx.path.split('/').slice(1) - - // log the request - log.con(req, ctx) - - // wait for all data if posting - if (req.method == 'POST') { - let buffer: Buffer[] = [] - req.on('data', function(data: Buffer) { - buffer.push(data) - }) - req.on('end', function() { - ctx.data = {raw:Buffer.concat(buffer).toString()} - ctx.data.raw.split('&').forEach(function (i: string) { - let [k,v] = i.split('=') - if (k && v) { - ctx.data[k] = decodeURIComponent(v).replace(/\+/g,' ') - } - }) - ctx.post_data = ctx.data.post_data - // forward the request to the handler - handle.main(ctx) - }) - } - // other methods continue - else { - // forward the request to the handler - handle.main(ctx) - } -} - -// 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.ts b/modules/log.ts deleted file mode 100644 index 7fadce7..0000000 --- a/modules/log.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Http2ServerRequest } from "http2"; - -declare global { - namespace Global { - export 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 - } - - export class Log { - we_logging: boolean - - constructor(we_log: boolean) - - __mask_ip(ip: string): string - __mask_url(url: string): string - - con(req: Http2ServerRequest, ctx: { ip: any }): void - con_err(req: Http2ServerRequest): void - status(msg: string): void - err(err: string): void - serverStart(type: string, domain: string, host: string, port: number): void - } - } -} - -namespace Global { - export 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 - } - - export class Log { - we_logging = false; - - constructor(we_log: boolean) { - this.we_logging = we_log - } - - __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 - } - - __mask_url(url: string) { - return url.split('?')[0] - } - - con(req: { url: any; method: any; httpVersion: any; headers: { [x: string]: any; authorization: any } }, ctx: { ip: any }) { - 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: { ip: any; headers: { host: any } }) { - if (this.we_logging) { - let ip = this.__mask_ip(req.ip) - console.log( - `\x1b[35m DEN[${ip}]: '${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/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.json b/package.json index 4e44ec2..95ca947 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "name": "keuknet", "version": "2.4.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", "scripts": { - "start": "npx tsc && node build/index.js", - "dev": "sudo npx nodemon index.ts -e . --ignore ./data/ --exec 'ts-node'" + "start": "npx tsc ./src && node build/index.js", + "dev": "sudo npx nodemon src/index.ts -e . --ignore ./data/ --exec 'ts-node'" }, "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", diff --git a/src/classes/db.ts b/src/classes/db.ts new file mode 100644 index 0000000..4885c65 --- /dev/null +++ b/src/classes/db.ts @@ -0,0 +1,87 @@ +export class DB { + special_tables = [ + 'user', + 'db_table_versions' + ] + db: any + prefix: string + + constructor(db: any, prefix: string) { + this.db = db + this.prefix = '_'+prefix+'_' + } + + __formatTableName = (name: string) => { + if ( + this.special_tables.includes(name) + || name.startsWith('_') + ) + return name + return this.prefix + name + } + + // Table modifications + createTable = (table: string, columns: string[]) => { + table = this.__formatTableName(table) + let columns_str = columns.join(', ') + this.db.run(`CREATE TABLE IF NOT EXISTS ${table} (${columns_str})`, [], (err?: Error) => { + if (err) throw new Error(`Failed creating new table ${table}. ${err}`) + }) + } + + dropTable = (table: string) => { + table = this.__formatTableName(table) + this.db.run(`DROP TABLE ${table}`, [], (err?: Error) => { + if (err) throw new Error(`Failed dropping table ${table}. ${err}`) + }) + } + + // Column modifications + addColumn = (table: string, column: string, callback: (err?: Error) => void) => { + table = this.__formatTableName(table) + this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column}`, [], callback) + } + + dropColumn = (table: string, column: string, callback: (err?: Error) => void) => { + table = this.__formatTableName(table) + this.db.run(`ALTER TABLE ${table} DROP COLUMN ${column}`, [], callback) + } + + renameColumn = (table: string, old_column: string, new_column: string, callback: (err?: Error) => void) => { + table = this.__formatTableName(table) + this.db.run(`ALTER TABLE ${table} RENAME COLUMN ${old_column} to ${new_column}`, [], callback) + } + + // Standard functions + select = (table: string, columns: string[], where: string, order_by: string, params: any, callback: (err: null|Error, data: null|any) => void) => { + table = this.__formatTableName(table) + let columns_str = columns.join(', ') + // Construct query + let query = `SELECT ${columns_str} FROM ${table}` + if (where) query += ` WHERE ${where}` + if (order_by) query += ` ORDER BY ${order_by}` + // Get result + this.db.all(query, params, callback) + } + + insert = (table: string, columns: string[], values: any[], callback: (err?: Error) => void) => { + 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, callback) + } + + update = (table: string, columns: string[], where: string, values: any, callback: (err?: Error) => void) => { + table = this.__formatTableName(table) + let columns_str = columns.join(', ') + // Run query + this.db.run(`UPDATE ${table} SET ${columns_str} WHERE ${where}`, values, callback) + } + + delete = (table: string, where: string, values: any, callback: (err?: Error) => void) => { + table = this.__formatTableName(table) + // Run query + this.db.run(`DELETE FROM ${table} WHERE ${where}`, values, callback) + } +} diff --git a/src/classes/extension.ts b/src/classes/extension.ts new file mode 100644 index 0000000..96d5919 --- /dev/null +++ b/src/classes/extension.ts @@ -0,0 +1,194 @@ +export abstract class ExtensionBase implements Extension { + admin_only = false + tables = false + dependencies: Extension['dependencies'] + initialized_deps = new DependencyMapImpl() + name: Extension['name'] + title: Extension['title'] + + static init(inst: ExtensionBase, context: InitContext): ResultStatus { + let global: any = context.modules + let path = context.path + let database = context.database + + // Init dependencies + for (const dep of inst.dependencies) { + switch (dep) { + case 'fetch': { + inst.initialized_deps.set('fetch', new global.Fetch(path)) + break + } + default: { + inst.initialized_deps.set(dep, global[dep]) + } + } + } + // Init db + if (inst.tables) { + // create interface + inst.initialized_deps.set('db', new global.DB(database, this.name)) + // init tables + new ((require(`${path}tables`))(global.Tables))(database, inst.initialized_deps.get('db'), this.name) + } + + return [true] + } + + abstract init: Extension['init'] + + /** + * @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'] = (ctx: Context) => { + ctx.context.extension = this as unknown as Extension + return this.handle(ctx, this.initialized_deps) + } + + 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] = 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: null|Error, data: FileData) => { + if (err) { + res.writeHead(500) + return res.end() + } + res.writeHead(200, content.HTML) + return res.end(data) + }) + } + + return_html: Extension['return_html'] = (ctx, item, err, err_code=500, success_code=200, headers=undefined) => { + const {req, res} = ctx + let [nj, content] = this.initialized_deps.massGet('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) + return res.end(data) + }) + } + + return_file: Extension['return_file'] = (ctx, file) => { + const {res} = ctx + let [fetch, content] = this.initialized_deps.massGet('fetch', 'content') + + fetch.file(file, (data: FileData, filetype: string, err?: Error) => { + if (err) { + res.writeHead(404) + res.end() + return + } + 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.initialized_deps.get('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.initialized_deps.get('cookie') + + return cookie.serialize( + key, + '', { + expires: new Date(1) + } + ) + } +} + +class DependencyMapImpl extends Map implements DependencyMap { + override set(key: string, value: any): this { + if (!this.has(key)) + super.set(key, value) + return this + } + + override delete(key: string): boolean { + return false + } + + override clear(): void {} + + massGet(...items: string[]): any[] { + let result: any[] = [] + items.forEach((v, _, __) => + result.push(this.get(v)) + ) + return result + } +} diff --git a/classes/tables.js b/src/classes/tables.js similarity index 98% rename from classes/tables.js rename to src/classes/tables.js index 7912d8f..0879d91 100644 --- a/classes/tables.js +++ b/src/classes/tables.js @@ -1,4 +1,4 @@ -module.exports = class Tables { +module.exports.Tables = class Tables { tables = {} constructor(raw_db, db, prefix) { 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.js b/src/extensions/admin/index.js new file mode 100644 index 0000000..35ed746 --- /dev/null +++ b/src/extensions/admin/index.js @@ -0,0 +1,16 @@ +import { ExtensionBase } from "../../modules" + +export default class extends ExtensionBase { + name = 'admin' + title = 'Admin' + admin_only = true + dependencies = ['content','nj'] + + init(context) { + return ExtensionBase.init(this, context) + } + + handle(req, res) { + this.return_html(req, res, '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.js b/src/extensions/chat/index.js new file mode 100644 index 0000000..d0da069 --- /dev/null +++ b/src/extensions/chat/index.js @@ -0,0 +1,61 @@ +import { ExtensionBase } from "../../modules" + +export default class extends ExtensionBase { + 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 + } + + init(context) { + return ExtensionBase.init(this, context) + } + + handle(ctx, deps) { + let [] = this.initialized_deps.massGet() + var location = ctx.path.shift() + + 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[ctx.context.user.id] = this.messages.length + return this.return_html(ctx, 'index') + } + else if (location == 'getnew') { + var part = this.last_got_id.hasOwnProperty(ctx.context.user.id) ? this.last_got_id[ctx.context.user.id] : 0 + this.last_got_id[ctx.context.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.js b/src/extensions/nothing/index.js new file mode 100644 index 0000000..efaa061 --- /dev/null +++ b/src/extensions/nothing/index.js @@ -0,0 +1,15 @@ +import { ExtensionBase } from "../../modules" + +export default class extends ExtensionBase { + name = 'nothing' + title = 'Nothing' + dependencies = ['content','nj'] + + init = (context) => { + return ExtensionBase.init(this, context) + } + + handle(ctx, deps) { + 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.js b/src/extensions/profile/index.js new file mode 100644 index 0000000..38eccd3 --- /dev/null +++ b/src/extensions/profile/index.js @@ -0,0 +1,124 @@ +import crypto from 'crypto' +import { ExtensionBase } from '../../modules' + +export default class extends ExtensionBase { + name = 'profile' + title = 'Network' + tables = true + dependencies = ['content','nj','fetch'] + wg = require('./wireguard') + wg_config = null + + + init(context) { + let config = context.modules.config + let data_path = context.data_path + this.wg_config = context.modules.wg_config + + this.wg.init(data_path, this.wg_config, config.tmp_dir) + + return ExtensionBase.init(this, context) + } + + requires_login(path) { + if (path.at(0) == 'getconf') { + return false + } + return true + } + + handle(ctx, deps) { + let [db] = deps.massGet('db') + var location = ctx.path.shift() + + switch (location) { + case '': + case undefined: { + db.select('device', ['*'], 'user_id=$id', null, [ctx.context.user.id], (err, profiles) => { + ctx.context.profiles = profiles + ctx.context.connected_ip = ctx.ip.startsWith(this.wg_config.subnet) ? ctx.ip : false + this.return_html(ctx, 'index', err) + }) + break + } + case 'delete': { + // Check ownership + this.owns(ctx.context.user, ctx.args.get('uuid'), (user_owns) => { + if (!user_owns) return this.return(ctx, true, 404) + // Delete db entry + db.delete('device', 'uuid=$uuid', [ctx.args.get('uuid')], (err) => { + // Delete wireguard profile + this.wg.delete(ctx.args.get('uuid'), () => { + return this.return(ctx, err, location='/profile') + }) + }) + }) + break + } + case 'add': { + // Get uuid + let uuid = crypto.randomUUID() + // Get IP suffix + 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 + db.insert('device', ['user_id','uuid','ip'], [ctx.context.user.id, uuid, ip], (err) => { + return this.return(ctx, err, location='/profile') + }) + }) + }) + break + } + case 'getconf': { + // Get uuid + let uuid = ctx.args.keys().next().value + // Get config + this.wg.getConfig(uuid, (data, err) => { + if (err) return this.return(ctx, true, 404) + // Mark as installed + db.update('device', ['installed=TRUE'], 'uuid=$uuid', [uuid], (err) => { + return this.return_data(ctx, data, err, {"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 + this.owns(ctx.context.user, ctx.args.get('uuid'), (user_owns) => { + if (!user_owns) return this.return(ctx, true, 404) + // Change name + db.update('device', ['name=$name'], 'uuid=$uuid', [ctx.data.form.post_data,ctx.args.get('uuid')], (err) => { + return this.return(ctx, err, location='/profile') + }) + }) + } + else { + 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 = (user, uuid, callback) => { + let db = this.initialized_deps.get('db') + + if (!uuid) return callback(undefined) + 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/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/www/extensions/profile/tables.js b/src/extensions/profile/tables.js similarity index 100% rename from www/extensions/profile/tables.js rename to src/extensions/profile/tables.js diff --git a/www/extensions/profile/wireguard.js b/src/extensions/profile/wireguard.js similarity index 98% rename from www/extensions/profile/wireguard.js rename to src/extensions/profile/wireguard.js index 2d03b6e..220375d 100644 --- a/www/extensions/profile/wireguard.js +++ b/src/extensions/profile/wireguard.js @@ -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 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..81d715c --- /dev/null +++ b/src/extensions/root/index.ts @@ -0,0 +1,233 @@ +import crypto from 'crypto' +import { ExtensionBase } from "../../modules" +import { readdirSync } from "fs" + + +export default class extends ExtensionBase implements RootExtension { + override name = 'root' + override title = 'Home' + override tables = true + override dependencies = ['content','nj','fetch','texts','cookie'] + + favicons: string[] = [] + favicons_path: string + + ip_scope: string + salt: string + + db: Database + + + 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.db = context.database + + 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}'`) + } + + let status = ExtensionBase.init(this, context) + + return status + } + + override requires_login = (path: string[]) => { + if (path.at(0) == '_') { + return true + } + return false + } + + override handle = (ctx: Context, deps: DependencyMap) => { + var location = ctx.path.shift() + let [db, data] = deps.massGet('db', 'data') + + 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 '_': { + var item = ctx.path.shift() + switch (item) { + case 'pfp': { + var args = ctx.req.url.split('?').at(1) + if (args) { + try { + args = decodeURIComponent(args) + } catch {} + db.update('user', ['pfp_code=$args'], 'id=$id', [args, ctx.context.user.id], (err?: Error) => { + if (err) + ctx.res.writeHead(500) + else + ctx.res.writeHead(307, {"Location": "/"}) + 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(auth: BasicAuth | undefined, ip: string, subnet: string, callback: (user: undefined|User, err?: Error) => void): void { + if (auth) { + // Try to get name and password + this.decrypt_auth(auth, (name, password, err) => { + if (err) { + return callback(undefined, err) + } + // Auth using name and password + this.db.get("SELECT * FROM user WHERE name=$name", name, (err?: Error|null, user?: User) => { + if (err === null) err = undefined + + if (user) { + if (password == user.password) { + return callback(user, err) + } + } + return callback(undefined, new Error("Wrong name or password")) + }) + }) + } + else if (ip.startsWith(subnet)) { + // Try using IP-address if no name and password + this.db.get("SELECT u.* FROM user u JOIN _profile_device p ON p.user_id = u.id WHERE p.ip=$ip", ip, function (err?: Error|null, user?: User) { + if (err === null) err = undefined + return callback(user, err) + }) + } + else + return callback(undefined, undefined) + } + + private decrypt_auth(auth: BasicAuth, callback: (name?: string, password?: string, err?: Error) => void): void { + // 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 callback(undefined, undefined, new Error("Missing name or password")) + } + + // hash password + password = this.hash_pw(password) + + return callback(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) { + 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 + this.db.run("INSERT INTO user(name,password,pfp_code) VALUES($name,$password,$pfp_code)", [name, password, 'seed='+name], (err?: Error|null) => { + if (err === null) err = undefined + return callback(err) + }) + }) + } + + private exists(name: User['name'], callback: (exists: boolean, err?: Error) => void): void { + // check if name already exists + this.db.get("SELECT EXISTS(SELECT 1 FROM user WHERE name=$name)", name, function (err?: Error|null, result?: Object) { + if (err === null) err = undefined + let exists = false + if (result) exists = !!Object.values(result)[0] + return callback(exists, 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/www/extensions/root/tables.js b/src/extensions/root/tables.js similarity index 100% rename from www/extensions/root/tables.js rename to src/extensions/root/tables.js 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..b55dafc --- /dev/null +++ b/src/extman.ts @@ -0,0 +1,17 @@ +export function load(modules: any, database: Database, namespace: string): unknown { + let context: InitContext = { + modules, + database, + path: `${__dirname}/extensions/${namespace}/`, + data_path: `${__dirname}/../data/${namespace}/`, + } + + let ext = new (require(`./extensions/${namespace}/index`).default) + let status = ext.init(context) + + if (status.Okay == false) { + throw new Error(`Failed initializing: ${status.Error}`) + } + + return ext +} diff --git a/src/handle.ts b/src/handle.ts new file mode 100644 index 0000000..f2cac0c --- /dev/null +++ b/src/handle.ts @@ -0,0 +1,78 @@ +import { load } from "./extman" +import Log from "./modules/log" + +let log = new Log(true) + +export default class implements Handle { + extensions_list = [ + 'profile','nothing','admin','chat' + ] + root: RootExtension + wg_config: any + extensions: Record + admin_extensions: Record + + constructor(modules: any, database: 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) + + this.root = load(modules, database, 'root') as RootExtension + + this.extensions = {} + for (const path of this.extensions_list) { + console.log(path) + try { + this.extensions[path] = load(modules, database, path) as Extension + } catch (err) { + log.err(`Unable to load extension '${path}':\n\t${err}`) + } + } + + this.admin_extensions = {} + for (const ext of Object.keys(this.extensions)) { + if (this.extensions[ext].admin_only) { + this.admin_extensions[ext] = this.extensions[ext] + delete this.extensions[ext] + } + } + } + + main: Handle['main'] = (ctx: Context) => { + var location = ctx.path.shift() || '' + + // set request context + ctx.context = {...ctx.args} + ctx.context.extensions = this.extensions + ctx.context.location = location + + // Authenticate using user&pass, else using ip + this.root.authenticate(ctx.req.headers.authorization as BasicAuth|undefined, ctx.ip, this.wg_config.subnet, (user, err) => { + ctx.context.user = user + if (err) ctx.context.auth_err = err + if (user && user.is_admin) ctx.context.extensions = {...ctx.context.extensions, ...this.admin_extensions} + + // Extension + if (location in ctx.context.extensions) { + let ext = ctx.context.extensions[location] + // If login required + if (!user && ext.requires_login(ctx.path)) { + ctx.res.writeHead(307, {Location: "/login"}) + return ctx.res.end() + } + if (user && !user.is_admin && ext.requires_admin(ctx.path)) { + ctx.res.writeHead(307, {Location: "/"}) + return ctx.res.end() + } + ext.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..31ff569 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,170 @@ +import sqlite3 from 'sqlite3' + +// enable use of dotenv +require('dotenv').config() + +// set up global context +import {cookie, config, Log} from './modules' +import * as modules from './modules' + +const db = new (sqlite3.verbose()).Database(`${__dirname}/../data/db.sqlite`) + +// get request handler +import Handle from './handle' +let handle = new Handle(modules, db) +// set up modules +const log: Log = new Log(config.logging) + +// handle all requests for both HTTPS and HTTP/2 or HTTP/nginx +const requestListener = async function (req: Http2ServerRequest, res: Http2ServerResponse) { + let ip: Context['ip'] + let cookies: Context['cookies'] + let args: Context['args'] + 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' + } + + { + let raw_path: string + let raw_args: string + // separate url arguments from the url itself + [raw_path, raw_args] = req.url.split('?') + + // split arguments into key:value pairs + args = new Map() + if (raw_args) { + for (let arg of raw_args.split('&')) { + let [key, value] = arg.split('=') + 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) + 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: Context = { + req, + res, + path, + args, + cookies, + ip, + data + } + // log the request + log.con(req, ctx) + // forward the request to the handler + handle.main(ctx) +} + +// Redirect requests to HTTPS +const httpsRedirect = function (req: Http2ServerRequest, res: Http2ServerResponse) { + res.writeHead(307, {"Location": `https://${req.headers.host}${req.url}`}) + res.end() +} + + +function startServer(http: boolean, https: boolean) { + if (https) { + let key = function (location: string, callback: (data?: any) => void) { + require('fs').promises.readFile(location, "utf8") + .then(callback) + .catch((err: Error) => { + log.err(`Failed fetching key at: '${location}'`) + callback(undefined) + }) + } + + log.status("Fetching encryption keys") + // Private key + key(config.private_key_path, function(private_key) { + // Certificate + key(config.server_cert_path, function(certificate) { + // Certificate chain + key(config.ca_cert_path, function(certificate_authority) { + log.status("Encryption keys fetched") + // Start server + require('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) { + // 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/src/modules.ts b/src/modules.ts new file mode 100644 index 0000000..a6e97de --- /dev/null +++ b/src/modules.ts @@ -0,0 +1,16 @@ +import * as Nunjucks from 'nunjucks' +export const nj: Environment = Nunjucks.configure([ + __dirname+'/templates', + __dirname+'/extensions' +]) + +export {default as cookie} from 'cookie' +export {ExtensionBase} from './classes/extension' +export {Tables} from './classes/tables' +export {DB} from './classes/db' +export {default as Log} from './modules/log' +export * as content from './modules/content_type' +export {default as Fetch} from './modules/fetch' +export {default as config} from '../config/config' +export {default as wg_config} from '../config/wireguard' +export {default as texts} from '../config/texts' diff --git a/src/modules/content_type.ts b/src/modules/content_type.ts new file mode 100644 index 0000000..47f8ef2 --- /dev/null +++ b/src/modules/content_type.ts @@ -0,0 +1,25 @@ +// Source: https://stackoverflow.com/a/51398471/15181929 + +export default (class { + 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) { + } +}) as ContentType diff --git a/src/modules/fetch.ts b/src/modules/fetch.ts new file mode 100644 index 0000000..898c2bc --- /dev/null +++ b/src/modules/fetch.ts @@ -0,0 +1,42 @@ +import { readFile } from "fs/promises" +import * as path from "path" + +export default class implements Fetch { + /** File extensions of binary filetypes */ + private readonly BIN_EXTS: Set = new Set(['png','jpg','mp3']) + /** Caches processed files */ + private cache: Map = new Map() + private root: string + + constructor (raw_path: string) { + this.root = path.join(raw_path, "/static/") + } + + file: Fetch['file'] = (file_path, callback) => { + // 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 callback(this.cache.get(file_path), filetype) + + // read the file + readFile(file_path) + // cache and return the data + .then((data: FileData) => { + if (!this.BIN_EXTS.has(filetype)) + data = data.toString('utf8') + + this.cache.set(file_path, data) + return callback(data, filetype) + }) + // error if file can't be read + .catch(err => { + return callback(undefined, undefined, err) + }) + } +} diff --git a/src/modules/log.ts b/src/modules/log.ts new file mode 100644 index 0000000..b5467dc --- /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) { + 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/tsconfig.json b/tsconfig.json index 2e452b4..dc6378f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,20 +2,17 @@ "compilerOptions": { "outDir": "./build", "allowJs": true, - // "target": "es5" + "target": "es2022", "moduleResolution": "Node16", "module": "Node16", "noImplicitThis": true, - // "types": [ - // "./global.d.ts", - // "node", - // "nunjucks", - // "cookie" - // ] - // "declaration": true, - // "declarationDir": "./types" - "typeRoots": ["./node_modules/@types", "types"] + "noImplicitAny": true, + "noImplicitOverride": true, + "strictNullChecks": true, + "typeRoots": ["./node_modules/@types", "./types/**/*"], }, - // "include": ["./**/*"], - "exclude": ["./build"] + "ts-node": { + "files": true, + }, + "exclude": ["./build"], } diff --git a/types/classes/db.d.ts b/types/classes/db.d.ts new file mode 100644 index 0000000..86dd4e2 --- /dev/null +++ b/types/classes/db.d.ts @@ -0,0 +1,25 @@ +declare interface DB { + special_tables: string[] + db: any + prefix: string + + __formatTableName(name: string): string + + createTable(table: string, columns: string[]): void + + dropTable(table: string): void + + addColumn(table: string, column: string, callback: (err: Error|null) => void): void + + dropColumn(table: string, column: string, callback: (err: Error|null) => void): void + + renameColumn(table: string, old_column: string, new_column: string, callback: (err: Error|null) => void): void + + select(table: string, columns: string[], where: string, order_by: string, params: {}, callback: (err: Error|null, data: any) => void): void + + insert(table: string, columns: string[], values: [], callback: (err: Error|null) => void): void + + update(table: string, columns: string[], where: string, values: {}, callback: (err: Error|null) => void): void + + delete(table: string, where: string, values: {}, callback: (err: Error|null) => void): void +} diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts new file mode 100644 index 0000000..05dd089 --- /dev/null +++ b/types/classes/extension.d.ts @@ -0,0 +1,41 @@ +declare interface Extension { + admin_only: boolean + tables: boolean + dependencies: string[] + + name: string + title: string + + init(context: InitContext): ResultStatus + + requires_login(path: string[]): boolean + + requires_admin(path: string[]): boolean + + handle_req(ctx: Context): void | Error + + handle(ctx: Context, mods: DependencyMap): void | Error + + 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 +} + +declare interface RootExtension extends Extension { + authenticate(auth: BasicAuth|undefined, ip: string, subnet: string, callback: (user: undefined|User, err?: Error) => void): void + addUser(name: User['name'], password: User['password'], callback: (err?: Error) => void): void +} + +declare interface DependencyMap extends Map { + massGet(...items: string[]): any[] +} \ No newline at end of file diff --git a/types/global.d.ts b/types/global.d.ts index f9064a3..27e6eec 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -1,2 +1,43 @@ 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 = Buffer | string + +declare type Context = { + req: Http2ServerRequest + res: Http2ServerResponse + + ip: string + cookies: Record + path: string[] + args: Map + data?: {bytes: Buffer, raw: string, form: any} + context?: any +} + +declare type User = { + id: number, + name: string, + password: string, + regdate: Date, + is_admin: boolean, + pfp_code: string +} + +declare type InitContext = { + modules: any, + database: Database, + path: string, + data_path: string, +} + +declare type ResultStatus = [Okay: false, Error: Error] | [Okay: true] diff --git a/types/handle.d.ts b/types/handle.d.ts new file mode 100644 index 0000000..eff6233 --- /dev/null +++ b/types/handle.d.ts @@ -0,0 +1,3 @@ +declare interface Handle { + main(ctx: Context): 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..0d5336e --- /dev/null +++ b/types/modules/fetch.d.ts @@ -0,0 +1,3 @@ +declare interface Fetch { + file(file_path: string, callback: (data?: FileData, type?: string, err?: Error) => void): void +} 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/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/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.ts b/www/index.ts deleted file mode 100644 index 06244d9..0000000 --- a/www/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Http2ServerResponse } from "http2" - -var extensions_list = [ - 'profile','servers','nothing','admin','chat' -] - -const {data, nj} = Global -var root -var wg_config, extensions, admin_extensions -exports.init = function (global: any) { - let texts, config - ({wg_config,texts,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 (const path of extensions_list) { - 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] - } - } -} -// import type { Http2ServerRequest } from "./../global.d" - -exports.main = function (ctx) { - var {req, res}: {req: Http2ServerRequest, res: Http2ServerResponse} = ctx - - var location = ctx.path.shift() - - // set request context - ctx.context = {...ctx.args} - ctx.context.extensions = extensions - ctx.context.location = location - - // Authenticate using user&pass, else using ip - data.authenticate(req.headers.authorization, ctx.ip, wg_config.subnet, function (user, err) { - ctx.context.user = user - if (err) ctx.context.auth_err = err - if (user && user.is_admin) ctx.context.extensions = {...ctx.context.extensions, ...admin_extensions} - - // Extension - if (location in ctx.context.extensions) { - let ext = ctx.context.extensions[location] - // If login required - if (!user && ext.requires_login(ctx.path)) { - res.writeHead(307, {Location: "/login"}) - res.end() - return - } - if (user && !user.is_admin && ext.requires_admin(ctx.path)) { - res.writeHead(307, {Location: "/"}) - res.end() - return - } - ext.handle_req(ctx) - } - // Root extension - else { - ctx.path.unshift(location) - root.handle_req(ctx) - } - }) -} From 9085c7e3b19a099298977896608e3ee8c2a99449 Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Sat, 3 Aug 2024 09:28:00 +0000 Subject: [PATCH 08/14] update packages --- package-lock.json | 70 ++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c4818f..2363dac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,9 +54,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, "license": "MIT" }, @@ -143,9 +143,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "20.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", + "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -173,9 +173,9 @@ "optional": true }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "license": "MIT", "bin": { @@ -186,11 +186,14 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dev": true, "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -526,9 +529,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -747,6 +750,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": { @@ -1248,9 +1252,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.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -1260,13 +1264,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", @@ -1294,9 +1295,9 @@ } }, "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==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1558,6 +1559,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": { @@ -1598,9 +1600,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" @@ -1965,9 +1967,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", From 12af20218d28b3786b559543958d78e492a4ff28 Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:49:05 +0000 Subject: [PATCH 09/14] alot of shit this branch has become such a mess --- .gitignore | 3 + package-lock.json | 640 +++++++++++++++++++++-------- package.json | 7 +- src/classes/extension.ts | 110 +++-- src/classes/tables.js | 60 --- src/classes/tables.ts | 110 +++++ src/extensions/profile/index.js | 4 +- src/extensions/profile/tables.js | 40 +- src/extensions/root/index.ts | 44 +- src/extensions/root/tables.js | 38 -- src/extensions/root/tables.ts | 47 +++ src/{extman.ts => extman.cts} | 10 +- src/handle.ts | 49 +-- src/index.ts | 65 +-- src/modules.ts | 24 +- src/modules/content_type.ts | 8 +- src/{classes => modules}/db.ts | 35 +- src/modules/fetch.ts | 9 +- src/modules/log.ts | 2 +- tsconfig.json | 12 +- types/classes/extension.d.ts | 10 +- types/global.d.ts | 27 +- types/handle.d.ts | 2 +- types/{classes => modules}/db.d.ts | 4 +- 24 files changed, 914 insertions(+), 446 deletions(-) delete mode 100644 src/classes/tables.js create mode 100644 src/classes/tables.ts delete mode 100644 src/extensions/root/tables.js create mode 100644 src/extensions/root/tables.ts rename src/{extman.ts => extman.cts} (54%) rename src/{classes => modules}/db.ts (73%) rename types/{classes => modules}/db.d.ts (80%) 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/package-lock.json b/package-lock.json index 2363dac..93dfaa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,57 +20,424 @@ "@types/node": "^20.12.12", "@types/nunjucks": "^3.2.6", "nodemon": "^3.1.0", - "ts-node": "^10.9.2" + "tsx": "^4.19.2" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "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", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "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 + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "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": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "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" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "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", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "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": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -107,34 +474,6 @@ "node": ">= 6" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -172,32 +511,6 @@ "license": "ISC", "optional": true }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -284,13 +597,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -521,13 +827,6 @@ "node": ">= 0.6" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -586,16 +885,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -651,6 +940,46 @@ "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/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -740,6 +1069,19 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/get-tsconfig": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.9.0.tgz", + "integrity": "sha512-52n24W52sIueosRe0XZ8Ex5Yle+WbhfCKnV/gWXpbVR8FXNTfqdKEKUSypKso66VRHTvvcQxL44UTZbJRlCTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1046,13 +1388,6 @@ "node": ">=10" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -1545,6 +1880,16 @@ "node": ">=8.10.0" } }, + "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": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -1910,48 +2255,24 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "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": "MIT", "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" }, "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "tsx": "dist/cli.mjs" }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "engines": { + "node": ">=18.0.0" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "optionalDependencies": { + "fsevents": "~2.3.3" } }, "node_modules/tunnel-agent": { @@ -2019,13 +2340,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2063,16 +2377,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } } } } diff --git a/package.json b/package.json index 95ca947..c4c5f39 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,10 @@ "version": "2.4.1", "description": "A webserver and client program for easily managing a WireGuard-network in a client-server setting.", "main": "src/index.ts", + "type": "module", "scripts": { - "start": "npx tsc ./src && node build/index.js", - "dev": "sudo npx nodemon src/index.ts -e . --ignore ./data/ --exec 'ts-node'" + "start": "npx tsc ./src/index.ts && node build/index.js", + "dev": "sudo npx nodemon src/index.ts -e . --ignore ./data/ --exec 'tsx'" }, "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", @@ -29,6 +30,6 @@ "@types/node": "^20.12.12", "@types/nunjucks": "^3.2.6", "nodemon": "^3.1.0", - "ts-node": "^10.9.2" + "tsx": "^4.19.2" } } diff --git a/src/classes/extension.ts b/src/classes/extension.ts index 96d5919..1065abf 100644 --- a/src/classes/extension.ts +++ b/src/classes/extension.ts @@ -1,37 +1,28 @@ +import { Environment } from "nunjucks" +import { Tables } from "./tables.ts" + export abstract class ExtensionBase implements Extension { admin_only = false tables = false - dependencies: Extension['dependencies'] - initialized_deps = new DependencyMapImpl() - name: Extension['name'] - title: Extension['title'] + dependencies: Extension['dependencies'] = [] + initialized_deps: DependencyMap = new DependencyMapImpl() + name: Extension['name'] = "default_name" + title: Extension['title'] = "Default Title" - static init(inst: ExtensionBase, context: InitContext): ResultStatus { + static init(inst: ExtensionBase, context: InitContext): void | Promise { let global: any = context.modules let path = context.path let database = context.database - // Init dependencies - for (const dep of inst.dependencies) { - switch (dep) { - case 'fetch': { - inst.initialized_deps.set('fetch', new global.Fetch(path)) - break - } - default: { - inst.initialized_deps.set(dep, global[dep]) - } - } - } + inst.initialized_deps = new DependencyMapImpl(global, context) + // Init db if (inst.tables) { - // create interface - inst.initialized_deps.set('db', new global.DB(database, this.name)) // init tables - new ((require(`${path}tables`))(global.Tables))(database, inst.initialized_deps.get('db'), this.name) + let tables = new (require(`${path}tables`).default)(database, inst.initialized_deps.get('DB'), inst.name) as Tables + let result = tables.migrate() + return result } - - return [true] } abstract init: Extension['init'] @@ -74,18 +65,22 @@ export abstract class ExtensionBase implements Extension { return_text: Extension['return_text'] = (ctx, item) => { const {req, res} = ctx - let [texts, nj, content] = this.initialized_deps.massGet('texts', 'nj', 'content') + 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: null|Error, data: FileData) => { + ctx.context, (err: Error | null, data: FileData) => { if (err) { res.writeHead(500) return res.end() } res.writeHead(200, content.HTML) - return res.end(data) + + if (data !== null) + return res.end(data) + else + return res.end() }) } @@ -106,22 +101,31 @@ export abstract class ExtensionBase implements Extension { return res.end() } res.writeHead(success_code, headers) - return res.end(data) + + if (data !== null) + return res.end(data) + else + return res.end() }) } return_file: Extension['return_file'] = (ctx, file) => { const {res} = ctx - let [fetch, content] = this.initialized_deps.massGet('fetch', 'content') + let [fetch, content]: [Fetch, ContentType] = this.initialized_deps.massGet('Fetch', 'content') - fetch.file(file, (data: FileData, filetype: string, err?: Error) => { + fetch.file(file, (data?: FileData, filetype?: string, err?: Error) => { if (err) { res.writeHead(404) res.end() return } + // @ts-ignore res.writeHead(200, content[filetype]) - res.end(data) + + if (data != null) + return res.end(data) + else + return res.end() }) } @@ -171,21 +175,49 @@ export abstract class ExtensionBase implements Extension { } } -class DependencyMapImpl extends Map implements DependencyMap { - override set(key: string, value: any): this { - if (!this.has(key)) - super.set(key, value) +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 } - override delete(key: string): boolean { - return false + 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) } - - override clear(): void {} - massGet(...items: string[]): any[] { - let result: any[] = [] + massGet(...items: S): VariableSizeArray { + let result = [] as VariableSizeArray items.forEach((v, _, __) => result.push(this.get(v)) ) diff --git a/src/classes/tables.js b/src/classes/tables.js deleted file mode 100644 index 0879d91..0000000 --- a/src/classes/tables.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports.Tables = 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/src/classes/tables.ts b/src/classes/tables.ts new file mode 100644 index 0000000..76e1d57 --- /dev/null +++ b/src/classes/tables.ts @@ -0,0 +1,110 @@ +export abstract class Tables { + static readonly SPECIAL = ['user', 'db_table_versions'] + private raw_db: Database + db: DB + prefix: string + + constructor(raw_db: Database, db: DB, prefix: string) { + this.raw_db = raw_db + this.db = db + this.prefix = prefix + } + + migrate(): Promise { + return new Promise((resolve, reject) => { + let versions = this.versions(new Map()) + let migrations = this.migrations(this.db, new Map()) + + this.raw_db.serialize(()=> { + this.db.select('db_table_versions', ['table_id','version'], null, null, [], (err, data) => { + if (err instanceof Error) { // Should only occur on this one missing and root called first + let migration = Tables.getMigration(migrations, 'db_table_versions', 0) + if (migration instanceof Error) return reject(migration) + else migration = migration as Migration + migration() + } + else { + 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.db.formatTableName(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}'`) + migration[0]() + + current_versions.set(table_id, 0) + this.raw_db.run("INSERT INTO db_table_versions (table_id, version) VALUES ($table, $version)", [table_id, current_versions.get(table_id)], (err) => { + if (err) return reject(`Failed adding table version identifier. ${err}`) + console.log(`Added table ${table_id}`) + }) + } + 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) + migration() + } + catch (err) { + console.error(err) + break + } + + current_versions.set(table_id, new_version) + + this.raw_db.run("UPDATE db_table_versions SET version=$version WHERE table_id=$table", [new_version, table_id], (err) => { + if (err) + return reject(`Failed updating table version identifier. ${err}`) + console.log(`Upgraded table ${table_id} to ${new_version}`) + }) + } + } + } + }) + }) + 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(db: DB, 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/src/extensions/profile/index.js b/src/extensions/profile/index.js index 38eccd3..1729705 100644 --- a/src/extensions/profile/index.js +++ b/src/extensions/profile/index.js @@ -28,7 +28,7 @@ export default class extends ExtensionBase { } handle(ctx, deps) { - let [db] = deps.massGet('db') + let [db] = deps.massGet('DB') var location = ctx.path.shift() switch (location) { @@ -114,7 +114,7 @@ export default class extends ExtensionBase { } owns = (user, uuid, callback) => { - let db = this.initialized_deps.get('db') + let db = this.initialized_deps.get('DB') if (!uuid) return callback(undefined) db.select('device', ['1'], 'user_id=$id AND uuid=$uuid', null, [user.id, uuid], (err, data) => { diff --git a/src/extensions/profile/tables.js b/src/extensions/profile/tables.js index be8cb98..fb37daf 100644 --- a/src/extensions/profile/tables.js +++ b/src/extensions/profile/tables.js @@ -1,19 +1,27 @@ -module.exports = (Tables) => {return class extends Tables { - tables = { - 'device': 0 +import { Tables } from '../../classes/tables' + +export default class extends Tables { + versions(versions) { + versions.set('device', 0) + + return versions } - 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)' - ]) - } + migrations(db, migrations) { + migrations.set('device', { + 0: ()=>{ + 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)' + ]) + } + }) + + return migrations } -}} +} diff --git a/src/extensions/root/index.ts b/src/extensions/root/index.ts index 81d715c..f0a99c9 100644 --- a/src/extensions/root/index.ts +++ b/src/extensions/root/index.ts @@ -32,9 +32,7 @@ export default class extends ExtensionBase implements RootExtension { (new modules.Log as Log).err(`No favicons found in '${this.favicons_path}'`) } - let status = ExtensionBase.init(this, context) - - return status + return ExtensionBase.init(this, context) } override requires_login = (path: string[]) => { @@ -46,7 +44,7 @@ export default class extends ExtensionBase implements RootExtension { override handle = (ctx: Context, deps: DependencyMap) => { var location = ctx.path.shift() - let [db, data] = deps.massGet('db', 'data') + let [db]: [DB] = deps.massGet('DB') switch (location) { case '': @@ -119,25 +117,27 @@ export default class extends ExtensionBase implements RootExtension { return } case '_': { - var item = ctx.path.shift() - switch (item) { - case 'pfp': { - var args = ctx.req.url.split('?').at(1) - if (args) { - try { - args = decodeURIComponent(args) - } catch {} - db.update('user', ['pfp_code=$args'], 'id=$id', [args, ctx.context.user.id], (err?: Error) => { - if (err) - ctx.res.writeHead(500) - else - ctx.res.writeHead(307, {"Location": "/"}) - ctx.res.end() - }) - return + 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 {} + db.update('user', ['pfp_code=$args'], 'id=$id', [args, ctx.context.user.id], (err: Error|null) => { + if (err) + ctx.res.writeHead(500) + else + ctx.res.writeHead(307, {"Location": "/"}) + ctx.res.end() + }) + return + } + else + return this.return_html(ctx, 'pfp') } - else - return this.return_html(ctx, 'pfp') } } break diff --git a/src/extensions/root/tables.js b/src/extensions/root/tables.js deleted file mode 100644 index 5613cb7..0000000 --- a/src/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/src/extensions/root/tables.ts b/src/extensions/root/tables.ts new file mode 100644 index 0000000..616502c --- /dev/null +++ b/src/extensions/root/tables.ts @@ -0,0 +1,47 @@ +import { MigrationMap, Tables, VersionMap } from '../../classes/tables' + +export default class extends Tables { + override versions(versions: VersionMap) { + versions.set('user', 1) + versions.set('db_table_versions', 0) + + return versions + } + + override migrations(db: DB, migrations: MigrationMap) { + migrations.set('user', { + 0: ()=>{ + 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: ()=>{ + db.select('user', ['id','name'], null, null, [], (err, data) => { + if(err) console.log(err) + db.addColumn('user', 'pfp_code TEXT', (err) => { + if(err) console.log(err) + for (const user of data) { + db.update('user', ['pfp_code=$name'], 'id=$id', [`seed=${user.name}`, user.id], (err)=>{if(err)console.log(err)}) + } + }) + }) + }, + }) + + migrations.set('db_table_versions', { + 0: ()=>{ + db.createTable('db_table_versions', [ + 'id INTEGER PRIMARY KEY AUTOINCREMENT', + 'table_id VARCHAR NOT NULL', + 'version INTEGER NOT NULL DEFAULT 1' + ]) + } + }) + + return migrations + } +} diff --git a/src/extman.ts b/src/extman.cts similarity index 54% rename from src/extman.ts rename to src/extman.cts index b55dafc..bb04062 100644 --- a/src/extman.ts +++ b/src/extman.cts @@ -1,17 +1,17 @@ -export function load(modules: any, database: Database, namespace: string): unknown { +module.exports.load = (modules: any, database: Database, namespace: string): unknown => { let context: InitContext = { modules, database, path: `${__dirname}/extensions/${namespace}/`, data_path: `${__dirname}/../data/${namespace}/`, + name: namespace, } - let ext = new (require(`./extensions/${namespace}/index`).default) + let ext = new (require(`./extensions/${namespace}/index`).default) as Extension let status = ext.init(context) - if (status.Okay == false) { - throw new Error(`Failed initializing: ${status.Error}`) - } + 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 index f2cac0c..db6e7e2 100644 --- a/src/handle.ts +++ b/src/handle.ts @@ -1,5 +1,6 @@ -import { load } from "./extman" -import Log from "./modules/log" +/** @ts-ignore 2305 */ +import { load } from "./extman.cjs" +import Log from "./modules/log.ts" let log = new Log(true) @@ -9,8 +10,8 @@ export default class implements Handle { ] root: RootExtension wg_config: any - extensions: Record - admin_extensions: Record + extensions = new Map() + admin_extensions = new Map() constructor(modules: any, database: any) { let config = modules.config @@ -21,32 +22,34 @@ export default class implements Handle { this.root = load(modules, database, 'root') as RootExtension - this.extensions = {} for (const path of this.extensions_list) { - console.log(path) try { - this.extensions[path] = load(modules, database, path) as Extension - } catch (err) { - log.err(`Unable to load extension '${path}':\n\t${err}`) + this.extensions.set(path, load(modules, database, path) as Extension) + } catch (err: any) { + log.err(`Unable to load extension '${path}':\n\t${err.message}\n${err.stack}`) } } - this.admin_extensions = {} - for (const ext of Object.keys(this.extensions)) { - if (this.extensions[ext].admin_only) { - this.admin_extensions[ext] = this.extensions[ext] - delete this.extensions[ext] + this.extensions.forEach((extension, name, m) => { + if (extension.admin_only) { + this.admin_extensions.set(name, extension) + this.extensions.delete(name) } - } + }) } - main: Handle['main'] = (ctx: Context) => { - var location = ctx.path.shift() || '' + main: Handle['main'] = (partial_ctx: PartialContext) => { + let location = partial_ctx.path.shift() ?? '' // set request context - ctx.context = {...ctx.args} - ctx.context.extensions = this.extensions - ctx.context.location = location + let ctx: Context = { + ...partial_ctx, + context: { + ...partial_ctx.args, + extensions: this.extensions, + location, + } + } // Authenticate using user&pass, else using ip this.root.authenticate(ctx.req.headers.authorization as BasicAuth|undefined, ctx.ip, this.wg_config.subnet, (user, err) => { @@ -55,14 +58,14 @@ export default class implements Handle { if (user && user.is_admin) ctx.context.extensions = {...ctx.context.extensions, ...this.admin_extensions} // Extension - if (location in ctx.context.extensions) { - let ext = ctx.context.extensions[location] + if (ctx.context.extensions.has(location)) { + let ext = ctx.context.extensions.get(location) as Extension // If login required if (!user && ext.requires_login(ctx.path)) { ctx.res.writeHead(307, {Location: "/login"}) return ctx.res.end() } - if (user && !user.is_admin && ext.requires_admin(ctx.path)) { + else if (user && !user.is_admin && ext.requires_admin(ctx.path)) { ctx.res.writeHead(307, {Location: "/"}) return ctx.res.end() } diff --git a/src/index.ts b/src/index.ts index 31ff569..df703f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,20 @@ import sqlite3 from 'sqlite3' +import { promises as fs } from 'fs' +import http, { IncomingMessage, RequestListener, ServerResponse } from 'http' +import http2, { Http2ServerRequest } from 'http2' // enable use of dotenv -require('dotenv').config() +import dotenv from 'dotenv' +dotenv.config() // set up global context -import {cookie, config, Log} from './modules' -import * as modules from './modules' +import {cookie, config, Log} from './modules.ts' +import * as modules from './modules.ts' -const db = new (sqlite3.verbose()).Database(`${__dirname}/../data/db.sqlite`) +const db = new (sqlite3.verbose()).Database(`${import.meta.dirname}/../data/db.sqlite`) // get request handler -import Handle from './handle' +import Handle from './handle.ts' let handle = new Handle(modules, db) // set up modules const log: Log = new Log(config.logging) @@ -19,7 +23,7 @@ const log: Log = new Log(config.logging) const requestListener = async function (req: Http2ServerRequest, res: Http2ServerResponse) { let ip: Context['ip'] let cookies: Context['cookies'] - let args: Context['args'] + let args: Context['args'] = new Map() let path: Context['path'] let data: Context['data'] @@ -52,17 +56,15 @@ const requestListener = async function (req: Http2ServerRequest, res: Http2Serve } { - let raw_path: string - let raw_args: string // separate url arguments from the url itself - [raw_path, raw_args] = req.url.split('?') + let [raw_path = req.url, raw_args = ''] = req.url.split('?', 2) // split arguments into key:value pairs - args = new Map() - if (raw_args) { + if (raw_args != '') { for (let arg of raw_args.split('&')) { - let [key, value] = arg.split('=') - args.set(key, value) + let [key, value] = arg.split('=', 2) + if (!(key === undefined || value === undefined)) + args.set(key, value) } } @@ -78,7 +80,7 @@ const requestListener = async function (req: Http2ServerRequest, res: Http2Serve buffer.push(data) }) req.on('end', function() { - let bytes = Buffer.concat(buffer) + let bytes = Buffer.concat(buffer as readonly Uint8Array[]) data = { bytes, raw: bytes.toString(), @@ -96,14 +98,14 @@ const requestListener = async function (req: Http2ServerRequest, res: Http2Serve }) } - let ctx: Context = { + let ctx: PartialContext = { req, res, path, args, cookies, ip, - data + data, } // log the request log.con(req, ctx) @@ -111,17 +113,31 @@ const requestListener = async function (req: Http2ServerRequest, res: Http2Serve 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 -const httpsRedirect = function (req: Http2ServerRequest, res: Http2ServerResponse) { +function httpsRedirect(req: IncomingMessage, res: ServerResponse) { res.writeHead(307, {"Location": `https://${req.headers.host}${req.url}`}) res.end() } -function startServer(http: boolean, https: boolean) { - if (https) { +function startServer(http_enabled: boolean, https_enabled: boolean) { + if (https_enabled) { let key = function (location: string, callback: (data?: any) => void) { - require('fs').promises.readFile(location, "utf8") + fs.readFile(location, "utf8") .then(callback) .catch((err: Error) => { log.err(`Failed fetching key at: '${location}'`) @@ -138,8 +154,7 @@ function startServer(http: boolean, https: boolean) { key(config.ca_cert_path, function(certificate_authority) { log.status("Encryption keys fetched") // Start server - require('http2') - .createSecureServer({ + http2.createSecureServer({ key: private_key, cert: certificate, ca: certificate_authority, @@ -154,10 +169,10 @@ function startServer(http: boolean, https: boolean) { }) }) } - if (http) { + if (http_enabled) { // Start server - require('http').createServer( - https ? httpsRedirect : requestListener + http.createServer( + https_enabled ? httpsRedirect : requestListenerCompat ).listen( config.http_port, config.host, diff --git a/src/modules.ts b/src/modules.ts index a6e97de..1558863 100644 --- a/src/modules.ts +++ b/src/modules.ts @@ -1,16 +1,16 @@ -import * as Nunjucks from 'nunjucks' +import Nunjucks from 'nunjucks' export const nj: Environment = Nunjucks.configure([ - __dirname+'/templates', - __dirname+'/extensions' + `${import.meta.dirname}/templates`, + `${import.meta.dirname}/extensions` ]) export {default as cookie} from 'cookie' -export {ExtensionBase} from './classes/extension' -export {Tables} from './classes/tables' -export {DB} from './classes/db' -export {default as Log} from './modules/log' -export * as content from './modules/content_type' -export {default as Fetch} from './modules/fetch' -export {default as config} from '../config/config' -export {default as wg_config} from '../config/wireguard' -export {default as texts} from '../config/texts' +export {ExtensionBase} from './classes/extension.ts' +export {Tables} from './classes/tables.ts' +export {DB} from './modules/db.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 index 47f8ef2..1299b52 100644 --- a/src/modules/content_type.ts +++ b/src/modules/content_type.ts @@ -1,6 +1,5 @@ // Source: https://stackoverflow.com/a/51398471/15181929 - -export default (class { +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"} @@ -22,4 +21,9 @@ export default (class { // Force singleton private constructor(private readonly key: string, public readonly value: any) { } + + init: Module['init'] = (_context) => { + return [true] + } + }) as ContentType diff --git a/src/classes/db.ts b/src/modules/db.ts similarity index 73% rename from src/classes/db.ts rename to src/modules/db.ts index 4885c65..a1dccb1 100644 --- a/src/classes/db.ts +++ b/src/modules/db.ts @@ -1,17 +1,20 @@ -export class DB { +import { Database } from "sqlite3" + +export class DB implements Module { special_tables = [ 'user', 'db_table_versions' ] - db: any + db: Database prefix: string - constructor(db: any, prefix: string) { - this.db = db - this.prefix = '_'+prefix+'_' + init: Module['init'] = (context: InitContext) => { + this.db = context.database + this.prefix = '_'+context.name+'_' + return [true] } - __formatTableName = (name: string) => { + formatTableName = (name: string) => { if ( this.special_tables.includes(name) || name.startsWith('_') @@ -22,7 +25,7 @@ export class DB { // Table modifications createTable = (table: string, columns: string[]) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) let columns_str = columns.join(', ') this.db.run(`CREATE TABLE IF NOT EXISTS ${table} (${columns_str})`, [], (err?: Error) => { if (err) throw new Error(`Failed creating new table ${table}. ${err}`) @@ -30,7 +33,7 @@ export class DB { } dropTable = (table: string) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) this.db.run(`DROP TABLE ${table}`, [], (err?: Error) => { if (err) throw new Error(`Failed dropping table ${table}. ${err}`) }) @@ -38,23 +41,23 @@ export class DB { // Column modifications addColumn = (table: string, column: string, callback: (err?: Error) => void) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column}`, [], callback) } dropColumn = (table: string, column: string, callback: (err?: Error) => void) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) this.db.run(`ALTER TABLE ${table} DROP COLUMN ${column}`, [], callback) } renameColumn = (table: string, old_column: string, new_column: string, callback: (err?: Error) => void) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) this.db.run(`ALTER TABLE ${table} RENAME COLUMN ${old_column} to ${new_column}`, [], callback) } // Standard functions - select = (table: string, columns: string[], where: string, order_by: string, params: any, callback: (err: null|Error, data: null|any) => void) => { - table = this.__formatTableName(table) + select = (table: string, columns: string[], where: string|null, order_by: string|null, params: any, callback: (err: null|Error, data: null|any) => void) => { + table = this.formatTableName(table) let columns_str = columns.join(', ') // Construct query let query = `SELECT ${columns_str} FROM ${table}` @@ -65,7 +68,7 @@ export class DB { } insert = (table: string, columns: string[], values: any[], callback: (err?: Error) => void) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) let columns_str = columns.join(', ') let values_str = `$${columns.join(', $')}` // Run query @@ -73,14 +76,14 @@ export class DB { } update = (table: string, columns: string[], where: string, values: any, callback: (err?: Error) => void) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) let columns_str = columns.join(', ') // Run query this.db.run(`UPDATE ${table} SET ${columns_str} WHERE ${where}`, values, callback) } delete = (table: string, where: string, values: any, callback: (err?: Error) => void) => { - table = this.__formatTableName(table) + table = this.formatTableName(table) // Run query this.db.run(`DELETE FROM ${table} WHERE ${where}`, values, callback) } diff --git a/src/modules/fetch.ts b/src/modules/fetch.ts index 898c2bc..68b5ccc 100644 --- a/src/modules/fetch.ts +++ b/src/modules/fetch.ts @@ -1,15 +1,18 @@ import { readFile } from "fs/promises" import * as path from "path" -export default class implements Fetch { +export default class implements Module, Fetch { /** File extensions of binary filetypes */ private readonly BIN_EXTS: Set = new Set(['png','jpg','mp3']) /** Caches processed files */ private cache: Map = new Map() private root: string - constructor (raw_path: string) { - this.root = path.join(raw_path, "/static/") + + + init: Module['init'] = (context) => { + this.root = path.join(context.path, "/static/") + return [true] } file: Fetch['file'] = (file_path, callback) => { diff --git a/src/modules/log.ts b/src/modules/log.ts index b5467dc..4dce31e 100644 --- a/src/modules/log.ts +++ b/src/modules/log.ts @@ -33,7 +33,7 @@ export default class implements Log { return url.split('?')[0] } - con(req: Http2ServerRequest, ctx: Context) { + con(req: Http2ServerRequest, ctx: Context | PartialContext) { if (this.we_logging) { let ip = this.mask_ip(ctx.ip || '') let url = this.mask_url(req.url) diff --git a/tsconfig.json b/tsconfig.json index dc6378f..da2840e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,12 +2,18 @@ "compilerOptions": { "outDir": "./build", "allowJs": true, - "target": "es2022", - "moduleResolution": "Node16", - "module": "Node16", + "target": "ESNext", + "moduleResolution": "nodenext", + "module": "NodeNext", + "allowImportingTsExtensions": true, + "noEmit": true, + "alwaysStrict": true, + "strict": true, "noImplicitThis": true, "noImplicitAny": true, "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, "strictNullChecks": true, "typeRoots": ["./node_modules/@types", "./types/**/*"], }, diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts index 05dd089..c21c32f 100644 --- a/types/classes/extension.d.ts +++ b/types/classes/extension.d.ts @@ -6,7 +6,7 @@ declare interface Extension { name: string title: string - init(context: InitContext): ResultStatus + init(context: InitContext): void | Promise requires_login(path: string[]): boolean @@ -36,6 +36,10 @@ declare interface RootExtension extends Extension { addUser(name: User['name'], password: User['password'], callback: (err?: Error) => void): void } -declare interface DependencyMap extends Map { - massGet(...items: string[]): any[] +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 index 27e6eec..d2eed9e 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -10,7 +10,7 @@ declare type HttpHeader = { declare type BasicAuth = `Basic ${string}` -declare type FileData = Buffer | string +declare type FileData = string | null declare type Context = { req: Http2ServerRequest @@ -21,7 +21,23 @@ declare type Context = { path: string[] args: Map data?: {bytes: Buffer, raw: string, form: any} - context?: 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 = { @@ -38,6 +54,13 @@ declare type InitContext = { database: Database, path: string, data_path: string, + name: string, } 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 index eff6233..7de00ae 100644 --- a/types/handle.d.ts +++ b/types/handle.d.ts @@ -1,3 +1,3 @@ declare interface Handle { - main(ctx: Context): void, + main(ctx: PartialContext): void, } diff --git a/types/classes/db.d.ts b/types/modules/db.d.ts similarity index 80% rename from types/classes/db.d.ts rename to types/modules/db.d.ts index 86dd4e2..425836d 100644 --- a/types/classes/db.d.ts +++ b/types/modules/db.d.ts @@ -3,7 +3,7 @@ declare interface DB { db: any prefix: string - __formatTableName(name: string): string + formatTableName(name: string): string createTable(table: string, columns: string[]): void @@ -15,7 +15,7 @@ declare interface DB { renameColumn(table: string, old_column: string, new_column: string, callback: (err: Error|null) => void): void - select(table: string, columns: string[], where: string, order_by: string, params: {}, callback: (err: Error|null, data: any) => void): void + select(table: string, columns: string[], where: string|null, order_by: string|null, params: {}, callback: (err: Error|null, data: any) => void): void insert(table: string, columns: string[], values: [], callback: (err: Error|null) => void): void From 043eafa30abb1a463c918ce3529e8bad19a363e2 Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:58:39 +0000 Subject: [PATCH 10/14] even more shit.... (should be cleaned up now tho) --- config/config.example.ts | 6 +- package-lock.json | 850 +++++++++++----------------- package.json | 13 +- src/classes/extension.ts | 22 +- src/classes/tables.ts | 139 ++--- src/extensions/chat/index.js | 2 +- src/extensions/profile/index.js | 7 +- src/extensions/profile/tables.js | 27 - src/extensions/profile/tables.ts | 27 + src/extensions/profile/wireguard.js | 14 +- src/extensions/root/index.ts | 108 ++-- src/extensions/root/tables.ts | 46 +- src/extman.cts | 17 - src/extman.ts | 19 + src/handle.ts | 14 +- src/index.ts | 118 ++-- src/modules.ts | 8 +- src/modules/db.ts | 90 --- src/modules/fetch.ts | 9 +- src/modules/knex.ts | 45 ++ tsconfig.json | 6 +- types/classes/extension.d.ts | 4 +- types/global.d.ts | 6 +- types/handle.d.ts | 1 + types/modules/db.d.ts | 25 - 25 files changed, 704 insertions(+), 919 deletions(-) delete mode 100644 src/extensions/profile/tables.js create mode 100644 src/extensions/profile/tables.ts delete mode 100644 src/extman.cts create mode 100644 src/extman.ts delete mode 100644 src/modules/db.ts create mode 100644 src/modules/knex.ts delete mode 100644 types/modules/db.d.ts diff --git a/config/config.example.ts b/config/config.example.ts index 86d3066..e905891 100644 --- a/config/config.example.ts +++ b/config/config.example.ts @@ -24,9 +24,9 @@ export default { /** 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: `${__dirname}/../certs/privkey.pem`, + private_key_path: `${import.meta.dirname}/../certs/privkey.pem`, /** The path to your HTTPS certificate-set's certificate. */ - server_cert_path: `${__dirname}/../certs/cert.pem`, + server_cert_path: `${import.meta.dirname}/../certs/cert.pem`, /** The path to your HTTPS certificate-set's CA-chain. */ - ca_cert_path: `${__dirname}/../certs/ca.pem`, + ca_cert_path: `${import.meta.dirname}/../certs/ca.pem`, } diff --git a/package-lock.json b/package-lock.json index 93dfaa0..5097213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,292 +9,21 @@ "version": "2.4.1", "license": "MPL-2.0", "dependencies": { - "cookie": "^0.6.0", + "cookie": "1.0.1", "dotenv": "^16.4.5", + "knex": "^3.1.0", "nunjucks": "^3.2.4", "sqlite3": "^5.1.6", - "typescript": "^5.4.5" + "typescript": "5.6.3" }, "devDependencies": { "@types/cookie": "^0.6.0", - "@types/node": "^20.12.12", + "@types/knex": "^0.15.2", + "@types/node": "22.7.5", "@types/nunjucks": "^3.2.6", - "nodemon": "^3.1.0", "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", @@ -312,125 +41,6 @@ "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": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -474,6 +84,13 @@ "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", @@ -481,14 +98,25 @@ "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": "20.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", - "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/nunjucks": { @@ -565,8 +193,9 @@ "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", + "optional": true, + "peer": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -607,8 +236,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", @@ -634,8 +263,9 @@ "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", + "optional": true, + "peer": true, "engines": { "node": ">=8" }, @@ -667,8 +297,8 @@ "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" @@ -678,8 +308,9 @@ "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", + "optional": true, + "peer": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -745,8 +376,9 @@ "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", + "optional": true, + "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -795,6 +427,12 @@ "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", @@ -808,8 +446,8 @@ "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", @@ -819,20 +457,20 @@ "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.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", + "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" } }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ms": "2.1.2" }, @@ -980,6 +618,24 @@ "@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", @@ -999,8 +655,9 @@ "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", + "optional": true, + "peer": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1033,19 +690,13 @@ "license": "ISC", "optional": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "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", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gauge": { @@ -1069,6 +720,15 @@ "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.9.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.9.0.tgz", @@ -1082,6 +742,12 @@ "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", @@ -1114,8 +780,9 @@ "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", + "optional": true, + "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1130,16 +797,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", @@ -1147,6 +804,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", @@ -1226,13 +895,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", @@ -1284,6 +946,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", @@ -1302,8 +973,9 @@ "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, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -1311,12 +983,28 @@ "node": ">=8" } }, + "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": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "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", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -1335,8 +1023,9 @@ "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", + "optional": true, + "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -1355,8 +1044,9 @@ "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", + "optional": true, + "peer": true, "engines": { "node": ">=0.12.0" } @@ -1375,6 +1065,89 @@ "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/knex/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "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", @@ -1432,8 +1205,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" }, @@ -1567,7 +1340,6 @@ "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": { @@ -1629,35 +1401,6 @@ "node": ">= 10.12.0" } }, - "node_modules/nodemon": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", - "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", - "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", @@ -1678,8 +1421,9 @@ "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", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -1761,12 +1505,25 @@ "node": ">=0.10.0" } }, + "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/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8.6" }, @@ -1821,13 +1578,6 @@ "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", @@ -1871,8 +1621,9 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -1880,6 +1631,47 @@ "node": ">=8.10.0" } }, + "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": { + "resolve": "^1.20.0" + }, + "engines": { + "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", @@ -2015,19 +1807,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", @@ -2159,17 +1938,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": { @@ -2232,12 +2010,31 @@ "node": ">=8" } }, + "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", + "engines": { + "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, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "is-number": "^7.0.0" }, @@ -2245,16 +2042,6 @@ "node": ">=8.0" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", @@ -2288,9 +2075,9 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -2300,17 +2087,10 @@ "node": ">=14.17" } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index c4c5f39..a71a995 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,18 @@ "main": "src/index.ts", "type": "module", "scripts": { - "start": "npx tsc ./src/index.ts && node build/index.js", - "dev": "sudo npx nodemon src/index.ts -e . --ignore ./data/ --exec 'tsx'" + "start": "npx tsc --outDir ./build ./src/index.ts && node build/index.js", + "dev": "tsx watch ./src/index.ts" }, "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", "dependencies": { - "cookie": "^0.6.0", + "cookie": "1.0.1", "dotenv": "^16.4.5", + "knex": "^3.1.0", "nunjucks": "^3.2.4", "sqlite3": "^5.1.6", - "typescript": "^5.4.5" + "typescript": "5.6.3" }, "repository": { "type": "git", @@ -27,9 +28,9 @@ "homepage": "https://keuk.net/", "devDependencies": { "@types/cookie": "^0.6.0", - "@types/node": "^20.12.12", + "@types/knex": "^0.15.2", + "@types/node": "22.7.5", "@types/nunjucks": "^3.2.6", - "nodemon": "^3.1.0", "tsx": "^4.19.2" } } diff --git a/src/classes/extension.ts b/src/classes/extension.ts index 1065abf..5ce1e47 100644 --- a/src/classes/extension.ts +++ b/src/classes/extension.ts @@ -9,18 +9,18 @@ export abstract class ExtensionBase implements Extension { name: Extension['name'] = "default_name" title: Extension['title'] = "Default Title" - static init(inst: ExtensionBase, context: InitContext): void | Promise { + static async init(inst: ExtensionBase, context: InitContext): Promise { let global: any = context.modules let path = context.path - let database = context.database + let knex = context.knex inst.initialized_deps = new DependencyMapImpl(global, context) // Init db if (inst.tables) { // init tables - let tables = new (require(`${path}tables`).default)(database, inst.initialized_deps.get('DB'), inst.name) as Tables - let result = tables.migrate() + 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 } } @@ -40,7 +40,7 @@ export abstract class ExtensionBase implements Extension { handle_req: Extension['handle_req'] = (ctx: Context) => { ctx.context.extension = this as unknown as Extension - return this.handle(ctx, this.initialized_deps) + return this.handle(ctx) } abstract handle: Extension['handle'] @@ -86,7 +86,7 @@ export abstract class ExtensionBase implements Extension { return_html: Extension['return_html'] = (ctx, item, err, err_code=500, success_code=200, headers=undefined) => { const {req, res} = ctx - let [nj, content] = this.initialized_deps.massGet('nj', 'content') + let [nj, content] = this.get_dependencies('nj', 'content') if (err) { res.writeHead(err_code) @@ -111,9 +111,9 @@ export abstract class ExtensionBase implements Extension { return_file: Extension['return_file'] = (ctx, file) => { const {res} = ctx - let [fetch, content]: [Fetch, ContentType] = this.initialized_deps.massGet('Fetch', 'content') + let [fetch, content]: [Fetch, ContentType] = this.get_dependencies('Fetch', 'content') - fetch.file(file, (data?: FileData, filetype?: string, err?: Error) => { + fetch.file(file, (data: FileData, filetype: string, err) => { if (err) { res.writeHead(404) res.end() @@ -146,7 +146,7 @@ export abstract class ExtensionBase implements Extension { } set_cookie: Extension['set_cookie'] = (key, value, secure=false) => { - let cookie = this.initialized_deps.get('cookie') + let [cookie] = this.get_dependencies('cookie') if (secure) return cookie.serialize( @@ -164,7 +164,7 @@ export abstract class ExtensionBase implements Extension { } del_cookie: Extension['del_cookie'] = (key) => { - let cookie = this.initialized_deps.get('cookie') + let [cookie] = this.get_dependencies('cookie') return cookie.serialize( key, @@ -173,6 +173,8 @@ export abstract class ExtensionBase implements Extension { } ) } + + get_dependencies: Extension['get_dependencies'] = (...args) => this.initialized_deps.massGet(...args) } class DependencyMapImpl implements DependencyMap { diff --git a/src/classes/tables.ts b/src/classes/tables.ts index 76e1d57..4c01992 100644 --- a/src/classes/tables.ts +++ b/src/classes/tables.ts @@ -1,81 +1,94 @@ +import { Knex as rawKnex } from "knex" +import { Knex } from "../modules.ts" + export abstract class Tables { - static readonly SPECIAL = ['user', 'db_table_versions'] - private raw_db: Database - db: DB + raw_knex: rawKnex + knex: Knex prefix: string - - constructor(raw_db: Database, db: DB, prefix: string) { - this.raw_db = raw_db - this.db = db + + constructor(raw_knex: rawKnex, knex: Knex, prefix: string) { + this.raw_knex = raw_knex + this.knex = knex this.prefix = prefix } - + migrate(): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let versions = this.versions(new Map()) - let migrations = this.migrations(this.db, new Map()) + let migrations = this.migrations(this.knex, new Map()) - this.raw_db.serialize(()=> { - this.db.select('db_table_versions', ['table_id','version'], null, null, [], (err, data) => { - if (err instanceof Error) { // Should only occur on this one missing and root called first - let migration = Tables.getMigration(migrations, 'db_table_versions', 0) - if (migration instanceof Error) return reject(migration) - else migration = migration as Migration - migration() + // 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) } - else { - 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.db.formatTableName(table) - if (!current_versions.has(table_id)) { - console.log(`Adding table ${table_id}`) + + for (var [table, latest] of versions) { + let table_id: TableId | string = `_${this.prefix}_${table}` - 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}'`) - migration[0]() + if (!current_versions.has(table_id)) { + console.log(`Adding table ${table_id}`) - current_versions.set(table_id, 0) - this.raw_db.run("INSERT INTO db_table_versions (table_id, version) VALUES ($table, $version)", [table_id, current_versions.get(table_id)], (err) => { - if (err) return reject(`Failed adding table version identifier. ${err}`) - console.log(`Added 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), }) - } - 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 + .then( + () => { + console.log(`Added table ${table_id}`) + }, (err) => { + if (err) return reject(`Failed adding table version identifier. ${err}`) + } + ) + } - 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) - migration() - } - catch (err) { - console.error(err) - break - } - - current_versions.set(table_id, new_version) + 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 - this.raw_db.run("UPDATE db_table_versions SET version=$version WHERE table_id=$table", [new_version, table_id], (err) => { - if (err) - return reject(`Failed updating table version identifier. ${err}`) - console.log(`Upgraded table ${table_id} to ${new_version}`) - }) + 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() }) } @@ -97,7 +110,7 @@ export abstract class Tables { versions(versions: VersionMap): VersionMap { return versions } - migrations(db: DB, migrations: MigrationMap): MigrationMap { + migrations(knex: Knex, migrations: MigrationMap): MigrationMap { return migrations } } @@ -107,4 +120,4 @@ export type MigrationRecord = Record export type MigrationMap = Map export type VersionMap = Map -type TableId = `_${string}_${string}` +type TableId = `${string}.${string}` diff --git a/src/extensions/chat/index.js b/src/extensions/chat/index.js index d0da069..c3ceb5b 100644 --- a/src/extensions/chat/index.js +++ b/src/extensions/chat/index.js @@ -20,7 +20,7 @@ export default class extends ExtensionBase { } handle(ctx, deps) { - let [] = this.initialized_deps.massGet() + let [] = this.deps.massGet() var location = ctx.path.shift() if (!location) { diff --git a/src/extensions/profile/index.js b/src/extensions/profile/index.js index 1729705..353a3ee 100644 --- a/src/extensions/profile/index.js +++ b/src/extensions/profile/index.js @@ -6,15 +6,16 @@ export default class extends ExtensionBase { title = 'Network' tables = true dependencies = ['content','nj','fetch'] - wg = require('./wireguard') + wg = null wg_config = null - init(context) { + async init(context) { let config = context.modules.config let data_path = context.data_path this.wg_config = context.modules.wg_config + this.wg = await import('./wireguard') this.wg.init(data_path, this.wg_config, config.tmp_dir) return ExtensionBase.init(this, context) @@ -48,7 +49,7 @@ export default class extends ExtensionBase { // Delete db entry db.delete('device', 'uuid=$uuid', [ctx.args.get('uuid')], (err) => { // Delete wireguard profile - this.wg.delete(ctx.args.get('uuid'), () => { + this.wg.remove(ctx.args.get('uuid'), () => { return this.return(ctx, err, location='/profile') }) }) diff --git a/src/extensions/profile/tables.js b/src/extensions/profile/tables.js deleted file mode 100644 index fb37daf..0000000 --- a/src/extensions/profile/tables.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Tables } from '../../classes/tables' - -export default class extends Tables { - versions(versions) { - versions.set('device', 0) - - return versions - } - - migrations(db, migrations) { - migrations.set('device', { - 0: ()=>{ - 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)' - ]) - } - }) - - return migrations - } -} diff --git a/src/extensions/profile/tables.ts b/src/extensions/profile/tables.ts new file mode 100644 index 0000000..86ccb44 --- /dev/null +++ b/src/extensions/profile/tables.ts @@ -0,0 +1,27 @@ +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.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/src/extensions/profile/wireguard.js b/src/extensions/profile/wireguard.js index 220375d..8b1ae51 100644 --- a/src/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 @@ -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/src/extensions/root/index.ts b/src/extensions/root/index.ts index f0a99c9..e9c05c2 100644 --- a/src/extensions/root/index.ts +++ b/src/extensions/root/index.ts @@ -1,5 +1,5 @@ import crypto from 'crypto' -import { ExtensionBase } from "../../modules" +import { ExtensionBase, Knex } from "../../modules.ts" import { readdirSync } from "fs" @@ -15,15 +15,12 @@ export default class extends ExtensionBase implements RootExtension { ip_scope: string salt: string - db: Database - 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.db = context.database this.favicons_path = data_path+'favicons/' try { @@ -42,9 +39,9 @@ export default class extends ExtensionBase implements RootExtension { return false } - override handle = (ctx: Context, deps: DependencyMap) => { + override handle = (ctx: Context) => { var location = ctx.path.shift() - let [db]: [DB] = deps.massGet('DB') + let [knex]: [Knex] = this.get_dependencies('Knex') switch (location) { case '': @@ -126,13 +123,19 @@ export default class extends ExtensionBase implements RootExtension { try { args = decodeURIComponent(args) } catch {} - db.update('user', ['pfp_code=$args'], 'id=$id', [args, ctx.context.user.id], (err: Error|null) => { - if (err) - ctx.res.writeHead(500) - else - ctx.res.writeHead(307, {"Location": "/"}) - ctx.res.end() - }) + + knex.query('user') + .update('pfp_code', args) + .where('id', ctx.context.user.id) + .then( + (v) => { + ctx.res.writeHead(307, {"Location": "/"}) + ctx.res.end() + }, (err) => { + ctx.res.writeHead(500) + ctx.res.end() + } + ) return } else @@ -156,7 +159,9 @@ export default class extends ExtensionBase implements RootExtension { } } - authenticate(auth: BasicAuth | undefined, ip: string, subnet: string, callback: (user: undefined|User, err?: Error) => void): void { + authenticate: RootExtension['authenticate'] = (auth, ip, subnet, callback) => { + let [knex]: [Knex] = this.get_dependencies('Knex') + if (auth) { // Try to get name and password this.decrypt_auth(auth, (name, password, err) => { @@ -164,24 +169,45 @@ export default class extends ExtensionBase implements RootExtension { return callback(undefined, err) } // Auth using name and password - this.db.get("SELECT * FROM user WHERE name=$name", name, (err?: Error|null, user?: User) => { - if (err === null) err = undefined - - if (user) { - if (password == user.password) { - return callback(user, err) - } - } - return callback(undefined, new Error("Wrong name or password")) - }) + if (name !== undefined) { + knex.query('user') + .select('*') + .where('name', name) + .then( + (result: User[]) => { + const user = result[0] + if (user !== undefined && password == user.password) { + return callback(user, err) + } + else { + return callback(undefined, new Error('Wrong name or password')) + } + }, + (err) => { + console.log(err) + return callback(undefined, new Error("Failed to check password")) + } + ) + } }) } else if (ip.startsWith(subnet)) { // Try using IP-address if no name and password - this.db.get("SELECT u.* FROM user u JOIN _profile_device p ON p.user_id = u.id WHERE p.ip=$ip", ip, function (err?: Error|null, user?: User) { - if (err === null) err = undefined - return callback(user, err) - }) + return knex.query({u: 'user', p: '_profile_device'}) + .select('u.*') + .join('_profile_device', 'u.id', '=', 'p.user_id') + .where('p.ip', ip) + .first() + .then( + (user) => { + console.log(user, "def") + callback(user) + }, + (err) => { + console.log(err, "gef") + callback(undefined, new Error("Failed to get user by IP.")) + } + ) } else return callback(undefined, undefined) @@ -208,26 +234,32 @@ export default class extends ExtensionBase implements RootExtension { } 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 - this.db.run("INSERT INTO user(name,password,pfp_code) VALUES($name,$password,$pfp_code)", [name, password, 'seed='+name], (err?: Error|null) => { - if (err === null) err = undefined - return callback(err) - }) + 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 - this.db.get("SELECT EXISTS(SELECT 1 FROM user WHERE name=$name)", name, function (err?: Error|null, result?: Object) { - if (err === null) err = undefined - let exists = false - if (result) exists = !!Object.values(result)[0] - return callback(exists, err) - }) + knex.query('user') + .select('id') + .where('name', name) + .then((value) => { + callback(!!value.length) + }, (err) => { + callback(false, err) + }) } } diff --git a/src/extensions/root/tables.ts b/src/extensions/root/tables.ts index 616502c..3430619 100644 --- a/src/extensions/root/tables.ts +++ b/src/extensions/root/tables.ts @@ -1,47 +1,33 @@ -import { MigrationMap, Tables, VersionMap } from '../../classes/tables' +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) - versions.set('db_table_versions', 0) return versions } - override migrations(db: DB, migrations: MigrationMap) { + override migrations(knex: Knex, migrations: MigrationMap) { migrations.set('user', { - 0: ()=>{ - 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))', - ]) + 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: ()=>{ - db.select('user', ['id','name'], null, null, [], (err, data) => { - if(err) console.log(err) - db.addColumn('user', 'pfp_code TEXT', (err) => { - if(err) console.log(err) - for (const user of data) { - db.update('user', ['pfp_code=$name'], 'id=$id', [`seed=${user.name}`, user.id], (err)=>{if(err)console.log(err)}) - } + 1: async ()=>{ + await knex.schema() + .alterTable('user', (table) => { + table.string('pfp_code') }) - }) }, }) - migrations.set('db_table_versions', { - 0: ()=>{ - db.createTable('db_table_versions', [ - 'id INTEGER PRIMARY KEY AUTOINCREMENT', - 'table_id VARCHAR NOT NULL', - 'version INTEGER NOT NULL DEFAULT 1' - ]) - } - }) - return migrations } } diff --git a/src/extman.cts b/src/extman.cts deleted file mode 100644 index bb04062..0000000 --- a/src/extman.cts +++ /dev/null @@ -1,17 +0,0 @@ -module.exports.load = (modules: any, database: Database, namespace: string): unknown => { - let context: InitContext = { - modules, - database, - path: `${__dirname}/extensions/${namespace}/`, - data_path: `${__dirname}/../data/${namespace}/`, - name: namespace, - } - - let ext = new (require(`./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/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 index db6e7e2..bcf74a6 100644 --- a/src/handle.ts +++ b/src/handle.ts @@ -1,5 +1,5 @@ -/** @ts-ignore 2305 */ -import { load } from "./extman.cjs" +import { Knex } from "knex" +import { load } from "./extman.ts" import Log from "./modules/log.ts" let log = new Log(true) @@ -13,18 +13,20 @@ export default class implements Handle { extensions = new Map() admin_extensions = new Map() - constructor(modules: any, database: any) { + 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) - - this.root = load(modules, database, 'root') as RootExtension + } + + 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, load(modules, database, path) as Extension) + 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}`) } diff --git a/src/index.ts b/src/index.ts index df703f1..a82ae9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,22 +1,50 @@ -import sqlite3 from 'sqlite3' -import { promises as fs } from 'fs' -import http, { IncomingMessage, RequestListener, ServerResponse } from 'http' -import http2, { Http2ServerRequest } from 'http2' - -// enable use of dotenv +import http2 from 'http2' +import http1, { IncomingMessage, ServerResponse } from 'http' +import Knex from 'knex' import dotenv from 'dotenv' -dotenv.config() - -// set up global context -import {cookie, config, Log} from './modules.ts' +import {cookie, config, Log } from './modules.ts' import * as modules from './modules.ts' +import Handle from './handle.ts' -const db = new (sqlite3.verbose()).Database(`${import.meta.dirname}/../data/db.sqlite`) +// 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 -import Handle from './handle.ts' -let handle = new Handle(modules, db) -// set up modules +let handle = new Handle(modules) +handle.init(modules, knex) + +// set up logging const log: Log = new Log(config.logging) // handle all requests for both HTTPS and HTTP/2 or HTTP/nginx @@ -134,45 +162,45 @@ function httpsRedirect(req: IncomingMessage, res: ServerResponse) { } -function startServer(http_enabled: boolean, https_enabled: boolean) { +async function startServer(http_enabled: boolean, https_enabled: boolean) { if (https_enabled) { - let key = function (location: string, callback: (data?: any) => void) { - fs.readFile(location, "utf8") - .then(callback) - .catch((err: Error) => { - log.err(`Failed fetching key at: '${location}'`) - callback(undefined) + 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") - // Private key - key(config.private_key_path, function(private_key) { - // Certificate - key(config.server_cert_path, function(certificate) { - // Certificate chain - key(config.ca_cert_path, function(certificate_authority) { - 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) - ) - }) - }) - }) + + 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 - http.createServer( - https_enabled ? httpsRedirect : requestListenerCompat + http1.createServer( + // @ts-expect-error + https_enabled ? httpsRedirect : requestListener ).listen( config.http_port, config.host, diff --git a/src/modules.ts b/src/modules.ts index 1558863..c417952 100644 --- a/src/modules.ts +++ b/src/modules.ts @@ -1,13 +1,13 @@ -import Nunjucks from 'nunjucks' -export const nj: Environment = Nunjucks.configure([ +import nunjucks from 'nunjucks' +export const nj: Environment = nunjucks.configure([ `${import.meta.dirname}/templates`, `${import.meta.dirname}/extensions` ]) -export {default as cookie} from 'cookie' +export * as cookie from 'cookie' export {ExtensionBase} from './classes/extension.ts' export {Tables} from './classes/tables.ts' -export {DB} from './modules/db.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' diff --git a/src/modules/db.ts b/src/modules/db.ts deleted file mode 100644 index a1dccb1..0000000 --- a/src/modules/db.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Database } from "sqlite3" - -export class DB implements Module { - special_tables = [ - 'user', - 'db_table_versions' - ] - db: Database - prefix: string - - init: Module['init'] = (context: InitContext) => { - this.db = context.database - this.prefix = '_'+context.name+'_' - return [true] - } - - formatTableName = (name: string) => { - if ( - this.special_tables.includes(name) - || name.startsWith('_') - ) - return name - return this.prefix + name - } - - // Table modifications - createTable = (table: string, columns: string[]) => { - table = this.formatTableName(table) - let columns_str = columns.join(', ') - this.db.run(`CREATE TABLE IF NOT EXISTS ${table} (${columns_str})`, [], (err?: Error) => { - if (err) throw new Error(`Failed creating new table ${table}. ${err}`) - }) - } - - dropTable = (table: string) => { - table = this.formatTableName(table) - this.db.run(`DROP TABLE ${table}`, [], (err?: Error) => { - if (err) throw new Error(`Failed dropping table ${table}. ${err}`) - }) - } - - // Column modifications - addColumn = (table: string, column: string, callback: (err?: Error) => void) => { - table = this.formatTableName(table) - this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column}`, [], callback) - } - - dropColumn = (table: string, column: string, callback: (err?: Error) => void) => { - table = this.formatTableName(table) - this.db.run(`ALTER TABLE ${table} DROP COLUMN ${column}`, [], callback) - } - - renameColumn = (table: string, old_column: string, new_column: string, callback: (err?: Error) => void) => { - table = this.formatTableName(table) - this.db.run(`ALTER TABLE ${table} RENAME COLUMN ${old_column} to ${new_column}`, [], callback) - } - - // Standard functions - select = (table: string, columns: string[], where: string|null, order_by: string|null, params: any, callback: (err: null|Error, data: null|any) => void) => { - table = this.formatTableName(table) - let columns_str = columns.join(', ') - // Construct query - let query = `SELECT ${columns_str} FROM ${table}` - if (where) query += ` WHERE ${where}` - if (order_by) query += ` ORDER BY ${order_by}` - // Get result - this.db.all(query, params, callback) - } - - insert = (table: string, columns: string[], values: any[], callback: (err?: Error) => void) => { - 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, callback) - } - - update = (table: string, columns: string[], where: string, values: any, callback: (err?: Error) => void) => { - table = this.formatTableName(table) - let columns_str = columns.join(', ') - // Run query - this.db.run(`UPDATE ${table} SET ${columns_str} WHERE ${where}`, values, callback) - } - - delete = (table: string, where: string, values: any, callback: (err?: Error) => void) => { - table = this.formatTableName(table) - // Run query - this.db.run(`DELETE FROM ${table} WHERE ${where}`, values, callback) - } -} diff --git a/src/modules/fetch.ts b/src/modules/fetch.ts index 68b5ccc..37ba64d 100644 --- a/src/modules/fetch.ts +++ b/src/modules/fetch.ts @@ -30,9 +30,12 @@ export default class implements Module, Fetch { // read the file readFile(file_path) // cache and return the data - .then((data: FileData) => { - if (!this.BIN_EXTS.has(filetype)) - data = data.toString('utf8') + .then((raw_data: Buffer) => { + let data: FileData = null + if (this.BIN_EXTS.has(filetype)) + data = raw_data as unknown as string + else + data = raw_data.toString('utf8') this.cache.set(file_path, data) return callback(data, filetype) diff --git a/src/modules/knex.ts b/src/modules/knex.ts new file mode 100644 index 0000000..c8a89ee --- /dev/null +++ b/src/modules/knex.ts @@ -0,0 +1,45 @@ +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) => { + // switch (typeof table) { + // case 'string': { + // table = this.parseTableName(table) + // break + // } + // case 'object': { + // Object.keys(table).forEach((key) => { + // // @ts-expect-error + // table[key] = this.parseTableName(table[key]) + // }) + // break + // } + // } + return this.knex(table as never).queryContext({prefix: this.prefix}) + } + + schema = () => { + return this.knex.schema.queryContext({prefix: this.prefix}) + } + + private parseTableName(name: string): string { + if (name.startsWith('_')) + return name + else { + return `_${this.prefix}_${name}` + } + } +} diff --git a/tsconfig.json b/tsconfig.json index da2840e..b047290 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,8 @@ "module": "NodeNext", "allowImportingTsExtensions": true, "noEmit": true, - "alwaysStrict": true, - "strict": true, + // "alwaysStrict": true, + // "strict": true, "noImplicitThis": true, "noImplicitAny": true, "noImplicitOverride": true, @@ -16,6 +16,8 @@ "noUncheckedIndexedAccess": true, "strictNullChecks": true, "typeRoots": ["./node_modules/@types", "./types/**/*"], + "allowImportingTsExtensions": true, + "noEmit": true, }, "ts-node": { "files": true, diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts index c21c32f..3914fb6 100644 --- a/types/classes/extension.d.ts +++ b/types/classes/extension.d.ts @@ -14,7 +14,7 @@ declare interface Extension { handle_req(ctx: Context): void | Error - handle(ctx: Context, mods: DependencyMap): void | Error + handle(ctx: Context): void | Error return(ctx: Context, err?: Error, location?: string, err_code?: number): void @@ -29,6 +29,8 @@ declare interface Extension { set_cookie(key: string, value: any, secure?: boolean): string del_cookie(key: string): string + + get_dependencies: DependencyMap['massGet'] } declare interface RootExtension extends Extension { diff --git a/types/global.d.ts b/types/global.d.ts index d2eed9e..ade90e8 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -17,7 +17,7 @@ declare type Context = { res: Http2ServerResponse ip: string - cookies: Record + cookies: Record path: string[] args: Map data?: {bytes: Buffer, raw: string, form: any} @@ -34,7 +34,7 @@ declare type PartialContext = { res: Http2ServerResponse ip: string - cookies: Record + cookies: Record path: string[] args: Map data?: {bytes: Buffer, raw: string, form: any} @@ -51,10 +51,10 @@ declare type User = { declare type InitContext = { modules: any, - database: Database, path: string, data_path: string, name: string, + knex: import('knex').Knex, } declare type ResultStatus = [Okay: false, Error: Error] | [Okay: true] diff --git a/types/handle.d.ts b/types/handle.d.ts index 7de00ae..6980ca0 100644 --- a/types/handle.d.ts +++ b/types/handle.d.ts @@ -1,3 +1,4 @@ declare interface Handle { + init(modules: any, knex: import('knex').Knex): Promise main(ctx: PartialContext): void, } diff --git a/types/modules/db.d.ts b/types/modules/db.d.ts deleted file mode 100644 index 425836d..0000000 --- a/types/modules/db.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -declare interface DB { - special_tables: string[] - db: any - prefix: string - - formatTableName(name: string): string - - createTable(table: string, columns: string[]): void - - dropTable(table: string): void - - addColumn(table: string, column: string, callback: (err: Error|null) => void): void - - dropColumn(table: string, column: string, callback: (err: Error|null) => void): void - - renameColumn(table: string, old_column: string, new_column: string, callback: (err: Error|null) => void): void - - select(table: string, columns: string[], where: string|null, order_by: string|null, params: {}, callback: (err: Error|null, data: any) => void): void - - insert(table: string, columns: string[], values: [], callback: (err: Error|null) => void): void - - update(table: string, columns: string[], where: string, values: {}, callback: (err: Error|null) => void): void - - delete(table: string, where: string, values: {}, callback: (err: Error|null) => void): void -} From 912b769340ab42cb862f91456f9b1ea06107850f Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:37:04 +0000 Subject: [PATCH 11/14] async/await and const --- package.json | 2 +- src/classes/extension.ts | 33 +++++++------- src/extensions/root/index.ts | 88 ++++++++++++++---------------------- src/handle.ts | 53 ++++++++++++---------- src/index.ts | 9 ++-- src/modules/fetch.ts | 38 ++++++++-------- src/util.ts | 4 ++ tsconfig.json | 3 +- types/classes/extension.d.ts | 8 ++-- types/modules/fetch.d.ts | 2 +- 10 files changed, 112 insertions(+), 128 deletions(-) create mode 100644 src/util.ts diff --git a/package.json b/package.json index a71a995..79d2f7e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "start": "npx tsc --outDir ./build ./src/index.ts && node build/index.js", - "dev": "tsx watch ./src/index.ts" + "dev": "tsx watch --exclude './data/**/*' --clear-screen=false ./src/index.ts" }, "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", diff --git a/src/classes/extension.ts b/src/classes/extension.ts index 5ce1e47..98675f9 100644 --- a/src/classes/extension.ts +++ b/src/classes/extension.ts @@ -1,5 +1,6 @@ import { Environment } from "nunjucks" import { Tables } from "./tables.ts" +import { unpack } from "../util.ts" export abstract class ExtensionBase implements Extension { admin_only = false @@ -38,9 +39,9 @@ export abstract class ExtensionBase implements Extension { return this.admin_only } - handle_req: Extension['handle_req'] = (ctx: Context) => { + handle_req: Extension['handle_req'] = async (ctx: Context) => { ctx.context.extension = this as unknown as Extension - return this.handle(ctx) + return await this.handle(ctx) } abstract handle: Extension['handle'] @@ -93,7 +94,7 @@ export abstract class ExtensionBase implements Extension { return res.end() } - headers = {...content.HTML, ...headers} + headers = {...content.HTML, ...headers} nj.render(this.name+'/'+item+'.html', ctx.context, (err: null|Error, data: FileData) => { if (err) { @@ -109,24 +110,22 @@ export abstract class ExtensionBase implements Extension { }) } - return_file: Extension['return_file'] = (ctx, file) => { + return_file: Extension['return_file'] = async (ctx, file) => { const {res} = ctx let [fetch, content]: [Fetch, ContentType] = this.get_dependencies('Fetch', 'content') - fetch.file(file, (data: FileData, filetype: string, err) => { - if (err) { - res.writeHead(404) - res.end() - return - } - // @ts-ignore - res.writeHead(200, content[filetype]) + const [result, err] = await fetch.file(file).then(unpack<[string, string]>) + if (err) { + res.writeHead(404) + res.end() + return + } - if (data != null) - return res.end(data) - else - return res.end() - }) + 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) => { diff --git a/src/extensions/root/index.ts b/src/extensions/root/index.ts index e9c05c2..581595e 100644 --- a/src/extensions/root/index.ts +++ b/src/extensions/root/index.ts @@ -1,6 +1,7 @@ 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 { @@ -32,14 +33,14 @@ export default class extends ExtensionBase implements RootExtension { return ExtensionBase.init(this, context) } - override requires_login = (path: string[]) => { + override requires_login: Extension['requires_login'] = (path) => { if (path.at(0) == '_') { return true } return false } - override handle = (ctx: Context) => { + override handle: Extension['handle'] = async (ctx) => { var location = ctx.path.shift() let [knex]: [Knex] = this.get_dependencies('Knex') @@ -124,18 +125,16 @@ export default class extends ExtensionBase implements RootExtension { args = decodeURIComponent(args) } catch {} - knex.query('user') + const head: [number, {}] = await knex.query('user') .update('pfp_code', args) .where('id', ctx.context.user.id) .then( - (v) => { - ctx.res.writeHead(307, {"Location": "/"}) - ctx.res.end() - }, (err) => { - ctx.res.writeHead(500) - ctx.res.end() - } + () => [307, {"Location": "/"}], + () => [500, {}] ) + + ctx.res.writeHead(...head) + ctx.res.end() return } else @@ -159,74 +158,57 @@ export default class extends ExtensionBase implements RootExtension { } } - authenticate: RootExtension['authenticate'] = (auth, ip, subnet, callback) => { + authenticate: RootExtension['authenticate'] = async (auth, ip, subnet) => { let [knex]: [Knex] = this.get_dependencies('Knex') if (auth) { // Try to get name and password - this.decrypt_auth(auth, (name, password, err) => { - if (err) { - return callback(undefined, err) - } - // Auth using name and password - if (name !== undefined) { - knex.query('user') - .select('*') - .where('name', name) - .then( - (result: User[]) => { - const user = result[0] - if (user !== undefined && password == user.password) { - return callback(user, err) - } - else { - return callback(undefined, new Error('Wrong name or password')) - } - }, - (err) => { - console.log(err) - return callback(undefined, new Error("Failed to check 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 - return knex.query({u: 'user', p: '_profile_device'}) + 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() - .then( - (user) => { - console.log(user, "def") - callback(user) - }, - (err) => { - console.log(err, "gef") - callback(undefined, new Error("Failed to get user by IP.")) - } - ) + + return user } - else - return callback(undefined, undefined) } - private decrypt_auth(auth: BasicAuth, callback: (name?: string, password?: string, err?: Error) => void): void { + 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 callback(undefined, undefined, new Error("Missing name or password")) + return new Error("Missing name or password") } // hash password password = this.hash_pw(password) - return callback(name, password) + return [name, password] } private hash_pw(password: string): string { diff --git a/src/handle.ts b/src/handle.ts index bcf74a6..952f9af 100644 --- a/src/handle.ts +++ b/src/handle.ts @@ -1,6 +1,6 @@ -import { Knex } from "knex" import { load } from "./extman.ts" import Log from "./modules/log.ts" +import { unpack } from "./util.ts" let log = new Log(true) @@ -40,7 +40,7 @@ export default class implements Handle { }) } - main: Handle['main'] = (partial_ctx: PartialContext) => { + main: Handle['main'] = async (partial_ctx: PartialContext) => { let location = partial_ctx.path.shift() ?? '' // set request context @@ -54,30 +54,33 @@ export default class implements Handle { } // Authenticate using user&pass, else using ip - this.root.authenticate(ctx.req.headers.authorization as BasicAuth|undefined, ctx.ip, this.wg_config.subnet, (user, err) => { - ctx.context.user = user - if (err) ctx.context.auth_err = err - if (user && user.is_admin) ctx.context.extensions = {...ctx.context.extensions, ...this.admin_extensions} - - // Extension - if (ctx.context.extensions.has(location)) { - let ext = ctx.context.extensions.get(location) as Extension - // If login required - if (!user && ext.requires_login(ctx.path)) { - ctx.res.writeHead(307, {Location: "/login"}) - return ctx.res.end() - } - else if (user && !user.is_admin && ext.requires_admin(ctx.path)) { - ctx.res.writeHead(307, {Location: "/"}) - return ctx.res.end() - } - ext.handle_req(ctx) + 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() } - // Root extension - else { - ctx.path.unshift(location) - this.root.handle_req(ctx) + 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 index a82ae9b..b56a7f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,14 +41,14 @@ if (!await knex.schema.hasTable('db_table_versions')) { }) } // get request handler -let handle = new Handle(modules) +const handle = new Handle(modules) handle.init(modules, knex) // set up logging -const log: Log = new Log(config.logging) +const log = new Log(config.logging) // handle all requests for both HTTPS and HTTP/2 or HTTP/nginx -const requestListener = async function (req: Http2ServerRequest, res: Http2ServerResponse) { +async function requestListener(req: Http2ServerRequest, res: Http2ServerResponse) { let ip: Context['ip'] let cookies: Context['cookies'] let args: Context['args'] = new Map() @@ -199,8 +199,7 @@ async function startServer(http_enabled: boolean, https_enabled: boolean) { if (http_enabled) { // Start server http1.createServer( - // @ts-expect-error - https_enabled ? httpsRedirect : requestListener + https_enabled ? httpsRedirect : requestListenerCompat ).listen( config.http_port, config.host, diff --git a/src/modules/fetch.ts b/src/modules/fetch.ts index 37ba64d..4d014dc 100644 --- a/src/modules/fetch.ts +++ b/src/modules/fetch.ts @@ -1,21 +1,21 @@ 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 BIN_EXTS: Set = new Set(['png','jpg','mp3']) + 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'] = (file_path, callback) => { + file: Fetch['file'] = async (file_path) => { // Ensure path is absolute if (!path.isAbsolute(file_path)) file_path = path.join(this.root, file_path) @@ -25,24 +25,22 @@ export default class implements Module, Fetch { // load from cache if available if (this.cache.has(file_path)) - return callback(this.cache.get(file_path), filetype) + return [this.cache.get(file_path) as string, filetype] // read the file - readFile(file_path) - // cache and return the data - .then((raw_data: Buffer) => { - let data: FileData = null - if (this.BIN_EXTS.has(filetype)) - data = raw_data as unknown as string - else - data = raw_data.toString('utf8') - - this.cache.set(file_path, data) - return callback(data, filetype) - }) - // error if file can't be read - .catch(err => { - return callback(undefined, undefined, err) - }) + 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/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 index b047290..726adc2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,7 @@ "target": "ESNext", "moduleResolution": "nodenext", "module": "NodeNext", - "allowImportingTsExtensions": true, - "noEmit": true, + "diagnostics": true, // "alwaysStrict": true, // "strict": true, "noImplicitThis": true, diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts index 3914fb6..0917a4d 100644 --- a/types/classes/extension.d.ts +++ b/types/classes/extension.d.ts @@ -12,9 +12,9 @@ declare interface Extension { requires_admin(path: string[]): boolean - handle_req(ctx: Context): void | Error - - handle(ctx: Context): void | Error + handle_req(ctx: Context): Promise + + handle(ctx: Context): void | Error | Promise return(ctx: Context, err?: Error, location?: string, err_code?: number): void @@ -34,7 +34,7 @@ declare interface Extension { } declare interface RootExtension extends Extension { - authenticate(auth: BasicAuth|undefined, ip: string, subnet: string, callback: (user: undefined|User, err?: Error) => void): void + authenticate(auth: BasicAuth|undefined, ip: string, subnet: string): Promise addUser(name: User['name'], password: User['password'], callback: (err?: Error) => void): void } diff --git a/types/modules/fetch.d.ts b/types/modules/fetch.d.ts index 0d5336e..d493efa 100644 --- a/types/modules/fetch.d.ts +++ b/types/modules/fetch.d.ts @@ -1,3 +1,3 @@ declare interface Fetch { - file(file_path: string, callback: (data?: FileData, type?: string, err?: Error) => void): void + file(file_path: string): Promise<[FileData, string] | Error> } From 26438890eb6d7da6d48235389533453c29b02292 Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Wed, 22 Jan 2025 20:34:23 +0000 Subject: [PATCH 12/14] update all extensions --- package.json | 2 +- src/classes/extension.ts | 5 +- src/extensions/admin/index.js | 16 --- src/extensions/admin/index.ts | 11 ++ src/extensions/chat/{index.js => index.ts} | 45 ++++--- src/extensions/nothing/index.js | 15 --- src/extensions/nothing/index.ts | 10 ++ src/extensions/profile/index.js | 125 ------------------ src/extensions/profile/index.ts | 146 +++++++++++++++++++++ src/extensions/profile/tables.ts | 1 + src/extensions/root/index.ts | 1 - src/index.ts | 1 - src/modules/knex.ts | 21 --- types/classes/extension.d.ts | 1 - 14 files changed, 194 insertions(+), 206 deletions(-) delete mode 100644 src/extensions/admin/index.js create mode 100644 src/extensions/admin/index.ts rename src/extensions/chat/{index.js => index.ts} (57%) delete mode 100644 src/extensions/nothing/index.js create mode 100644 src/extensions/nothing/index.ts delete mode 100644 src/extensions/profile/index.js create mode 100644 src/extensions/profile/index.ts diff --git a/package.json b/package.json index 79d2f7e..15f12ab 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "start": "npx tsc --outDir ./build ./src/index.ts && node build/index.js", - "dev": "tsx watch --exclude './data/**/*' --clear-screen=false ./src/index.ts" + "dev": "sudo tsx watch --exclude './data/**/*' --clear-screen=false ./src/index.ts" }, "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", diff --git a/src/classes/extension.ts b/src/classes/extension.ts index 98675f9..7eefdfe 100644 --- a/src/classes/extension.ts +++ b/src/classes/extension.ts @@ -5,7 +5,6 @@ import { unpack } from "../util.ts" export abstract class ExtensionBase implements Extension { admin_only = false tables = false - dependencies: Extension['dependencies'] = [] initialized_deps: DependencyMap = new DependencyMapImpl() name: Extension['name'] = "default_name" title: Extension['title'] = "Default Title" @@ -26,7 +25,9 @@ export abstract class ExtensionBase implements Extension { } } - abstract init: Extension['init'] + init: Extension['init'] = (context) => { + return ExtensionBase.init(this, context) + } /** * @returns true if the path requires being logged in, else false diff --git a/src/extensions/admin/index.js b/src/extensions/admin/index.js deleted file mode 100644 index 35ed746..0000000 --- a/src/extensions/admin/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import { ExtensionBase } from "../../modules" - -export default class extends ExtensionBase { - name = 'admin' - title = 'Admin' - admin_only = true - dependencies = ['content','nj'] - - init(context) { - return ExtensionBase.init(this, context) - } - - handle(req, res) { - this.return_html(req, res, 'index') - } -} 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/src/extensions/chat/index.js b/src/extensions/chat/index.ts similarity index 57% rename from src/extensions/chat/index.js rename to src/extensions/chat/index.ts index c3ceb5b..6876236 100644 --- a/src/extensions/chat/index.js +++ b/src/extensions/chat/index.ts @@ -1,45 +1,41 @@ -import { ExtensionBase } from "../../modules" +import { ExtensionBase } from "../../modules.ts" export default class extends ExtensionBase { - name = 'chat' - title = 'Chat' - dependencies = ['content','nj','fetch'] - messages = [{ + 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 = {} - - requires_login(path) { - return true - } + last_got_id: {[user_id: number]: number} = {} - init(context) { - return ExtensionBase.init(this, context) - } - - handle(ctx, deps) { - let [] = this.deps.massGet() + 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}, + user: { + name: ctx.context.user?.name, + pfp_code: ctx.context.user?.pfp_code, + }, time: now, - content: message + content: message, }) } ctx.context.chat = this.messages - this.last_got_id[ctx.context.user.id] = this.messages.length + 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(ctx.context.user.id) ? this.last_got_id[ctx.context.user.id] : 0 - this.last_got_id[ctx.context.user.id] = this.messages.length + 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') { @@ -47,9 +43,12 @@ export default class extends ExtensionBase { 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}, + user: { + name: ctx.context.user?.name, + pfp_code: ctx.context.user?.pfp_code, + }, time: now, - content: message + content: message, }) } return this.return(ctx) diff --git a/src/extensions/nothing/index.js b/src/extensions/nothing/index.js deleted file mode 100644 index efaa061..0000000 --- a/src/extensions/nothing/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import { ExtensionBase } from "../../modules" - -export default class extends ExtensionBase { - name = 'nothing' - title = 'Nothing' - dependencies = ['content','nj'] - - init = (context) => { - return ExtensionBase.init(this, context) - } - - handle(ctx, deps) { - this.return_html(ctx, 'index') - } -} 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/src/extensions/profile/index.js b/src/extensions/profile/index.js deleted file mode 100644 index 353a3ee..0000000 --- a/src/extensions/profile/index.js +++ /dev/null @@ -1,125 +0,0 @@ -import crypto from 'crypto' -import { ExtensionBase } from '../../modules' - -export default class extends ExtensionBase { - name = 'profile' - title = 'Network' - tables = true - dependencies = ['content','nj','fetch'] - wg = null - wg_config = null - - - async init(context) { - let config = context.modules.config - let data_path = context.data_path - this.wg_config = context.modules.wg_config - - this.wg = await import('./wireguard') - this.wg.init(data_path, this.wg_config, config.tmp_dir) - - return ExtensionBase.init(this, context) - } - - requires_login(path) { - if (path.at(0) == 'getconf') { - return false - } - return true - } - - handle(ctx, deps) { - let [db] = deps.massGet('DB') - var location = ctx.path.shift() - - switch (location) { - case '': - case undefined: { - db.select('device', ['*'], 'user_id=$id', null, [ctx.context.user.id], (err, profiles) => { - ctx.context.profiles = profiles - ctx.context.connected_ip = ctx.ip.startsWith(this.wg_config.subnet) ? ctx.ip : false - this.return_html(ctx, 'index', err) - }) - break - } - case 'delete': { - // Check ownership - this.owns(ctx.context.user, ctx.args.get('uuid'), (user_owns) => { - if (!user_owns) return this.return(ctx, true, 404) - // Delete db entry - db.delete('device', 'uuid=$uuid', [ctx.args.get('uuid')], (err) => { - // Delete wireguard profile - this.wg.remove(ctx.args.get('uuid'), () => { - return this.return(ctx, err, location='/profile') - }) - }) - }) - break - } - case 'add': { - // Get uuid - let uuid = crypto.randomUUID() - // Get IP suffix - 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 - db.insert('device', ['user_id','uuid','ip'], [ctx.context.user.id, uuid, ip], (err) => { - return this.return(ctx, err, location='/profile') - }) - }) - }) - break - } - case 'getconf': { - // Get uuid - let uuid = ctx.args.keys().next().value - // Get config - this.wg.getConfig(uuid, (data, err) => { - if (err) return this.return(ctx, true, 404) - // Mark as installed - db.update('device', ['installed=TRUE'], 'uuid=$uuid', [uuid], (err) => { - return this.return_data(ctx, data, err, {"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 - this.owns(ctx.context.user, ctx.args.get('uuid'), (user_owns) => { - if (!user_owns) return this.return(ctx, true, 404) - // Change name - db.update('device', ['name=$name'], 'uuid=$uuid', [ctx.data.form.post_data,ctx.args.get('uuid')], (err) => { - return this.return(ctx, err, location='/profile') - }) - }) - } - else { - 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 = (user, uuid, callback) => { - let db = this.initialized_deps.get('DB') - - if (!uuid) return callback(undefined) - 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/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/src/extensions/profile/tables.ts b/src/extensions/profile/tables.ts index 86ccb44..c92609b 100644 --- a/src/extensions/profile/tables.ts +++ b/src/extensions/profile/tables.ts @@ -12,6 +12,7 @@ export default class extends Tables { 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') diff --git a/src/extensions/root/index.ts b/src/extensions/root/index.ts index 581595e..ee737e1 100644 --- a/src/extensions/root/index.ts +++ b/src/extensions/root/index.ts @@ -8,7 +8,6 @@ export default class extends ExtensionBase implements RootExtension { override name = 'root' override title = 'Home' override tables = true - override dependencies = ['content','nj','fetch','texts','cookie'] favicons: string[] = [] favicons_path: string diff --git a/src/index.ts b/src/index.ts index b56a7f0..867101d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -209,4 +209,3 @@ async function startServer(http_enabled: boolean, https_enabled: boolean) { } startServer(true, !config.nginx) - \ No newline at end of file diff --git a/src/modules/knex.ts b/src/modules/knex.ts index c8a89ee..0bc9519 100644 --- a/src/modules/knex.ts +++ b/src/modules/knex.ts @@ -15,31 +15,10 @@ export default class implements Module { } query = (table: string | object) => { - // switch (typeof table) { - // case 'string': { - // table = this.parseTableName(table) - // break - // } - // case 'object': { - // Object.keys(table).forEach((key) => { - // // @ts-expect-error - // table[key] = this.parseTableName(table[key]) - // }) - // break - // } - // } return this.knex(table as never).queryContext({prefix: this.prefix}) } schema = () => { return this.knex.schema.queryContext({prefix: this.prefix}) } - - private parseTableName(name: string): string { - if (name.startsWith('_')) - return name - else { - return `_${this.prefix}_${name}` - } - } } diff --git a/types/classes/extension.d.ts b/types/classes/extension.d.ts index 0917a4d..f96fd08 100644 --- a/types/classes/extension.d.ts +++ b/types/classes/extension.d.ts @@ -1,7 +1,6 @@ declare interface Extension { admin_only: boolean tables: boolean - dependencies: string[] name: string title: string From 8c944ac4a7e7cc8d6a72b674227a3d6e782818af Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Wed, 22 Jan 2025 20:43:09 +0000 Subject: [PATCH 13/14] update version number and packages --- package-lock.json | 748 +++++++++++++++++++++++++++++----------------- package.json | 8 +- 2 files changed, 472 insertions(+), 284 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5097213..62b0a58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,301 @@ { "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": "1.0.1", + "cookie": "^1.0.2", "dotenv": "^16.4.5", "knex": "^3.1.0", "nunjucks": "^3.2.4", "sqlite3": "^5.1.6", - "typescript": "5.6.3" + "typescript": "^5.7.3" }, "devDependencies": { "@types/cookie": "^0.6.0", "@types/knex": "^0.15.2", - "@types/node": "22.7.5", + "@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", @@ -41,6 +313,125 @@ "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": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -110,13 +501,13 @@ } }, "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "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.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/nunjucks": { @@ -153,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": { @@ -189,21 +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==", - "license": "ISC", - "optional": true, - "peer": true, - "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", @@ -259,20 +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==", - "license": "MIT", - "optional": true, - "peer": true, - "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", @@ -304,20 +666,6 @@ "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==", - "license": "MIT", - "optional": true, - "peer": true, - "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", @@ -372,32 +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==", - "license": "MIT", - "optional": true, - "peer": true, - "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", @@ -434,12 +756,12 @@ "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": { @@ -457,20 +779,19 @@ "optional": true }, "node_modules/cookie": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", - "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "license": "MIT", - "optional": true, "dependencies": { "ms": "2.1.2" }, @@ -524,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" @@ -651,20 +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==", - "license": "MIT", - "optional": true, - "peer": true, - "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", @@ -690,6 +997,21 @@ "license": "ISC", "optional": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "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", @@ -730,9 +1052,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.9.0.tgz", - "integrity": "sha512-52n24W52sIueosRe0XZ8Ex5Yle+WbhfCKnV/gWXpbVR8FXNTfqdKEKUSypKso66VRHTvvcQxL44UTZbJRlCTnw==", + "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": { @@ -776,20 +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==", - "license": "ISC", - "optional": true, - "peer": true, - "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", @@ -969,20 +1277,6 @@ "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==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -998,17 +1292,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1019,20 +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==", - "license": "MIT", - "optional": true, - "peer": true, - "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", @@ -1040,17 +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==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1116,32 +1374,6 @@ } } }, - "node_modules/knex/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/knex/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1343,15 +1575,15 @@ "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": { @@ -1359,9 +1591,9 @@ } }, "node_modules/node-abi": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", - "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "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" @@ -1417,17 +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==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -1470,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", @@ -1517,24 +1747,10 @@ "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", "license": "MIT" }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "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", @@ -1542,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", @@ -1579,9 +1795,9 @@ } }, "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", @@ -1617,20 +1833,6 @@ "node": ">= 6" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -1968,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", @@ -2028,20 +2230,6 @@ "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==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", @@ -2075,9 +2263,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "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", @@ -2088,9 +2276,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "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 15f12ab..73ed3db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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": "src/index.ts", "type": "module", @@ -11,12 +11,12 @@ "author": "Keukeiland, Fizitzfux", "license": "MPL-2.0", "dependencies": { - "cookie": "1.0.1", + "cookie": "^1.0.2", "dotenv": "^16.4.5", "knex": "^3.1.0", "nunjucks": "^3.2.4", "sqlite3": "^5.1.6", - "typescript": "5.6.3" + "typescript": "^5.7.3" }, "repository": { "type": "git", @@ -29,7 +29,7 @@ "devDependencies": { "@types/cookie": "^0.6.0", "@types/knex": "^0.15.2", - "@types/node": "22.7.5", + "@types/node": "^22.10.7", "@types/nunjucks": "^3.2.6", "tsx": "^4.19.2" } From fb840a030c0087423ca49a9abfcc0eef36d89b94 Mon Sep 17 00:00:00 2001 From: Fizitzfux <66255957+fizitzfux@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:13:56 +0000 Subject: [PATCH 14/14] fix npm start --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73ed3db..0df0837 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "type": "module", "scripts": { - "start": "npx tsc --outDir ./build ./src/index.ts && node build/index.js", + "start": "tsx src/index.ts", "dev": "sudo tsx watch --exclude './data/**/*' --clear-screen=false ./src/index.ts" }, "author": "Keukeiland, Fizitzfux",