WebAccess.js 4.7 KB

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