From c764c4f402b0011de32f02a9cf657a625e50b773 Mon Sep 17 00:00:00 2001 From: MrFry Date: Tue, 7 Apr 2020 14:09:34 +0200 Subject: [PATCH] Exit cleanup functions, authentication polish, db polish --- middlewares/auth.middleware.js | 26 +++-- middlewares/reqlogger.middleware.js | 17 ++- modules/api/api.js | 138 +++++++++++++++++++++- modules/api/apiDBStruct.json | 93 ++++++++++++--- modules/api/views/man.ejs | 172 ---------------------------- modules/dataEditor/dataEditor.js | 1 - server.js | 46 +++++++- utils/classes.js | 1 - utils/dbSetup.js | 18 ++- utils/dbtools.js | 31 +++-- 10 files changed, 314 insertions(+), 229 deletions(-) delete mode 100755 modules/api/views/man.ejs diff --git a/middlewares/auth.middleware.js b/middlewares/auth.middleware.js index 700b267..6e42c98 100644 --- a/middlewares/auth.middleware.js +++ b/middlewares/auth.middleware.js @@ -1,13 +1,8 @@ const logger = require('../utils/logger.js') const dbtools = require('../utils/dbtools.js') -const exceptions = [ - 'favicon', - '/login' -] - module.exports = function (options) { - const { authDB, jsonResponse } = options + const { authDB, jsonResponse, exceptions } = options const renderLogin = (res) => { if (jsonResponse) { @@ -54,7 +49,7 @@ module.exports = function (options) { logger.DebugLog(`ID #${user.id}: ${req.url}`, 'auth', 1) - // TODO: add stat to acesses table + UpdateAccess(authDB, user, ip, sessionID) dbtools.Update(authDB, 'sessions', { lastAccess: new Date().toString() @@ -73,10 +68,25 @@ module.exports = function (options) { } } +function UpdateAccess (db, user, ip, sessionID) { + const accesses = dbtools.Select(db, 'accesses', { + userId: user.id, + ip: ip + }) + + if (accesses.length === 0) { + dbtools.Insert(db, 'accesses', { + userID: user.id, + ip: ip, + sessionID: sessionID, + date: new Date().toString() + }) + } +} + function GetUserBySessionID (db, sessionID, req) { logger.DebugLog(`Getting user from db`, 'auth', 2) - // TODO: check same ip? const session = dbtools.Select(db, 'sessions', { id: sessionID })[0] diff --git a/middlewares/reqlogger.middleware.js b/middlewares/reqlogger.middleware.js index 609dbed..e016140 100644 --- a/middlewares/reqlogger.middleware.js +++ b/middlewares/reqlogger.middleware.js @@ -1,9 +1,8 @@ const logger = require('../utils/logger.js') -// TODO: use this middleware in all modules - module.exports = function (options) { const loggableKeywords = options ? options.loggableKeywords : undefined + const loggableModules = options ? options.loggableModules : undefined return function (req, res, next) { res.on('finish', function () { @@ -13,15 +12,15 @@ module.exports = function (options) { const ip = req.headers['cf-connecting-ip'] || req.connection.remoteAddress const hostname = req.hostname.replace('www.', '').split('.')[0] - // TODO: merge req.url and req.hostname checking - // TODO: regexp includes checking - let toLog = loggableKeywords && loggableKeywords.some((x) => { + + // fixme: regexp includes checking + const hasLoggableKeyword = loggableKeywords && loggableKeywords.some((x) => { return req.url.includes(x) }) - - if (hostname.includes('dataeditor')) { - toLog = true - } + const hasLoggableModule = loggableModules && loggableModules.some((x) => { + return hostname.includes(x) + }) + const toLog = hasLoggableModule || hasLoggableKeyword logger.LogReq(req, true, res.statusCode) if (toLog) { logger.LogReq(req) } diff --git a/modules/api/api.js b/modules/api/api.js index e4e0789..21b72cf 100644 --- a/modules/api/api.js +++ b/modules/api/api.js @@ -46,6 +46,8 @@ const dataEditsLog = 'stats/dataEdits' const dailyDataCountFile = 'stats/dailyDataCount' const usersDBPath = 'data/dbs/users.db' +const maxVeteranPwGetCount = 5 + if (!utils.FileExists(usersDBPath)) { throw new Error('No user DB exists yet! please run utils/dbSetup.js first!') } @@ -67,7 +69,12 @@ app.set('views', [ ]) app.use(auth({ authDB: authDB, - jsonResponse: true + jsonResponse: true, + exceptions: [ + 'favicon', + '/login', + '/getveteranpw' + ] })) app.use(express.static('public')) app.use(busboy({ @@ -106,6 +113,118 @@ Load() // ------------------------------------------------------------- +app.post('/getpw', function (req, res) { + logger.LogReq(req) + + const requestingUser = req.session.user + + if (requestingUser.avaiblePWRequests <= 0) { + res.json({ + result: 'error', + msg: 'Too many passwords requested or cant request password yet, try later' + }) + logger.Log(`User #${requestingUser.id} requested too much passwords`, logger.GetColor('cyan')) + return + } + + dbtools.Update(authDB, 'users', { + avaiblePWRequests: requestingUser.avaiblePWRequests - 1, + pwRequestCount: requestingUser.pwRequestCount + 1 + }, { + id: requestingUser.id + }) + + const pw = uuidv4() + const insertRes = dbtools.Insert(authDB, 'users', { + pw: pw, + created: new Date().toString() + }) + + logger.Log(`User #${requestingUser.id} creted new user #${insertRes.lastInsertRowid}`, logger.GetColor('cyan')) + + console.log(requestingUser) + + res.json({ + result: 'success', + pw: pw, + remaining: requestingUser.avaiblePWRequests - 1 + }) +}) + +app.post('/getveteranpw', function (req, res) { + logger.LogReq(req) + const ip = req.headers['cf-connecting-ip'] || req.connection.remoteAddress + + const tries = dbtools.Select(authDB, 'veteranPWRequests', { + ip: ip + })[0] + + if (tries) { + if (tries.count > maxVeteranPwGetCount) { + res.json({ + result: 'error', + msg: 'Too many tries' + }) + logger.Log(`Too many veteran PW requests from ${ip}!`, logger.GetColor('cyan')) + return + } else { + dbtools.Update(authDB, 'veteranPWRequests', { + count: tries.count + 1, + lastDate: new Date().toString() + }, { + id: tries.id + }) + } + } else { + dbtools.Insert(authDB, 'veteranPWRequests', { + ip: ip, + lastDate: new Date().toString() + }) + } + + const oldUserID = req.body.cid + if (!oldUserID) { + res.json({ + result: 'error', + msg: 'No CID recieved' + }) + logger.Log(`No client ID recieved`, logger.GetColor('cyan')) + return + } + + const user = dbtools.Select(authDB, 'users', { + oldCID: oldUserID + })[0] + + if (user) { + if (user.pwGotFromCID === 0) { + logger.Log(`Sent password to veteran user #${user.id}`, logger.GetColor('cyan')) + dbtools.Update(authDB, 'users', { + pwGotFromCID: 1 + }, { + id: user.id + }) + + res.json({ + result: 'success', + pw: user.pw + }) + } else { + logger.Log(`Veteran user #${user.id} already requested password`, logger.GetColor('cyan')) + res.json({ + result: 'error', + msg: 'Password already requested once' + }) + } + } else { + logger.Log(`Invalid password request with CID: ${oldUserID}`, logger.GetColor('cyan')) + res.json({ + result: 'error', + msg: 'no such CID' + }) + } +}) + app.post('/login', (req, res) => { logger.LogReq(req) const pw = req.body.pw @@ -430,15 +549,26 @@ app.post('*', function (req, res) { res.status(404).render('404') }) -exports.app = app -exports.dailyAction = () => { +function ExportDailyDataCount () { utils.AppendToFile(JSON.stringify({ date: new Date(), subjectCount: data.Subjects.length, questionCOunt: data.Subjects.reduce((acc, subj) => { return acc + subj.Questions.length - }, 0) + }, 0), + userCount: dbtools.TableInfo(authDB, 'users').dataCount }), dailyDataCountFile) } +exports.app = app +exports.cleanup = () => { + logger.Log('Closing Auth DB') + authDB.close() +} +exports.dailyAction = () => { + ExportDailyDataCount() + + // TODO: selectAll from users, check if date is more than x, and increment every y +} + logger.Log('API module started', logger.GetColor('yellow')) diff --git a/modules/api/apiDBStruct.json b/modules/api/apiDBStruct.json index d810509..1a10e8a 100644 --- a/modules/api/apiDBStruct.json +++ b/modules/api/apiDBStruct.json @@ -12,7 +12,8 @@ "unique": true }, "oldCID": { - "type": "text" + "type": "text", + "unique": true }, "lastIP": { "type": "text" @@ -24,24 +25,42 @@ "type": "number", "defaultZero": true }, + "created": { + "type": "text", + "notNull": true + }, "lastLogin": { "type": "text" }, "lastAccess": { "type": "text" + }, + "avaiblePWRequests": { + "type": "number", + "defaultZero": true + }, + "pwRequestCount": { + "type": "number", + "defaultZero": true + }, + "pwGotFromCID": { + "type": "number", + "defaultZero": true } } }, "sessions": { - "foreignKey": { - "keysFrom": [ - "userID" - ], - "table": "users", - "keysTo": [ - "id" - ] - }, + "foreignKey": [ + { + "keysFrom": [ + "userID" + ], + "table": "users", + "keysTo": [ + "id" + ] + } + ], "tableStruct": { "id": { "type": "text", @@ -65,18 +84,60 @@ } } }, - "acesses": { + "accesses": { + "foreignKey": [ + { + "keysFrom": [ + "userID" + ], + "table": "users", + "keysTo": [ + "id" + ] + } + ], "tableStruct": { - "accessId": { - "type": "number", + "accessID": { + "type": "integer", "primary": true, + "autoIncrement": true + }, + "userID": { + "type": "number", "notNull": true }, - "userId": { - "type": "number" + "ip": { + "type": "text", + "notNull": true + }, + "date": { + "type": "text", + "notNull": true + }, + "sessionID": { + "type": "text", + "notNull": true + } + } + }, + "veteranPWRequests": { + "tableStruct": { + "id": { + "type": "integer", + "primary": true, + "autoIncrement": true }, "ip": { - "type": "text" + "type": "text", + "notNull": true + }, + "count": { + "type": "number", + "defaultZero": true + }, + "lastDate": { + "type": "text", + "notNull": true } } } diff --git a/modules/api/views/man.ejs b/modules/api/views/man.ejs deleted file mode 100755 index db1a489..0000000 --- a/modules/api/views/man.ejs +++ /dev/null @@ -1,172 +0,0 @@ -
-

Moodle/Elearnig/KMOOC manual

-
-
- Ez a userscript Moodle/Elearnig/KMOOC tesztek megoldása során segítséget jelenít meg. -
-

-A válasz ablakban jobb felül lévő százalék jelzi, hogy mekkora eséllyel jó a megoldás. Ez -sokszor jó viszonyítás, de semmi sem biztos! Bármikor előfordulhat, hogy nem jó a -megjelenített válasz! Ezért csak saját felelősségedre használd! Sok kikerülhetetlen -hibalehetőség van, amit egyszerű nem lehet scriptben lekezelni (Pl rosszul megadott kérdés -tanár részéről). Kézzel is lehet keresni a elmentett kérdések között. Ezért mindig -legyen egy letöltött verziód a kérdésekről, mert nem 100% hogy mindég elérhető a szerver! -Továbbá ha a moodle oldalán a DOM megváltozik, a script nem fog működni! Ez nem annyira -gyakori, de bármikor megtörténhet! Érdemes nem kikapcsolni a tampermonkey-ban a userscript -frissítést. Ez nem windows update, itt tényleg hibajavítások jönnek ki. Hiba, észrevétel -esetén : Script Feedback (ezt -gyakran még aznap megnézem.) -

-

-Továbbá ez a userscript HTTP requestekket küldhet egy szerver felé, ahova az összes megoldott -tesztjeid kérdéseit és (helyes)válaszait feltölti! Ezzel garantálja, hogy neked, és mindenki -másnak a legfrissebb adatok állnak rendelkezésre. -
- -

Tartalomjegyzék

-
-
- -
-

Használat

-
-
- - - -
-

Először is tölts le egy userscript futtató kiegészítőt a böngésződhöz. Én Tampermonkeyt használok, és ezzel van tesztelve a - userscript is, ezért ez ajánlott. Más is működhet (violentmonkey, etc), de az nem garantált. - Majd a weboldalról egy kattintással elvileg - le tudod tölteni a scriptet, és elvileg kész is. Script majd udvariasan megkéri, hogy - hagy beszélgessen a szerverrel, mert mással nem tud. -

-

Teszt közben még több dolog történhet: -

-
    -
  • Nem jó kérdésre ad választ a script: Ilyenkor az van, hogy nincs meg a - kérdés, vagy több hasonló kérdés/válasz van. Ilyenkor a jobbra/balra gombbal - váltogathatsz azok a kérdés/válasz combók közül, amit talált a script
  • - -
  • Több teszt kérdés van egy oldalon: Fel le gombbal váltogathatsz a kérdések között. - Ilyenkor is működik az előbb említett funkció. Az indexek, amit kiír a bal felső sarokban: - aktuális kérdés száma / aktuális találat száma.
  • - -
  • Nem jelenik meg semmi, vagy nem működik a script: Megesik az ilyesmi. Ha - a webszerver még elérhető akkor ott meg bírod nézni a kérdéseket, és ott lehet - keresgélni Ctrl + F -el Ha az sincs, akkor lehet hogy jól jön ha van egy - lementett kérdés gyűjteményed.
  • -
- -

Egyéb funkciók: -
    -
  • - Ha esetleg videókat nézel, akkor spaceval lehet play/pausolni, és jobbra/balra - gombbal ugrani a videóban. -
  • -
  • - Ha bármikor nem kell a script, akkor a menü gomb alatt bekapcsolhatod a passzív - módot, ami nem piszkálja a szervert. Vagy kikapcsolhatod magát a scriptet - tampermonkey-ban. Ha bármiért is el akarod tüntetni a következő oldalig az éppen - megjelenő script ablakot, akkor középső egér gombbal kattintva rajta ezt - megteheted. -
  • -
- -

Ha 2.0 előttről jöttél, és rettenetesen össze vagy zavarodva: -

- 2.0 előtt a script az egész adatbázist leszedte, beolvasta, és onnan keresett. Ez a - keresés most szerver oldalon van megvalósítva, és a script csak a kérdést, hozzá tartózó - egyéb infót (pl kép nevek) és a tárgy nevét küldi el szervernek. Ezután az visszaküldi a - helyes válaszokat. - -
- img -
-
-

Eddigi teszt kérdések:

-
-
-Eddigi összes kérdés -Továbbá ez még arra jó, hogy ha valamiért bugos a script, akkor itt tudsz ctrl-f el nézegetni, -vagy ha lemented az összes kérdést, akkor még akkor is biztonságban vagy, ha netán leáll a -szerver, vagy elmegy a neted. Bár úgy nehezen moodlezel, de mind1 - -
-

Gyakran előforduló kérdések

-
-
- -
Jogosultságok: -
GM_openInTab: help megnyitása új lapon, GM_xmlhttpRequest: online adatbázishoz. GM_info: a -scriptről információ, a verzióváltozás érzékeléséhez. GM_getValue/ GM_setValue: oldal -bezárásakor megmaradó változók kezelése. Előző verzió tárolására, ugyanúgy verzióváltozás -érzékeléséhez, néhány beállítás, illetve hogy melyik tárgyakból keressen kérdéseket. Ezek -függvények, és a sciptben néhol meg vannak hívva, keresd meg. -

Elküldött adatok online módban: Minden teszt végén az összes kérdés, és rá a moodle szerint -helyesnek vélt válaszok. Fogadott adatok: az összes eddig ismert moodle kérdés -
-
-

-Weboldal -

-
- diff --git a/modules/dataEditor/dataEditor.js b/modules/dataEditor/dataEditor.js index 36e1422..a3adc7a 100644 --- a/modules/dataEditor/dataEditor.js +++ b/modules/dataEditor/dataEditor.js @@ -71,7 +71,6 @@ AddHtmlRoutes(utils.ReadDir('modules/dataEditor/public')) // -------------------------------------------------------------- app.get('/', function (req, res) { - // TODO: log this, regexp $/^ res.end('hai') logger.LogReq(req) }) diff --git a/server.js b/server.js index fdcffe3..9f1a06c 100755 --- a/server.js +++ b/server.js @@ -52,12 +52,40 @@ try { console.log(e) } +// Setting up exits +// process.on('exit', () => exit('exit')) +// process.on('exit', () => exit('exit')) +process.on('SIGINT', () => exit('SIGINT')) +process.on('SIGINT', () => exit('SIGINT')) +process.on('SIGTERM', () => exit('SIGTERM')) +process.on('SIGTERM', () => exit('SIGTERM')) + +function exit (reason) { + logger.Log(`Exiting, reason: ${reason}`) + Object.keys(modules).forEach((k, i) => { + const x = modules[k] + if (x.cleanup) { + try { + x.cleanup() + } catch (e) { + logger.Log(`Error in ${k} cleanup! Details in STDERR`, logger.GetColor('redbg')) + console.err(e) + } + } + }) + process.exit() +} + const app = express() app.use(cors()) -app.use(reqlogger([ - 'stable.user.js', // TODO - 'dataeditor' -])) +app.use(reqlogger({ + loggableKeywords: [ + 'stable.user.js' + ], + loggableModules: [ + 'dataeditor' + ] +})) Object.keys(modules).forEach(function (k, i) { let x = modules[k] @@ -65,11 +93,12 @@ Object.keys(modules).forEach(function (k, i) { let mod = require(x.path) if (mod.setup) { mod.setup({ - url: 'http://' + x.urls[0] // TODO http https or neither + url: 'https://' + x.urls[0] }) } x.app = mod.app x.dailyAction = mod.dailyAction + x.cleanup = mod.cleanup x.urls.forEach((url) => { app.use(vhost(url, x.app)) }) @@ -127,7 +156,12 @@ function setLogTimer () { Object.keys(modules).forEach((k, i) => { const x = modules[k] if (x.dailyAction) { - x.dailyAction() + try { + x.dailyAction() + } catch (e) { + logger.Log(`Error in ${k} daily action! Details in STDERR`, logger.GetColor('redbg')) + console.err(e) + } } }) diff --git a/utils/classes.js b/utils/classes.js index 5006f63..d173010 100755 --- a/utils/classes.js +++ b/utils/classes.js @@ -270,7 +270,6 @@ class Question { return SUtils.CompareString(this.A, qObj.A) } - // TODO: return q / a / data match for debuging Compare (q2, data) { assert(q2) let qObj diff --git a/utils/dbSetup.js b/utils/dbSetup.js index ae86647..54bf143 100644 --- a/utils/dbSetup.js +++ b/utils/dbSetup.js @@ -11,9 +11,22 @@ let authDB console.clear() CreateDB() +// authDB.backup(usersDBPath) +// .then(() => { +// logger.Log('backup complete!') +// }) +// .catch((err) => { +// logger.Log('backup failed!', logger.GetColor('redbg')) +// console.log(err) +// }) + +authDB.close() + function CreateDB () { const dbStruct = utils.ReadJSON(dbStructPath) + // authDB = dbtools.GetDB(':memory:') authDB = dbtools.GetDB(usersDBPath) + authDB.pragma('synchronous = OFF') Object.keys(dbStruct).forEach((tableName) => { const tableData = dbStruct[tableName] @@ -24,11 +37,14 @@ function CreateDB () { const uids = utils.ReadFile('../dbUsers/keys').split('\n') uids.forEach((cid, i) => { + if (!cid) { return } logger.Log(`[ ${i} / ${uids.length} ]`) try { dbtools.Insert(authDB, 'users', { pw: uuidv4(), - oldCID: cid + oldCID: cid, + avaiblePWRequests: 4, + created: new Date().toString() }) } catch (e) { logger.Log('Error during inserting', logger.GetColor('redbg')) diff --git a/utils/dbtools.js b/utils/dbtools.js index 30b6365..fcabba6 100644 --- a/utils/dbtools.js +++ b/utils/dbtools.js @@ -21,8 +21,8 @@ const utils = require('../utils/utils.js') const debugLog = process.env.NS_SQL_DEBUG_LOG // { asd: 'asd', basd: 4 } => asd = 'asd', basd = 4 -function GetSqlQuerry (conditions) { - return Object.keys(conditions).reduce((acc, key) => { +function GetSqlQuerry (conditions, type) { + const res = Object.keys(conditions).reduce((acc, key) => { const item = conditions[key] if (typeof item === 'string') { acc.push(`${key} = '${conditions[key]}'`) @@ -30,14 +30,21 @@ function GetSqlQuerry (conditions) { acc.push(`${key} = ${conditions[key]}`) } return acc - }, []).join(', ') + }, []) + if (type === 'where') { + return res.join(' AND ') + } else { + return res.join(', ') + } } // ------------------------------------------------------------------------- function GetDB (path) { utils.CreatePath(path) - return new Sqlite(path) + const res = new Sqlite(path) + res.pragma('synchronous = OFF') + return res } function DebugLog (msg) { @@ -87,7 +94,7 @@ function TableInfo (db, table) { function Update (db, table, newData, conditions) { try { - const s = `UPDATE ${table} SET ${GetSqlQuerry(newData)} WHERE ${GetSqlQuerry(conditions)}` + const s = `UPDATE ${table} SET ${GetSqlQuerry(newData, 'set')} WHERE ${GetSqlQuerry(conditions, 'where')}` DebugLog(s) const stmt = db.prepare(s) @@ -99,7 +106,7 @@ function Update (db, table, newData, conditions) { function Delete (db, table, conditions) { try { - const s = `DELETE FROM ${table} WHERE ${GetSqlQuerry(conditions)}` + const s = `DELETE FROM ${table} WHERE ${GetSqlQuerry(conditions, 'where')}` DebugLog(s) const stmt = db.prepare(s) @@ -137,14 +144,16 @@ function CreateTable (db, name, columns, foreignKeys) { return acc }, []).join(', ') - let fKeys = '' + let fKeys = [] if (foreignKeys) { - const { keysFrom, table, keysTo } = foreignKeys - fKeys = `, FOREIGN KEY(${keysFrom.join(', ')}) REFERENCES ${table}(${keysTo.join(', ')})` + foreignKeys.forEach((f) => { + const { keysFrom, table, keysTo } = f + fKeys.push(`, FOREIGN KEY(${keysFrom.join(', ')}) REFERENCES ${table}(${keysTo.join(', ')})`) + }) } // IF NOT EXISTS - const s = `CREATE TABLE ${name}(${cols}${fKeys})` + const s = `CREATE TABLE ${name}(${cols}${fKeys.join(', ')})` DebugLog(s) const stmt = db.prepare(s) @@ -168,7 +177,7 @@ function SelectAll (db, from) { function Select (db, from, conditions) { try { - const s = `SELECT * from ${from} WHERE ${GetSqlQuerry(conditions)}` + const s = `SELECT * from ${from} WHERE ${GetSqlQuerry(conditions, 'where')}` DebugLog(s) const stmt = db.prepare(s)