From 70e31009e01da5b4e5acebb9bd209daf789923e0 Mon Sep 17 00:00:00 2001 From: jusax23 Date: Wed, 1 Mar 2023 16:14:46 +0100 Subject: [PATCH] basic server struct --- package-lock.json | 34 +++++++++++++++++- package.json | 6 +++- src/api/get.ts | 72 +++++++++++++++++++++++++++++++++++++++ src/api/post.ts | 5 +++ src/api/user.ts | 16 +++++++++ src/api/ws.ts | 6 ++++ src/main.ts | 68 ++++++++++++++++++++++++++++++------ src/server/permissions.ts | 23 +++++++++++++ src/server/serverCerts.ts | 29 ++++++++++++++-- src/setup/config.ts | 8 ++--- src/sys/db.ts | 2 ++ 11 files changed, 249 insertions(+), 20 deletions(-) create mode 100644 src/api/get.ts create mode 100644 src/api/post.ts create mode 100644 src/api/user.ts create mode 100644 src/api/ws.ts diff --git a/package-lock.json b/package-lock.json index add68fa..c5feb7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0-only", "dependencies": { "commander": "^10.0.0", + "cors": "^2.8.5", "dblang": "https://jusax.de/git/attachments/c13552b7-c9f0-4f50-bcce-96a124c1c286", "express": "^4.18.2", "juml": "https://jusax.de/git/attachments/208913c5-2851-4b86-a53d-ca99fed168cc", @@ -18,10 +19,12 @@ "ws": "^8.12.1" }, "devDependencies": { + "@types/cors": "^2.8.13", "@types/express": "^4.17.17", "@types/node": "^18.11.18", "@types/pg": "^8.6.6", "@types/prompts": "^2.4.2", + "@types/ws": "^8.5.4", "esbuild": "^0.17.10", "pkg": "^5.8.0", "typescript": "^4.9.4" @@ -529,6 +532,15 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/express": { "version": "4.17.17", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", @@ -612,6 +624,15 @@ "@types/node": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1046,6 +1067,18 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -2168,7 +2201,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index 3b3e3aa..6b48e05 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "dist/main.js", "type": "module", "scripts": { - "main": "tsc && node . -c config.juml" + "main": "tsc && node . -c config.juml", + "setup": "tsc && node . -c config.juml -s" }, "repository": { "type": "git", @@ -17,16 +18,19 @@ "author": "jusax23, comcloudway", "license": "AGPL-3.0-only", "devDependencies": { + "@types/cors": "^2.8.13", "@types/express": "^4.17.17", "@types/node": "^18.11.18", "@types/pg": "^8.6.6", "@types/prompts": "^2.4.2", + "@types/ws": "^8.5.4", "esbuild": "^0.17.10", "pkg": "^5.8.0", "typescript": "^4.9.4" }, "dependencies": { "commander": "^10.0.0", + "cors": "^2.8.5", "dblang": "https://jusax.de/git/attachments/c13552b7-c9f0-4f50-bcce-96a124c1c286", "express": "^4.18.2", "juml": "https://jusax.de/git/attachments/208913c5-2851-4b86-a53d-ca99fed168cc", diff --git a/src/api/get.ts b/src/api/get.ts new file mode 100644 index 0000000..3db0cd5 --- /dev/null +++ b/src/api/get.ts @@ -0,0 +1,72 @@ +import { eq, ge, select } from "dblang"; +import express, { Request, Response } from "express"; +import { suspectRequest } from "../sys/bruteforce.js"; +import { oConf } from "../sys/config.js"; +import { accounts, db, rooms } from "../sys/db.js"; +import { error } from "../sys/log.js"; +import { getSettings, SETTINGS } from "../sys/settings.js"; + +var Methods: ([string, (req: Request, res: Response) => Promise])[] = [ + ["users", async (req: Request, res: Response) => { + let resp = await select([accounts.name], accounts) + .where(eq(accounts.viewable, true)) + .query(db); + res.json(resp.map(d => d[accounts.name])); + }], + ["server/publicKey", async (req: Request, res: Response) => { + let publicKey = await getSettings(SETTINGS.publicKey); + let expires = await getSettings(SETTINGS.certExpires); + if (publicKey && expires) { + res.json({ + publicKey, + expires + }); + } + }], + ["isUser/:user", async (req: suspectRequest, res: Response) => { + var user = req.params.user; + let data = await select([accounts.accID], accounts) + .where(eq(accounts.name, user)) + .query(db); + if (data.length == 0) { + if (req.suspect) req.suspect(); + res.send("error"); + } else { + res.send("ok"); + } + }], + ["publicRooms", async (req: Request, res: Response) => { + let data = await select([rooms.name, rooms.title, rooms.description, rooms.icon], rooms) + .where(ge(rooms.public, 1)) + .query(db); + var out = data.map(d => { + let name = d[rooms.name]; + let title = d[rooms.title]; + let description = d[rooms.description]; + let icon = d[rooms.icon]; + if (name != null && title != null && description != null && icon != null) { + return { name, title, description, icon }; + } + return null; + }); + res.json(out.filter(d => d != null)); + }] +]; + +export const addGetMethods = (server: express.Express) => { + server.get("/.well-known/outbag/server", (req, res) => { + res.json({ + port: oConf.get("System", "PORTexposed"), + path: oConf.get("System", "PATHexposed"), + }) + }); + Methods.forEach((d) => void server.get("/api/" + d[0], async (req, res) => { + try { + await d[1](req, res); + if (!res.headersSent) res.status(500).send(); + } catch (e) { + error("API", "Get Request Error:", e); + res.status(500).send(); + } + })); +} \ No newline at end of file diff --git a/src/api/post.ts b/src/api/post.ts new file mode 100644 index 0000000..7bff105 --- /dev/null +++ b/src/api/post.ts @@ -0,0 +1,5 @@ +import express from "express"; + +export const addPostMethods = (server: express.Express) => { + +} \ No newline at end of file diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..8ac86f6 --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,16 @@ +export const STATE = { + no: 0b0001, + remoteP: 0b0010, + remote: 0b0100, + client: 0b1000, +}; + +export abstract class Client{ + name: String; + server: String; + ip: String; + rights: number; + state: number; + accID = -1; + +} \ No newline at end of file diff --git a/src/api/ws.ts b/src/api/ws.ts new file mode 100644 index 0000000..1180ad3 --- /dev/null +++ b/src/api/ws.ts @@ -0,0 +1,6 @@ +import ws from "ws"; +import http from "http"; + +export const wsOnConnection = (socket:ws.WebSocket, req: http.IncomingMessage) => { + +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index ccb5520..3ef15e2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,14 +4,20 @@ declare global { import express from "express"; import https from "https"; import http from "http"; +import nman from "nman"; +import cors from "cors"; +import { WebSocketServer } from "ws"; import { Command } from "commander"; -import { oConf } from "./sys/config.js" -import { log } from "./sys/log.js" -import { connectToDB } from "./sys/db.js" -import bruteforce from "./sys/bruteforce.js" -import { getSettings, setSettings } from "./sys/settings.js" +import { oConf } from "./sys/config.js"; +import { error, log } from "./sys/log.js"; +import { connectToDB } from "./sys/db.js"; +import bruteforce, { suspectRequest } from "./sys/bruteforce.js"; +import { fullSetup, partiellSetup } from "./setup/config.js"; +import { addGetMethods } from "./api/get.js"; +import { addPostMethods } from "./api/post.js"; +import { wsOnConnection } from "./api/ws.js"; +import { startUpdateCerts } from "./server/serverCerts.js"; -import { fullSetup, partiellSetup } from "./setup/config.js" const config = { version: "0.0.1" @@ -32,13 +38,13 @@ program global.debug = debug != null; if (config) { log("System", "Starting with config:", config); - if(!oConf.connect(config)) dofullSetup = true; + if (!oConf.connect(config)) dofullSetup = true; } - - if(setup || dofullSetup){ - if(dofullSetup) fullSetup(); + + if (setup || dofullSetup) { + if (dofullSetup) fullSetup(); else partiellSetup(); - }else{ + } else { startServer(); } }); @@ -46,10 +52,50 @@ program program.parse(); +var serverclose = { close: (d: () => void) => d() }; +nman.addShutdownTask(() => new Promise(async (res, rej) => { + //await closeWebSocket(); + serverclose.close(() => res()); +}), 30000); async function startServer() { await connectToDB(); const server = express(); + server.use(express.json({ limit: '100mb' })); + server.use(express.urlencoded({ limit: '100mb', extended: false })); + server.use(cors()); server.use(bruteforce); + addGetMethods(server); + addPostMethods(server); + if (oConf.get("ssl", "enable")) { + log("Server", "SSL Enabled"); + oConf.readPathes(oConf.get("ssl", "privkey"), oConf.get("ssl", "cert"), oConf.get("ssl", "chain")) + .then(([privkey, cert, chain]: any) => { + const HTTPserver = https.createServer({ key: privkey, cert: cert, ca: chain }, server); + const wssServer = new WebSocketServer({ server: HTTPserver }); + wssServer.on('connection', wsOnConnection); + serverclose = HTTPserver.listen(oConf.get("System", "PORT"), () => { + log("Server", 'Listening...'); + complete_loaded(); + }); + }).catch(err => { + error("Server", err); + nman.shutdown(); + }); + } else { + log("Server", "SSL Disabled"); + const HTTPserver = http.createServer({}, server); + const wssServer = new WebSocketServer({ server: HTTPserver }); + wssServer.on('connection', wsOnConnection); + serverclose = HTTPserver.listen(oConf.get("System", "PORT"), () => { + log("Server", 'Listening...'); + complete_loaded(); + }); + } } + + +function complete_loaded() { + startUpdateCerts(); +} \ No newline at end of file diff --git a/src/server/permissions.ts b/src/server/permissions.ts index c0588ca..a985597 100644 --- a/src/server/permissions.ts +++ b/src/server/permissions.ts @@ -1,3 +1,6 @@ +import { eq, select, update } from "dblang"; +import { accounts, db } from "../sys/db.js"; + export const PERMISSIONS = { NONE: 0b0000000000000000, // equal to no account or blocked account @@ -14,3 +17,23 @@ export const PERMISSIONS = { EDIT_USERS: 0b1000000000000000, ALL: 0b1111111111111111, }; + +export const checkRight = async (accID: number, right: number) => { + try { + if (right == 0) return true; + let data = await select([accounts.rights], accounts) + .where(eq(accounts.accID, accID)) + .query(db); + return ((data[0][accounts.rights]) & right) == right; + } catch (e) { + + } + return false; +}; + +export const setRights = async (accID: number, rights: number) => { + await update(accounts) + .set(accounts.rights, rights) + .where(eq(accounts.accID, accID)) + .query(db); +}; \ No newline at end of file diff --git a/src/server/serverCerts.ts b/src/server/serverCerts.ts index 37218f4..b13faf2 100644 --- a/src/server/serverCerts.ts +++ b/src/server/serverCerts.ts @@ -1,8 +1,31 @@ import { outbagURL } from "./outbagURL.js"; import { db, serverCerts } from "../sys/db.js" -import { eq, exists, insert, not, select, update } from "dblang"; -import { error } from "../sys/log.js"; -import { uts } from "../sys/tools.js" +import { eq, exists, insert, not, remove, select, update } from "dblang"; +import { error, log } from "../sys/log.js"; +import { uts } from "../sys/tools.js"; +import { getSettings, setSettings, SETTINGS } from "../sys/settings.js" +import { generateSigningKey } from "../sys/crypto.js" +import { oConf } from "../sys/config.js"; +import nman from "nman"; + +export const startUpdateCerts = () => { + const update = async () => { + var exp = parseInt(await getSettings(SETTINGS.certExpires)); + if (uts() - 60 >= (exp || 0)) { + log("serverCert", "generete new Cert"); + let { privateKey, publicKey } = await generateSigningKey(); + await setSettings(SETTINGS.publicKey, publicKey); + await setSettings(SETTINGS.privateKey, privateKey); + await setSettings(SETTINGS.certExpires, String(uts() + oConf.get("System", "CertLiveSec"))); + } + }; + let intervalId = setInterval(update, 1000 * 60); + update(); + + nman.addShutdownTask(() => { + clearInterval(intervalId); + }, 100); +} async function updateRemote(url: string, pKey: string = ""): Promise { diff --git a/src/setup/config.ts b/src/setup/config.ts index 894170b..e88d2cb 100644 --- a/src/setup/config.ts +++ b/src/setup/config.ts @@ -22,10 +22,10 @@ const isFile = async (path: string) => { } const loading = () => { - var P = ["\\", "|", "/", "-"]; + var P = "⣾⣽⣻⢿⡿⣟⣯⣷"; var x = 0; let intID = setInterval(function () { - process.stdout.write("\r" + P[(x++) % P.length]); + process.stdout.write("\r\x1b[1m" + P[(x++) % P.length]+" "); }, 150); return () => { process.stdout.write("\r"); @@ -214,7 +214,7 @@ const database = async () => { oConf.set("Database", "password", resp.password); oConf.set("Database", "database", resp.database); oConf.save(); - db.close(); + await db.close(); return true; } catch (e) { cancle(); @@ -235,7 +235,7 @@ const outbag = async () => { }, { type: "number", name: "defaultMaxRooms", - message: "Please enter the default maximum nuber of rooms!", + message: "Please enter the default maximum number of rooms!", initial: oConf.get("Settings", "defaultMaxRooms"), validate: i => i >= -1 }, { diff --git a/src/sys/db.ts b/src/sys/db.ts index 6912650..49d2f84 100644 --- a/src/sys/db.ts +++ b/src/sys/db.ts @@ -47,6 +47,8 @@ accounts.addAttributes({ accID: { type: INT, primaryKey: true, autoIncrement: true }, name: { type: VARCHAR(255), default: PERMISSIONS.DEFAULT }, + rights: { type: BIGINT, default: PERMISSIONS.DEFAULT }, + accountKeySalt: { type: VARCHAR(64) }, accountKey: { type: VARCHAR(64) },