diff --git a/.gitignore b/.gitignore index 88278a7..e66b282 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ dist config.juml .vscode test -build \ No newline at end of file +build +testLocal.juml +openssl \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml index 88a4d48..b79b0e8 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -10,6 +10,12 @@ pipeline: - mkdir build - npm run prepublish - npm run bundleRelease + test: + image: node:18-alpine + commands: + - apk add openssl + - sh genSelfSignedCert.sh + - node tests/tester.js ci buildBin: image: node:18 commands: @@ -34,4 +40,13 @@ pipeline: - npx gitea-release "$${API_URL}" "$${GITEA_TOKEN}" "$${CI_REPO}" "$${CI_COMMIT_BRANCH}" "$${CI_COMMIT_TAG}" "upload" "$${CI_COMMIT_MESSAGE}" when: event: tag - secrets: [ gitea_token, api_url ] \ No newline at end of file + secrets: [ gitea_token, api_url ] + + +services: + database: + image: mysql + environment: + - MYSQL_DATABASE=outbag + - MYSQL_USER=outbag + - MYSQL_PASSWORD=12345678 \ No newline at end of file diff --git a/genSelfSignedCert.sh b/genSelfSignedCert.sh new file mode 100644 index 0000000..155ae65 --- /dev/null +++ b/genSelfSignedCert.sh @@ -0,0 +1,3 @@ +mkdir -p openssl +openssl req -nodes -new -x509 -keyout openssl/server.key -out openssl/server.cert -subj '/CN=localhost' +touch openssl/server.chain \ No newline at end of file diff --git a/src/api/acts/login.ts b/src/api/acts/login.ts index 68bb355..88debc5 100644 --- a/src/api/acts/login.ts +++ b/src/api/acts/login.ts @@ -45,22 +45,29 @@ export const signup: Act = { return; } let salt = get64(16); - let req = await insert(accounts.name, accounts.rights, accounts.accountKey, accounts.accountKeySalt) - .add(data.name, userNum == 0 ? PERMISSIONS.ALL : PERMISSIONS.DEFAULT, sha256(salt + data.accountKey), salt) - .query(db); - if (req.affectedRows > 0) { - let accID = Number(req.insertId); - if (!isNaN(accID)) { - aws("ok", ""); - client.state = STATE.client; - client.accID = accID; - client.name = data.name; - client.server = new outbagServer(data.server, oConf.get("System", "URL") + "", oConf.get("System", "PATHexposed") + "", oConf.get("System", "PORTexposed") + ""); + try { + let req = await insert(accounts.name, accounts.rights, accounts.accountKey, accounts.accountKeySalt) + .add(data.name, userNum == 0 ? PERMISSIONS.ALL : PERMISSIONS.DEFAULT, sha256(salt + data.accountKey), salt) + .query(db); + if (req.affectedRows > 0) { + let accID = Number(req.insertId); + if (!isNaN(accID)) { + aws("ok", ""); + client.state = STATE.client; + client.accID = accID; + client.name = data.name; + client.server = new outbagServer(data.server, oConf.get("System", "URL") + "", oConf.get("System", "PATHexposed") + "", oConf.get("System", "PORTexposed") + ""); + } + } else { + client.suspect(); + aws("error", "existence"); } - } else { + } catch (error) { client.suspect(); aws("error", "existence"); } + + } }; diff --git a/src/api/acts/rooms.ts b/src/api/acts/rooms.ts index d5b6d92..f481a0e 100644 --- a/src/api/acts/rooms.ts +++ b/src/api/acts/rooms.ts @@ -369,4 +369,4 @@ export const roomMeta: Act = { .query(db); aws("ok", ""); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 65da381..7dd7cca 100644 --- a/src/main.ts +++ b/src/main.ts @@ -37,6 +37,9 @@ program .action(({ config, debug, setup }) => { let dofullSetup = false; global.debug = debug != null; + if(global.debug){ + process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; + } if (config) { log("System", "Starting with config:", config); if (!oConf.connect(config)) dofullSetup = true; @@ -75,7 +78,9 @@ async function startServer() { 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 HTTPserver = chain != "" ? + https.createServer({ key: privkey, cert: cert, ca: chain }, server) : + https.createServer({ key: privkey, cert: cert }, server); const wssServer = new WebSocketServer({ server: HTTPserver }); wssServer.on('connection', wsOnConnection); serverclose = HTTPserver.listen(oConf.get("System", "PORT"), () => { @@ -101,7 +106,7 @@ async function complete_loaded() { startUpdateCert(); await wait(500); let succ = await generateTag(); - if(!succ) error("Outbag", "Could not check own Server Tag. Remote-Auth may not work! Check if the Server is reachable and the config ist correct!"); + if (!succ) error("Outbag", "Could not check own Server Tag. Remote-Auth may not work! Check if the Server is reachable and the config ist correct!"); activatePost(); activateWS(); log("Server", 'Listening...'); diff --git a/src/sys/db.ts b/src/sys/db.ts index d863728..1c45d9d 100644 --- a/src/sys/db.ts +++ b/src/sys/db.ts @@ -46,7 +46,7 @@ nMan.addShutdownTask(db.close, 3000, 10); export const accounts = db.newTable("accounts"); accounts.addAttributes({ accID: { type: INT, primaryKey: true, autoIncrement: true }, - name: { type: VARCHAR(100), default: PERMISSIONS.DEFAULT }, + name: { type: VARCHAR(100), unique: true, default: PERMISSIONS.DEFAULT }, rights: { type: INT, default: PERMISSIONS.DEFAULT }, diff --git a/test.juml b/test.juml new file mode 100644 index 0000000..585fde0 --- /dev/null +++ b/test.juml @@ -0,0 +1,27 @@ + +[System] +#The Server will listen on this Port! +PORT=7224 +PORTexposed=7224 +PATHexposed=/ +URL=localhost +CertLiveSec=2592000 + +[ssl] +enable=true +privkey=openssl/server.key +cert=openssl/server.cert +chain=openssl/server.chain + +[Database] +host=database +port=3306 +user=root +password=12345678 +database=outbag + +[Settings] +maxUsers=2 +defaultMaxRooms=3 +defaultMaxRoomSize=10 +defaultMaxUsersPerRoom=2 diff --git a/tests/post.js b/tests/post.js new file mode 100644 index 0000000..4a6ee4d --- /dev/null +++ b/tests/post.js @@ -0,0 +1,3 @@ +export async function postTester(){ + +} \ No newline at end of file diff --git a/tests/tester.js b/tests/tester.js new file mode 100644 index 0000000..2d018af --- /dev/null +++ b/tests/tester.js @@ -0,0 +1,43 @@ +import { spawn } from "child_process"; +import { postTester } from "./post.js"; +import { wsTester } from "./ws.js"; + +let inCI = process.argv.includes("ci"); + +const ls = spawn('node', ['.', '-c', inCI ? 'test.juml' : 'testLocal.juml', '-d']); + +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; + +ls.stdout.on('data', (data) => { + process.stdout.write(data); + if(data.includes("Listening...")) test(); +}); + +ls.stderr.on('data', (data) => { + console.error(`stderr: ${data}`); + +}); + +ls.on('close', (code) => { + console.log(`child process exited with code ${code}`); + process.exit(code); +}); + +function kill() { + ls.kill('SIGINT'); +} + +let startet = false; + +async function test() { + if(startet) return; + startet = true; + console.log("Start testing"); + let url = "localhost:7224"; + await wsTester("wss://" + url + "/", kill); + await postTester("https://" + url + "/", kill); + kill(); +} + + + diff --git a/tests/ws.js b/tests/ws.js new file mode 100644 index 0000000..c291972 --- /dev/null +++ b/tests/ws.js @@ -0,0 +1,132 @@ +import WebSocket from 'ws'; +import { generateSigningKey, sign } from '../dist/sys/crypto.js'; +import { uts, wait } from '../dist/sys/tools.js'; +import * as ws from "./ws/ws.js" + +function conn(url) { + const ws = new WebSocket(url); + + ws.on('close', function open() { + wsOpen = false; + }); + + let wsOpen = true; + + var list = []; + var c = 0; + + ws.on('message', function message(data) { + data = data.toString(); + if (data == "error") return void console.log("Server error"); + var json = JSON.parse(data); + for (let i = 0; i < list.length; i++) { + const e = list[i]; + if (e[0] == json.id) { + e[2]({ state: json.state, data: json.data }); + list.splice(i, 1); + return; + } + } + }); + + this.close = function () { + ws.close(); + } + + setInterval(() => { + for (let i = 0; i < list.length; i++) { + const e = list[i]; + if (e[1] + 5 < uts()) { + e[2]("timeout"); + list.splice(i, 1); + } + } + }, 1000); + + function req(act, data) { + return new Promise((res, rej) => { + if (!wsOpen) { + console.error("WebSocket is closed"); + rej("WebSocket is closed"); + return; + } + var id = ++c; + list.push([id, uts(), res, rej]); + ws.send(JSON.stringify({ + id, + act, + data + })); + }) + } + this.req = req; +}; + +function shallowEqual(object1, object2) { + const keys1 = Object.keys(object1); + const keys2 = Object.keys(object2); + if (keys1.length < keys2.length) { + return false; + } + for (let key of keys1) { + if (object1[key] != object2[key]) { + return false; + } + } + return true; +} + + + +export async function wsTester(url, kill) { + async function test(conn, act, data, expState, expData) { + console.log("Testing Act:", act, "with Data:", data); + let resp = await conn.req(act, data); + if (resp.state != expState) { + console.error(`Expected state: '${expState}', but got: '${resp.state}'`); + kill(); + process.exit(1); + } + if (typeof expData == "object" && expData != null) { + if (!shallowEqual(expData, resp.data)) { + console.error(`Expected data: '${expData}', but got: '${resp.data}'`); + kill(); + process.exit(1); + } + } else { + if (expData != resp.data) { + console.error(`Expected data: '${expData}', but got: '${resp.data}'`); + kill(); + process.exit(1); + } + } + return resp.data; + } + + for (const k in ws) { + console.log(`Testing '${k}':`); + const handler = [new conn(url)]; + await wait(100); + const currTest = ws[k]; + let resp = true; + try { + resp = await currTest(handler[0], test, async ()=>{ + let h = new conn(url); + await wait(100); + handler.push(h); + return h; + }); + } catch (error) { + console.error("Test Error: ", error); + kill(); + process.exit(1); + } + + handler.forEach(h=>h.close()); + if (resp === false) { + console.log("Test respond with error indication!"); + kill(); + process.exit(1); + } + } +} diff --git a/tests/ws/login.js b/tests/ws/login.js new file mode 100644 index 0000000..3bf661a --- /dev/null +++ b/tests/ws/login.js @@ -0,0 +1,32 @@ +let name1 = "testUser1"; +let name2 = "testUser2"; +let name3 = "testUser3"; +let accountKey = "123456789"; + +export const signup = async (handler, req, newHandler) => { + await req(handler, "signup", { + name: name1, + server: "localhost:7224", + accountKey + }, "ok", ""); + await req(handler, "signup", { + name: name1, + server: "localhost:7224", + accountKey + }, "error", "wrongstate"); + await req(await newHandler(), "signup", { + name: name1, + server: "localhost:7224", + accountKey + }, "error", "existence"); + await req(await newHandler(), "signup", { + name: name2, + server: "localhost:7224", + accountKey + }, "ok", ""); + await req(await newHandler(), "signup", { + name: name2, + server: "localhost:7224", + accountKey + }, "error", "config"); +}; \ No newline at end of file diff --git a/tests/ws/ws.js b/tests/ws/ws.js new file mode 100644 index 0000000..94d9888 --- /dev/null +++ b/tests/ws/ws.js @@ -0,0 +1 @@ +export * from "./login.js"; \ No newline at end of file