diff --git a/package-lock.json b/package-lock.json index fa749df..9623a96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "dependencies": { "@types/express": "^4.17.9", "@types/node": "^15.0.1", + "@types/socket.io": "^3.0.2", "better-sqlite3": "^7.1.5", "connect-busboy": "0.0.2", "cookie-parser": "^1.4.5", @@ -15,6 +16,7 @@ "ejs": "^3.1.6", "express": "^4.6.1", "express-fileupload": "^1.2.1", + "socket.io": "^4.1.2", "ts-node": "^9.0.0", "typescript": "^4.1.2", "uuid": "^8.3.2", @@ -999,6 +1001,11 @@ "@types/node": "*" } }, + "node_modules/@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" + }, "node_modules/@types/connect": { "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", @@ -1007,6 +1014,16 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==" + }, + "node_modules/@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" + }, "node_modules/@types/express": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", @@ -1118,6 +1135,15 @@ "@types/node": "*" } }, + "node_modules/@types/socket.io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", + "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", + "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", + "dependencies": { + "socket.io": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", @@ -1722,6 +1748,14 @@ "node": ">=0.10.0" } }, + "node_modules/base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1741,6 +1775,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -2263,8 +2305,7 @@ "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -2439,7 +2480,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2712,6 +2752,42 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.1.1.tgz", + "integrity": "sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "dependencies": { + "base64-arraybuffer": "0.1.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -5530,8 +5606,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanomatch": { "version": "1.2.13", @@ -7383,6 +7458,43 @@ "node": ">=0.10.0" } }, + "node_modules/socket.io": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz", + "integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==", + "dependencies": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~5.1.0", + "socket.io-adapter": "~2.3.0", + "socket.io-parser": "~4.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.1.tgz", + "integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw==" + }, + "node_modules/socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8547,7 +8659,6 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", - "dev": true, "engines": { "node": ">=8.3.0" }, @@ -9497,6 +9608,11 @@ "@types/node": "*" } }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" + }, "@types/connect": { "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", @@ -9505,6 +9621,16 @@ "@types/node": "*" } }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==" + }, + "@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" + }, "@types/express": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", @@ -9616,6 +9742,14 @@ "@types/node": "*" } }, + "@types/socket.io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", + "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", + "requires": { + "socket.io": "*" + } + }, "@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", @@ -10045,11 +10179,21 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -10471,8 +10615,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "concat-map": { "version": "0.0.1", @@ -10616,7 +10759,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -10814,6 +10956,35 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.1.1.tgz", + "integrity": "sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, + "engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "requires": { + "base64-arraybuffer": "0.1.4" + } + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -13008,8 +13179,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nanomatch": { "version": "1.2.13", @@ -14447,6 +14617,37 @@ } } }, + "socket.io": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz", + "integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==", + "requires": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~5.1.0", + "socket.io-adapter": "~2.3.0", + "socket.io-parser": "~4.0.3" + } + }, + "socket.io-adapter": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.1.tgz", + "integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw==" + }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -15372,7 +15573,6 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", - "dev": true, "requires": {} }, "xml-name-validator": { diff --git a/package.json b/package.json index a7752cf..b81d889 100755 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "dependencies": { "@types/express": "^4.17.9", "@types/node": "^15.0.1", + "@types/socket.io": "^3.0.2", "better-sqlite3": "^7.1.5", "connect-busboy": "0.0.2", "cookie-parser": "^1.4.5", @@ -11,6 +12,7 @@ "ejs": "^3.1.6", "express": "^4.6.1", "express-fileupload": "^1.2.1", + "socket.io": "^4.1.2", "ts-node": "^9.0.0", "typescript": "^4.1.2", "uuid": "^8.3.2", diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index 09de932..fcb8260 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -8,30 +8,30 @@ interface Options { exceptions: Array } -const testUser = { +export const testUser = { id: 19, avaiblePWRequests: 645, pwRequestCount: 19, created: new Date(), } +function renderLogin(req, res, jsonResponse) { + res.status('401') // Unauthorized + if (jsonResponse) { + res.json({ + result: 'nouser', + msg: 'You are not logged in', + }) + } else { + res.render('login', { + devel: process.env.NS_DEVEL, + }) + } +} + export default function (options: Options): any { const { userDB, jsonResponse, exceptions } = options - const renderLogin = (req, res) => { - res.status('401') // Unauthorized - if (jsonResponse) { - res.json({ - result: 'nouser', - msg: 'You are not logged in', - }) - } else { - res.render('login', { - devel: process.env.NS_DEVEL, - }) - } - } - return function (req, res, next) { const sessionID = req.cookies.sessionID const isException = exceptions.some((exc) => { @@ -66,7 +66,7 @@ export default function (options: Options): any { return } logger.DebugLog(`No session ID: ${req.url}`, 'auth', 1) - renderLogin(req, res) + renderLogin(req, res, jsonResponse) return } @@ -80,7 +80,7 @@ export default function (options: Options): any { return } logger.DebugLog(`No user:${req.url}`, 'auth', 1) - renderLogin(req, res) + renderLogin(req, res, jsonResponse) return } diff --git a/src/middlewares/socketAuth.middleware.ts b/src/middlewares/socketAuth.middleware.ts new file mode 100644 index 0000000..7a9752c --- /dev/null +++ b/src/middlewares/socketAuth.middleware.ts @@ -0,0 +1,64 @@ +import logger from '../utils/logger' +import dbtools from '../utils/dbtools' +import cookie from 'cookie' + +import { testUser } from './auth.middleware' + +interface Options { + userDB: any +} + +export default function SocketAuth(options: Options): any { + const { userDB } = options + + return (socket, next) => { + try { + const cookies = cookie.parse(socket.handshake.headers.cookie || '') + const sessionID = cookies.sessionID + + if (process.env.NS_NOUSER) { + socket.user = testUser + next() + return + } + + if (!sessionID) { + next(new Error('Not authenticated, please log in')) + return + } + + const user = GetUserBySessionID(userDB, sessionID) + + if (!user) { + next(new Error('Not authenticated, please log in')) + return + } + socket.user = user + next() + } catch (e) { + next(new Error('Authentication server error')) + console.error('Authentication server error') + console.error(e) + } + } +} + +function GetUserBySessionID(db: any, sessionID: string) { + logger.DebugLog(`Getting user from db`, 'auth', 2) + + const session = dbtools.Select(db, 'sessions', { + id: sessionID, + })[0] + + if (!session) { + return + } + + const user = dbtools.Select(db, 'users', { + id: session.userID, + })[0] + + if (user) { + return user + } +} diff --git a/src/modules/api/api.ts b/src/modules/api/api.ts index 22ccb6c..08112e3 100644 --- a/src/modules/api/api.ts +++ b/src/modules/api/api.ts @@ -40,6 +40,8 @@ const moduleName = 'API' let userDB let url let publicdirs = [] +let httpServer +let httpsServer function GetApp(): ModuleType { const app = express() @@ -203,6 +205,8 @@ function setupSubModules( url: url, publicdirs: publicdirs, moduleSpecificData: moduleSpecificData, + httpServer: httpServer, + httpsServer: httpsServer, }) moduleDatas.push(loadedModData || {}) } catch (e) { @@ -219,7 +223,9 @@ export default { getApp: GetApp, setup: (data: SetupData): void => { userDB = data.userDB - url = data.url // eslint-disable-line + url = data.url publicdirs = data.publicdirs + httpServer = data.httpServer + httpsServer = data.httpsServer }, } diff --git a/src/modules/api/msgsDbStruct.json b/src/modules/api/msgsDbStruct.json new file mode 100644 index 0000000..a1f39ba --- /dev/null +++ b/src/modules/api/msgsDbStruct.json @@ -0,0 +1,52 @@ +{ + "msgs": { + "tableStruct": { + "id": { + "type": "integer", + "primary": true, + "autoIncrement": true + }, + "sender": { + "type": "integer", + "notNull": true + }, + "reciever": { + "type": "integer", + "notNull": true + }, + "msg": { + "type": "text" + }, + "type": { + "type": "text" + }, + "date": { + "type": "integer" + }, + "unread": { + "type": "integer", + "defaultZero": true + } + }, + "foreignKey": [ + { + "keysFrom": [ + "reciever" + ], + "table": "users", + "keysTo": [ + "id" + ] + }, + { + "keysFrom": [ + "sender" + ], + "table": "users", + "keysTo": [ + "id" + ] + } + ] + } +} diff --git a/src/modules/api/submodules/chat.ts b/src/modules/api/submodules/chat.ts new file mode 100644 index 0000000..7de508d --- /dev/null +++ b/src/modules/api/submodules/chat.ts @@ -0,0 +1,241 @@ +import { Server as socket, Socket } from 'socket.io' + +import utils from '../../../utils/utils' +import dbtools from '../../../utils/dbtools' +import logger from '../../../utils/logger' +import { Request, SubmoduleData, User } from '../../../types/basicTypes' +import socketAuth from '../../../middlewares/socketAuth.middleware' + +const msgDbPath = './data/dbs/msgs.db' +const msgPaginationLimit = 15 + +interface ExtendedSocket extends Socket { + user: User +} + +function setup(data: SubmoduleData): void { + const { app, httpServer, httpsServer, userDB, publicdirs } = data + const msgDB = dbtools.GetDB(msgDbPath) + + const publicDir = publicdirs[0] + const uloadFiles = publicDir + 'chatFiles' + logger.Log(`Starting Socket.io Server on ${httpsServer ? 'https' : 'http'}`) + // https://socket.io/docs/v4/handling-cors/#Configuration + const io = new socket(httpsServer || httpServer, { + cors: { + credentials: true, + origin: true, + }, + }) + + function chatMessageRead(data) { + const { sender, reciever } = data + dbtools.runStatement( + msgDB, + `update msgs + set unread = 0 + where sender = ${sender} and reciever = ${reciever}`, + 'run' + ) + io.sockets.in(sender.toString()).emit('chat message read', { + userReadMsg: reciever, + }) + } + + io.use(socketAuth({ userDB: userDB })) + + io.on('connection', (socket: ExtendedSocket) => { + // TODO: UNCOMMENT + // ----------------------------------------------------------------- + // const userid = socket.user.id + // logger.Log(`Chat connect: ${userid}`, logger.GetColor('green')) + // ----------------------------------------------------------------- + + socket.on('join', function (data) { + // TODO: REMOVE + // ----------------------------------------------------------------- + const userid = parseInt(data.id) + logger.Log(`Chat connect: ${userid}`, logger.GetColor('green')) + // ----------------------------------------------------------------- + + socket.join(userid.toString()) + let currUser = dbtools.Select(msgDB, 'users', { + id: userid, + })[0] + if (!currUser) { + currUser = { + id: userid, + } + dbtools.Insert(msgDB, 'users', currUser) + } + + const groups = dbtools + .runStatement( + msgDB, + `select * from + ( + select sender as a + from msgs + where sender = ${userid} or reciever = ${userid} + union + select reciever + from msgs + where sender = ${userid} or reciever = ${userid} + )t + order by t.a asc` + ) + .reduce((acc, x) => { + if (x.a !== userid) acc.push(x.a) + return acc + }, []) + + socket.emit('prev messages', { + prevMsgs: groups.map((to) => { + const first = dbtools.runStatement( + msgDB, + `select * from msgs + where sender = ${userid} and reciever = ${to} or + sender = ${to} and reciever = ${userid} + order by date desc + limit 1` + )[0] + return first + }), + }) + + socket.on('get chat messages', (data) => { + const { chatPartner, from } = data + + const msgs = dbtools.runStatement( + msgDB, + `select * from msgs + where (sender = ${userid} and reciever = ${chatPartner} or + sender = ${chatPartner} and reciever = ${userid}) + ${from ? `and date < ${from}` : ''} + order by date desc + limit ${msgPaginationLimit}` + ) + + socket.emit('get chat messages', { + requestsdMsgs: msgs, + hasMore: msgs.length === msgPaginationLimit, + }) + + // Read update + chatMessageRead({ sender: chatPartner, reciever: userid }) + }) + + socket.on('chat message read', (data) => { + const { chatPartner } = data + chatMessageRead({ sender: chatPartner, reciever: userid }) + }) + + socket.on('chat message', (message) => { + const { reciever, msg, type } = message + const recieverUser = dbtools.Select(msgDB, 'users', { + id: reciever, + })[0] + if (!recieverUser) { + socket.emit('chat message', { + success: false, + date: new Date().getTime(), + sender: reciever, + reciever: userid, + type: 'text', + msg: `A #${reciever} számú felhasználó nem létezik`, + }) + return + } + + const msgObj = { + sender: userid, + reciever: parseInt(reciever), + msg: msg, + type: type || 'text', + date: new Date().getTime(), + unread: 1, + } + dbtools.Insert(msgDB, 'msgs', msgObj) + if (userid !== reciever) { + io.sockets.in(reciever.toString()).emit('chat message', msgObj) + } + }) + }) + + // socket.on('disconnect', () => {}) + // socket.on('close', () => {}) + }) + + app.post('/postchatfile', function (req: Request, res: any) { + logger.LogReq(req) + utils + .uploadFile(req, uloadFiles) + .then((result) => { + res.json({ + success: true, + path: result.filePath.replace(publicDir, ''), + }) + }) + .catch(() => { + res.json({ success: false, msg: 'error during uploading' }) + return + }) + }) + + app.get('/hasNewMsg', (req: Request, res) => { + let userid: any = req.query.userid + if (!userid || isNaN(userid)) { + res.json({ + success: false, + msg: 'Query "userid" (number) is required!', + }) + return + } + userid = parseInt(userid) + + const groups = dbtools + .runStatement( + msgDB, + `select * from + ( + select sender as a + from msgs + where sender = ${userid} or reciever = ${userid} + union + select reciever + from msgs + where sender = ${userid} or reciever = ${userid} + )t + order by t.a asc` + ) + .reduce((acc, x) => { + if (x.a !== userid) acc.push(x.a) + return acc + }, []) + + const prevMsgs = groups.map((to) => { + const first = dbtools.runStatement( + msgDB, + `select * from msgs + where sender = ${userid} and reciever = ${to} or + sender = ${to} and reciever = ${userid} + order by date desc + limit 1` + )[0] + return first + }) + + res.json({ + unreads: prevMsgs.reduce((acc, msg) => { + if (msg && msg.unread === 1 && msg.sender !== userid) { + acc.push(msg.sender) + } + return acc + }, []), + }) + }) +} + +export default { + setup: setup, +} diff --git a/src/modules/api/apiDBStruct.json b/src/modules/api/usersDBStruct.json similarity index 89% rename from src/modules/api/apiDBStruct.json rename to src/modules/api/usersDBStruct.json index fa5e7bc..84cc4c0 100644 --- a/src/modules/api/apiDBStruct.json +++ b/src/modules/api/usersDBStruct.json @@ -11,10 +11,6 @@ "notNull": true, "unique": true }, - "oldCID": { - "type": "text", - "unique": true - }, "notes": { "type": "text" }, @@ -40,10 +36,6 @@ "type": "number", "defaultZero": true }, - "pwGotFromCID": { - "type": "number", - "defaultZero": true - }, "createdBy": { "type": "number" } diff --git a/src/server.ts b/src/server.ts index e04ae2a..e6d2e3f 100755 --- a/src/server.ts +++ b/src/server.ts @@ -75,6 +75,8 @@ export interface SetupData { publicdirs: Array userDB?: any nextdir?: string + httpServer: any + httpsServer: any } if (!utils.FileExists(usersDBPath)) { @@ -128,7 +130,44 @@ function exit(reason) { process.exit() } +// https://certbot.eff.org/ +const privkeyFile = '/etc/letsencrypt/live/frylabs.net/privkey.pem' +const fullchainFile = '/etc/letsencrypt/live/frylabs.net/fullchain.pem' +const chainFile = '/etc/letsencrypt/live/frylabs.net/chain.pem' + +let certsLoaded = false +let certs +if ( + startHTTPS && + utils.FileExists(privkeyFile) && + utils.FileExists(fullchainFile) && + utils.FileExists(chainFile) +) { + try { + const key = utils.ReadFile(privkeyFile) + const cert = utils.ReadFile(fullchainFile) + const ca = utils.ReadFile(chainFile) + certs = { + key: key, + cert: cert, + ca: ca, + } + certsLoaded = true + } catch (err) { + logger.Log('Error loading cert files!', logger.GetColor('redbg')) + console.error(err) + } +} + const app = express() +const httpServer = http.createServer(app) +let httpsServer +if (certsLoaded) { + httpsServer = https.createServer(certs, app) + logger.Log('Listening on port: ' + httpsport + ' (https)') +} else { + logger.Log('Https not avaible') +} if (!process.env.NS_DEVEL) { app.use(function (req, res, next) { @@ -193,6 +232,8 @@ Object.keys(modules).forEach(function (key) { userDB: userDB, publicdirs: module.publicdirs, nextdir: module.nextdir, + httpServer: httpServer, + httpsServer: httpsServer, }) } @@ -208,35 +249,6 @@ Object.keys(modules).forEach(function (key) { } }) -// https://certbot.eff.org/ -const privkeyFile = '/etc/letsencrypt/live/frylabs.net/privkey.pem' -const fullchainFile = '/etc/letsencrypt/live/frylabs.net/fullchain.pem' -const chainFile = '/etc/letsencrypt/live/frylabs.net/chain.pem' - -let certsLoaded = false -let certs -if ( - startHTTPS && - utils.FileExists(privkeyFile) && - utils.FileExists(fullchainFile) && - utils.FileExists(chainFile) -) { - try { - const key = utils.ReadFile(privkeyFile) - const cert = utils.ReadFile(fullchainFile) - const ca = utils.ReadFile(chainFile) - certs = { - key: key, - cert: cert, - ca: ca, - } - certsLoaded = true - } catch (err) { - logger.Log('Error loading cert files!', logger.GetColor('redbg')) - console.error(err) - } -} - setLogTimer() function setLogTimer() { const now = new Date() @@ -320,12 +332,7 @@ if (isRoot) { logger.Log('Running as root', logger.GetColor('red')) } -const httpServer = http.createServer(app) httpServer.listen(port) -if (certsLoaded) { - const httpsServer = https.createServer(certs, app) +if (httpsServer) { httpsServer.listen(httpsport) - logger.Log('Listening on port: ' + httpsport + ' (https)') -} else { - logger.Log('Https not avaible') } diff --git a/src/standaloneUtils/dbSetup.js b/src/standaloneUtils/dbSetup.js index 6026597..feafed0 100644 --- a/src/standaloneUtils/dbSetup.js +++ b/src/standaloneUtils/dbSetup.js @@ -1,72 +1,41 @@ -const utils = require('../utils/utils.js') -const logger = require('../utils/logger.js') -const dbtools = require('../utils/dbtools.js') -const dbStructPath = '../modules/api/apiDBStruct.json' -const usersDBPath = '../data/dbs/users.db' -const { v4: uuidv4 } = require('uuid') +const utils = require('../../dist/utils/utils.js').default // eslint-disable-line +const logger = require('../../dist/utils/logger.js').default // eslint-disable-line +const dbtools = require('../../dist/utils/dbtools.js').default // eslint-disable-line +const { v4: uuidv4 } = require('uuid') // eslint-disable-line -let authDB +const dbStructPaths = [ + { structPath: '../modules/api/usersDBStruct.json', name: 'users.db' }, + { structPath: '../modules/api/msgsDbStruct.json', name: 'msgs.db' }, +] -console.clear() -CreateDB() +dbStructPaths.forEach((data) => { + const { structPath, name } = data + createDB(structPath, name) +}) -authDB.close() - -function CreateDB() { - const dbStruct = utils.ReadJSON(dbStructPath) - // authDB = dbtools.GetDB(':memory:') - authDB = dbtools.GetDB(usersDBPath) - authDB.pragma('synchronous = OFF') +function createDB(path, name) { + const dbStruct = utils.ReadJSON(path) + const db = dbtools.GetDB(`./${name}`) + db.pragma('synchronous = OFF') Object.keys(dbStruct).forEach((tableName) => { const tableData = dbStruct[tableName] dbtools.CreateTable( - authDB, + db, tableName, tableData.tableStruct, tableData.foreignKey ) }) - - try { - if (utils.FileExists('./ids')) { - const uids = utils.ReadFile('./ids').split('\n') - - uids.forEach((cid, i) => { - if (!cid) { - return - } - logger.Log(`[ ${i} / ${uids.length} ]`) - try { - dbtools.Insert(authDB, 'users', { - pw: uuidv4(), - oldCID: cid, - avaiblePWRequests: 4, - created: utils.GetDateString(), - }) - } catch (e) { - logger.Log('Error during inserting', logger.GetColor('redbg')) - console.error(e) - } - }) - } - } catch (e) { - console.error(e) - } - - const dir = `./dbSetupResult/${utils.GetDateString().replace(/ /g, '_')}` - utils.CreatePath(dir) - Object.keys(dbStruct).forEach((key) => { - const path = `${dir}/${key}.json` - logger.Log(`Writing ${path}...`) - utils.WriteFile( - JSON.stringify({ - tableInfo: dbtools.TableInfo(authDB, key), - tableRows: dbtools.SelectAll(authDB, key), - }), - path - ) - }) + printDb(db, dbStruct) + db.close() logger.Log('Done') } + +function printDb(db, dbStruct) { + Object.keys(dbStruct).forEach((key) => { + console.log(dbtools.TableInfo(db, key)) + console.log(dbtools.SelectAll(db, key)) + }) +} diff --git a/src/types/basicTypes.ts b/src/types/basicTypes.ts index 4a050df..fe75332 100644 --- a/src/types/basicTypes.ts +++ b/src/types/basicTypes.ts @@ -63,9 +63,9 @@ export interface User { id: number pw: string pwRequestCount: number - pwGotFromCID: number avaiblePWRequests: number loginCount: number + createdBy: number } export interface Request extends express.Request { @@ -81,5 +81,7 @@ export interface SubmoduleData { publicdirs: Array userDB?: any nextdir?: string + httpServer: any moduleSpecificData?: any + httpsServer?: any } diff --git a/src/utils/dbtools.ts b/src/utils/dbtools.ts index abe1b00..13c1454 100644 --- a/src/utils/dbtools.ts +++ b/src/utils/dbtools.ts @@ -2,16 +2,17 @@ // https://github.com/JoshuaWise/better-sqlite3/blob/HEAD/docs/api.md export default { - GetDB, - AddColumn, - TableInfo, - Update, - Delete, - CreateTable, - SelectAll, - Select, - Insert, - CloseDB, + GetDB: GetDB, + AddColumn: AddColumn, + TableInfo: TableInfo, + Update: Update, + Delete: Delete, + CreateTable: CreateTable, + SelectAll: SelectAll, + Select: Select, + Insert: Insert, + CloseDB: CloseDB, + runStatement: runStatement, } import Sqlite from 'better-sqlite3' @@ -21,7 +22,7 @@ import utils from '../utils/utils' const debugLog = process.env.NS_SQL_DEBUG_LOG // { asd: 'asd', basd: 4 } => asd = 'asd', basd = 4 -function GetSqlQuerry(conditions, type) { +function GetSqlQuerry(conditions: any, type: string, joiner?: string) { const res = Object.keys(conditions).reduce((acc, key) => { const item = conditions[key] if (typeof item === 'string') { @@ -32,7 +33,11 @@ function GetSqlQuerry(conditions, type) { return acc }, []) if (type === 'where') { - return res.join(' AND ') + if (joiner) { + return res.join(` ${joiner} `) + } else { + return res.join(' AND ') + } } else { return res.join(', ') } @@ -158,7 +163,7 @@ function CreateTable(db: any, name: any, columns: any, foreignKeys: any): any { } // IF NOT EXISTS - const command = `CREATE TABLE ${name}(${cols}${fKeys.join(', ')})` + const command = `CREATE TABLE ${name}(${cols}${fKeys.join(' ')})` const stmt = PrepareStatement(db, command) return stmt.run() } catch (err) { @@ -177,13 +182,21 @@ function SelectAll(db: any, from: any): any { } } -function Select(db: any, from: any, conditions: any): any { +// SELECT * FROM MyTable WHERE SomeColumn > LastValue ORDER BY SomeColumn LIMIT 100; +function Select(db: any, from: any, conditions: any, options: any = {}): any { + const { joiner, limit } = options + try { - const command = `SELECT * from ${from} WHERE ${GetSqlQuerry( + let command = `SELECT * from ${from} WHERE ${GetSqlQuerry( conditions, - 'where' + 'where', + joiner )}` + if (!isNaN(limit)) { + command += ` LIMIT ${limit}` + } + const stmt = PrepareStatement(db, command) return stmt.all() } catch (err) { @@ -221,6 +234,15 @@ function Insert(db: any, table: any, data: any): any { } } +function runStatement(db: any, command: string, runType?: string): any { + const stmt = PrepareStatement(db, command) + if (!runType) { + return stmt.all() + } else if (runType === 'run') { + return stmt.run() + } +} + function CloseDB(db: any): void { db.close((err) => { if (err) { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 30b697c..cb14115 100755 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -206,7 +206,6 @@ function uploadFile(req: Request, path: string): Promise { const temp = file.name.split('.') const extension = temp.pop() fileName = temp.join('.') + '_' + id + '.' + extension - console.log(fileName) fileDestination = path + '/' + fileName } diff --git a/submodules/moodle-test-userscript b/submodules/moodle-test-userscript index 00ceb74..27211c7 160000 --- a/submodules/moodle-test-userscript +++ b/submodules/moodle-test-userscript @@ -1 +1 @@ -Subproject commit 00ceb74ba763ebbca1a3edc8f0fc682e59f214a3 +Subproject commit 27211c7bc83e0e930fabc430ffb5615b52523106 diff --git a/submodules/qmining-page b/submodules/qmining-page index 5d233f5..602e160 160000 --- a/submodules/qmining-page +++ b/submodules/qmining-page @@ -1 +1 @@ -Subproject commit 5d233f549dbaf3dd608481b3dddda67e41d4bea0 +Subproject commit 602e16046e6600a405ae68814c11349fde745244