WebAccess.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. const { JembaDbThread } = require('jembadb');
  2. const utils = require('../core/utils');
  3. const log = new (require('../core/AppLogger'))().log;//singleton
  4. const asyncExit = new (require('./AsyncExit'))();
  5. const cleanPeriod = 1*60*1000;//1 минута
  6. const cleanUnusedTokenTimeout = 5*60*1000;//5 минут
  7. class WebAccess {
  8. constructor(config) {
  9. this.config = config;
  10. this.freeAccess = (config.accessPassword === '');
  11. this.accessTimeout = config.accessTimeout*60*1000;
  12. this.accessMap = new Map();
  13. asyncExit.add(this.closeDb.bind(this));
  14. setTimeout(() => { this.periodicClean(); }, cleanPeriod);
  15. }
  16. async init() {
  17. const config = this.config;
  18. const dbPath = `${config.dataDir}/web-access`;
  19. const db = new JembaDbThread();//в отдельном потоке
  20. await db.lock({
  21. dbPath,
  22. create: true,
  23. softLock: true,
  24. tableDefaults: {
  25. cacheSize: config.dbCacheSize,
  26. },
  27. });
  28. try {
  29. //открываем таблицы
  30. await db.openAll();
  31. } catch(e) {
  32. if (
  33. e.message.indexOf('corrupted') >= 0
  34. || e.message.indexOf('Unexpected token') >= 0
  35. || e.message.indexOf('invalid stored block lengths') >= 0
  36. ) {
  37. log(LM_ERR, `DB ${dbPath} corrupted`);
  38. log(`Open "${dbPath}" with auto repair`);
  39. await db.openAll({autoRepair: true});
  40. } else {
  41. throw e;
  42. }
  43. }
  44. await db.create({table: 'access', quietIfExists: true});
  45. //проверим, нужно ли обнулить таблицу access
  46. const pass = utils.getBufHash(this.config.accessPassword, 'sha256', 'hex');
  47. await db.create({table: 'config', quietIfExists: true});
  48. let rows = await db.select({table: 'config', where: `@@id('pass')`});
  49. if (!rows.length || rows[0].value !== pass) {
  50. //пароль сменился в конфиге, обнуляем токены
  51. await db.truncate({table: 'access'});
  52. await db.insert({table: 'config', replace: true, rows: [{id: 'pass', value: pass}]});
  53. }
  54. //загрузим токены сессий
  55. rows = await db.select({table: 'access'});
  56. for (const row of rows)
  57. this.accessMap.set(row.id, row.value);
  58. this.db = db;
  59. }
  60. async closeDb() {
  61. if (this.db) {
  62. await this.db.unlock();
  63. this.db = null;
  64. }
  65. }
  66. async periodicClean() {
  67. while (1) {//eslint-disable-line no-constant-condition
  68. try {
  69. const now = Date.now();
  70. //почистим accessMap
  71. if (!this.freeAccess) {
  72. for (const [accessToken, accessRec] of this.accessMap) {
  73. if ( !(accessRec.used > 0 || now - accessRec.time < cleanUnusedTokenTimeout)
  74. || !(this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout)
  75. ) {
  76. await this.deleteAccess(accessToken);
  77. } else if (!accessRec.saved) {
  78. await this.saveAccess(accessToken);
  79. }
  80. }
  81. }
  82. } catch(e) {
  83. log(LM_ERR, `WebAccess.periodicClean error: ${e.message}`);
  84. }
  85. await utils.sleep(cleanPeriod);
  86. }
  87. }
  88. async hasAccess(accessToken) {
  89. if (this.freeAccess)
  90. return true;
  91. const accessRec = this.accessMap.get(accessToken);
  92. if (accessRec) {
  93. const now = Date.now();
  94. if (this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout) {
  95. accessRec.used++;
  96. accessRec.time = now;
  97. accessRec.saved = false;
  98. if (accessRec.used === 1)
  99. await this.saveAccess(accessToken);
  100. return true;
  101. }
  102. }
  103. return false;
  104. }
  105. async deleteAccess(accessToken) {
  106. await this.db.delete({table: 'access', where: `@@id(${this.db.esc(accessToken)})`});
  107. this.accessMap.delete(accessToken);
  108. }
  109. async saveAccess(accessToken) {
  110. const value = this.accessMap.get(accessToken);
  111. if (!value || value.saved)
  112. return;
  113. value.saved = true;
  114. await this.db.insert({
  115. table: 'access',
  116. replace: true,
  117. rows: [{id: accessToken, value}]
  118. });
  119. }
  120. newToken() {
  121. const salt = utils.randomHexString(32);
  122. const accessToken = utils.getBufHash(this.config.accessPassword + salt, 'sha256', 'hex');
  123. this.accessMap.set(accessToken, {time: Date.now(), used: 0});
  124. return salt;
  125. }
  126. }
  127. module.exports = WebAccess;