From bf9ae0b9e1e902dff544f1cfee6c37f982c1a0e7 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 27 Nov 2022 19:57:35 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81?= =?UTF-8?q?=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0=D0=BC=D0=B8=20=D1=81?= =?UTF-8?q?=D0=B5=D1=81=D1=81=D0=B8=D0=B9=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/controllers/WebSocketController.js | 73 +++-------- server/core/WebAccess.js | 148 ++++++++++++++++++++++ server/index.js | 6 +- 3 files changed, 174 insertions(+), 53 deletions(-) create mode 100644 server/core/WebAccess.js diff --git a/server/controllers/WebSocketController.js b/server/controllers/WebSocketController.js index fd4f11d..6f88cf0 100644 --- a/server/controllers/WebSocketController.js +++ b/server/controllers/WebSocketController.js @@ -6,18 +6,15 @@ const WebWorker = require('../core/WebWorker');//singleton const log = new (require('../core/AppLogger'))().log;//singleton const utils = require('../core/utils'); -const cleanPeriod = 1*60*1000;//1 минута, не менять! -const cleanUnusedTokenTimeout = 5*60*1000;//5 минут +const cleanPeriod = 1*60*1000;//1 минута const closeSocketOnIdle = 5*60*1000;//5 минут class WebSocketController { - constructor(wss, config) { + constructor(wss, webAccess, config) { this.config = config; this.isDevelopment = (config.branch == 'development'); - this.freeAccess = (config.accessPassword === ''); - this.accessTimeout = config.accessTimeout*60*1000; - this.accessMap = new Map(); + this.webAccess = webAccess; this.workerState = new WorkerState(); this.webWorker = new WebWorker(config); @@ -34,53 +31,28 @@ class WebSocketController { }); }); - setTimeout(() => { this.periodicClean(); }, cleanPeriod); + this.periodicClean();//no await } - periodicClean() { - try { - const now = Date.now(); + async periodicClean() { + while (1) {//eslint-disable-line no-constant-condition + try { + const now = Date.now(); - //почистим accessMap - if (!this.freeAccess) { - for (const [accessToken, accessRec] of this.accessMap) { - if ( !(accessRec.used > 0 || now - accessRec.time < cleanUnusedTokenTimeout) - || !(this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout) - ) { - this.accessMap.delete(accessToken); + //почистим ws-клиентов + this.wss.clients.forEach((ws) => { + if (!ws.lastActivity || now - ws.lastActivity > closeSocketOnIdle - 50) { + ws.terminate(); } - } + }); + } catch(e) { + log(LM_ERR, `WebSocketController.periodicClean error: ${e.message}`); } - - //почистим ws-клиентов - this.wss.clients.forEach((ws) => { - if (!ws.lastActivity || now - ws.lastActivity > closeSocketOnIdle - 50) { - ws.terminate(); - } - }); - } finally { - setTimeout(() => { this.periodicClean(); }, cleanPeriod); + + await utils.sleep(cleanPeriod); } } - hasAccess(accessToken) { - if (this.freeAccess) - return true; - - const accessRec = this.accessMap.get(accessToken); - if (accessRec) { - const now = Date.now(); - - if (this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout) { - accessRec.used++; - accessRec.time = now; - return true; - } - } - - return false; - } - async onMessage(ws, message) { let req = {}; try { @@ -96,12 +68,9 @@ class WebSocketController { this.send({_rok: 1}, req, ws); //access - if (!this.hasAccess(req.accessToken)) { + if (!this.webAccess.hasAccess(req.accessToken)) { await utils.sleep(500); - const salt = utils.randomHexString(32); - const accessToken = utils.getBufHash(this.config.accessPassword + salt, 'sha256', 'hex'); - this.accessMap.set(accessToken, {time: Date.now(), used: 0}); - + const salt = this.webAccess.newToken(); this.send({error: 'need_access_token', salt}, req, ws); return; } @@ -163,14 +132,14 @@ class WebSocketController { } async logout(req, ws) { - this.accessMap.delete(req.accessToken); + await this.webAccess.deleteAccess(req.accessToken); this.send({success: true}, req, ws); } async getConfig(req, ws) { const config = _.pick(this.config, this.config.webConfigParams); config.dbConfig = await this.webWorker.dbConfig(); - config.freeAccess = this.freeAccess; + config.freeAccess = this.webAccess.freeAccess; this.send(config, req, ws); } diff --git a/server/core/WebAccess.js b/server/core/WebAccess.js new file mode 100644 index 0000000..6c05b37 --- /dev/null +++ b/server/core/WebAccess.js @@ -0,0 +1,148 @@ +const { JembaDbThread } = require('jembadb'); +const utils = require('../core/utils'); +const log = new (require('../core/AppLogger'))().log;//singleton + +const cleanPeriod = 1*60*1000;//1 минута +const cleanUnusedTokenTimeout = 5*60*1000;//5 минут + +class WebAccess { + constructor(config) { + this.config = config; + + this.freeAccess = (config.accessPassword === ''); + this.accessTimeout = config.accessTimeout*60*1000; + this.accessMap = new Map(); + + setTimeout(() => { this.periodicClean(); }, cleanPeriod); + } + + async init() { + const config = this.config; + const dbPath = `${config.dataDir}/web-access`; + const db = new JembaDbThread();//в отдельном потоке + await db.lock({ + dbPath, + create: true, + softLock: true, + + tableDefaults: { + cacheSize: config.dbCacheSize, + }, + }); + + try { + //открываем таблицы + await db.openAll(); + } catch(e) { + if ( + e.message.indexOf('corrupted') >= 0 + || e.message.indexOf('Unexpected token') >= 0 + || e.message.indexOf('invalid stored block lengths') >= 0 + ) { + log(LM_ERR, `DB ${dbPath} corrupted`); + log(`Open "${dbPath}" with auto repair`); + await db.openAll({autoRepair: true}); + } else { + throw e; + } + } + + //проверим, можно ли загружать токены из таблицы access + const pass = utils.getBufHash(this.config.accessPassword, 'sha256', 'hex'); + await db.create({table: 'config', quietIfExists: true}); + let rows = await db.select({table: 'config', where: `@@id('pass')`}); + + let loadMap = false; + if (rows.length && rows[0].value === pass) { + //пароль не сменился в конфиге, можно загружать токены + loadMap = true; + } else { + await db.insert({table: 'config', replace: true, rows: [{id: 'pass', value: pass}]}); + } + + await db.create({table: 'access', quietIfExists: true}); + + if (loadMap) { + //загрузим токены сессий + rows = await db.select({table: 'access'}); + + for (const row of rows) + this.accessMap.set(row.id, row.value); + } + + this.db = db; + } + + async periodicClean() { + while (1) {//eslint-disable-line no-constant-condition + try { + const now = Date.now(); + + //почистим accessMap + if (!this.freeAccess) { + for (const [accessToken, accessRec] of this.accessMap) { + if ( !(accessRec.used > 0 || now - accessRec.time < cleanUnusedTokenTimeout) + || !(this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout) + ) { + await this.deleteAccess(accessToken); + } else if (!accessRec.saved) { + await this.saveAccess(accessToken); + } + } + } + + } catch(e) { + log(LM_ERR, `WebAccess.periodicClean error: ${e.message}`); + } + + await utils.sleep(cleanPeriod); + } + } + + hasAccess(accessToken) { + if (this.freeAccess) + return true; + + const accessRec = this.accessMap.get(accessToken); + if (accessRec) { + const now = Date.now(); + + if (this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout) { + accessRec.used++; + accessRec.time = now; + accessRec.saved = false; + return true; + } + } + + return false; + } + + async deleteAccess(accessToken) { + await this.db.delete({table: 'access', where: `@@id(${this.db.esc(accessToken)})`}); + this.accessMap.delete(accessToken); + } + + async saveAccess(accessToken) { + const value = this.accessMap.get(accessToken); + if (!value || value.saved) + return; + + value.saved = true; + await this.db.insert({ + table: 'access', + replace: true, + rows: [{id: accessToken, value}] + }); + } + + newToken() { + const salt = utils.randomHexString(32); + const accessToken = utils.getBufHash(this.config.accessPassword + salt, 'sha256', 'hex'); + this.accessMap.set(accessToken, {time: Date.now(), used: 0}); + + return salt; + } +} + +module.exports = WebAccess; \ No newline at end of file diff --git a/server/index.js b/server/index.js index 0bc984b..33ea96e 100644 --- a/server/index.js +++ b/server/index.js @@ -158,8 +158,12 @@ async function main() { opds(app, config); initStatic(app, config); + const WebAccess = require('./core/WebAccess'); + const webAccess = new WebAccess(config); + await webAccess.init(); + const { WebSocketController } = require('./controllers'); - new WebSocketController(wss, config); + new WebSocketController(wss, webAccess, config); if (devModule) { devModule.logErrors(app);