iobroker.admin 7.7.1 → 7.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +44 -11
  2. package/admin/admin.png +0 -0
  3. package/admin/i18n/de.json +1 -1
  4. package/admin/i18n/es.json +1 -1
  5. package/admin/i18n/fr.json +1 -1
  6. package/admin/i18n/it.json +1 -1
  7. package/admin/i18n/nl.json +1 -1
  8. package/admin/i18n/pl.json +1 -1
  9. package/admin/i18n/pt.json +1 -1
  10. package/admin/i18n/ru.json +1 -1
  11. package/admin/i18n/uk.json +1 -1
  12. package/admin/i18n/zh-cn.json +1 -1
  13. package/admin/jsonConfig.json5 +1 -2
  14. package/adminWww/assets/{AdapterUpdateDialog-Bgswiqs9.js → AdapterUpdateDialog-BQUTMe7v.js} +1 -1
  15. package/adminWww/assets/Adapters-KuMqwfzU.js +9 -0
  16. package/adminWww/assets/Config-LH40Fzyp.js +1 -0
  17. package/adminWww/assets/CustomModal-UQ6ICKdg.js +1 -0
  18. package/adminWww/assets/CustomTab-CE2qvngn.js +1 -0
  19. package/adminWww/assets/DefaultPropsProvider-BxjOYH1h.js +30 -0
  20. package/adminWww/assets/EasyMode-XXQKZmWs.js +1 -0
  21. package/adminWww/assets/{Enums-8m252lzC.js → Enums-Ddmi-fYo.js} +3 -3
  22. package/adminWww/assets/{Fields-lI3YkIzQ.js → Fields-CQNdUYkd.js} +1 -1
  23. package/adminWww/assets/{Files-fQpSPYWI.js → Files-DgOiCRiN.js} +2 -2
  24. package/adminWww/assets/FilledInput-B9JSi0Ta.js +2 -0
  25. package/adminWww/assets/Hosts-BgDpLhdl.js +121 -0
  26. package/adminWww/assets/Instances-DikJwBl0.js +2 -0
  27. package/adminWww/assets/Intro-vxIHtYzr.js +2 -0
  28. package/adminWww/assets/Logs-DZUWTbJn.js +1 -0
  29. package/adminWww/assets/Objects-Dm1_4aGR.js +60 -0
  30. package/adminWww/assets/{State-nZ6sjU9_.js → State-CrcWiBdx.js} +1 -1
  31. package/adminWww/assets/TextField-j-uewRwI.js +138 -0
  32. package/adminWww/assets/ThemeProvider-DHntAcOp.js +29 -0
  33. package/adminWww/assets/Users-GcWjwa7A.js +1 -0
  34. package/adminWww/assets/ace-1DqSbvTN.js +985 -0
  35. package/adminWww/assets/bootstrap-B3ZI_-0g.js +2024 -0
  36. package/adminWww/assets/{createSvgIcon-DuKI6CvW.js → createSvgIcon-Ds12z7B3.js} +1 -1
  37. package/adminWww/assets/emotion-cache.browser.esm-BgJfNZJN.js +1 -0
  38. package/adminWww/assets/emotion-react.browser.esm-CBtpmfsG.js +8 -0
  39. package/adminWww/assets/emotion-serialize.esm-Q4o_CgeF.js +1 -0
  40. package/adminWww/assets/emotion-styled.browser.esm-BeD8nBzN.js +1 -0
  41. package/adminWww/assets/emotion-use-insertion-effect-with-fallbacks.browser.esm-BIy6Leuu.js +1 -0
  42. package/adminWww/assets/geosearch.module-B6h9BuIR.js +1 -0
  43. package/adminWww/assets/hostInit-fzxL8JAx.js +1 -1
  44. package/adminWww/assets/iconBase-BAOnvyiQ.js +1 -0
  45. package/adminWww/assets/{index-BP5kWU3L.js → index-BOnh7es7.js} +1 -1
  46. package/adminWww/assets/{index-BmWdDCX_.js → index-BSfwmoAE.js} +4 -4
  47. package/adminWww/assets/{index-gHyOgbGJ.js → index-BVgYaw7t.js} +1 -1
  48. package/adminWww/assets/{index-D7Rlw-5R.js → index-C9i3HTsG.js} +1 -1
  49. package/adminWww/assets/{index-DH1kmwK1.js → index-CELb-gCK.js} +2 -2
  50. package/adminWww/assets/{index-HOidq_rM.js → index-CU6eZCem.js} +1 -1
  51. package/adminWww/assets/{index-Bsv_DXxB.js → index-DZ0MncEz.js} +2 -2
  52. package/adminWww/assets/index-Dg-s3k0-.js +10 -0
  53. package/adminWww/assets/index-tS5iCAF7.js +55 -0
  54. package/adminWww/assets/iobroker_admin__loadShare___mf_0_emotion_mf_1_react__loadShare__-CXogaIXO.js +1 -0
  55. package/adminWww/assets/{iobroker_admin__loadShare__leaflet__loadShare__-B1OZ7tj-.js → iobroker_admin__loadShare__leaflet__loadShare__-DIpcqFbm.js} +1 -1
  56. package/adminWww/assets/iobroker_admin__loadShare__prop_mf_2_types__loadShare__-pDONiJUe.js +1 -0
  57. package/adminWww/assets/{iobroker_admin__loadShare__react__loadShare__-DtlEM_52.js → iobroker_admin__loadShare__react__loadShare__-CuzHmAOj.js} +1 -1
  58. package/adminWww/assets/iobroker_admin__mf_v__runtimeInit__mf_v__-CHE4rLsT.js +5 -0
  59. package/adminWww/assets/leaflet-src-BRBM-1HK.js +4 -0
  60. package/adminWww/assets/{sentry-CNy_SQq1.js → sentry-B6yHtsbd.js} +1 -1
  61. package/adminWww/assets/{zh-cn-DZTx1vAh.js → zh-cn-heTcsLnU.js} +15 -15
  62. package/adminWww/img/admin.png +0 -0
  63. package/adminWww/index.html +2 -2
  64. package/adminWww/mf-manifest.json +1 -1
  65. package/adminWww/remoteEntry.js +2 -2
  66. package/adminWww/static/js/worker-javascript.js +1 -1
  67. package/adminWww/static/js/worker-yaml.js +1 -1
  68. package/build/i18n/de.json +18 -0
  69. package/build/i18n/es.json +18 -0
  70. package/build/i18n/fr.json +18 -0
  71. package/build/i18n/it.json +18 -0
  72. package/build/i18n/nl.json +18 -0
  73. package/build/i18n/pl.json +18 -0
  74. package/build/i18n/pt.json +18 -0
  75. package/build/i18n/ru.json +18 -0
  76. package/build/i18n/uk.json +18 -0
  77. package/build/i18n/zh-cn.json +18 -0
  78. package/build/lib/DockerManager.js +1315 -0
  79. package/build/lib/DockerManager.js.map +1 -0
  80. package/build/lib/dockerManager.types.js +3 -0
  81. package/build/lib/dockerManager.types.js.map +1 -0
  82. package/{build-backend → build}/lib/testPassword.js +1 -1
  83. package/{build-backend → build}/lib/testPassword.js.map +1 -1
  84. package/{build-backend/src → build}/lib/translations.js +16 -1
  85. package/build/lib/translations.js.map +1 -0
  86. package/{build-backend → build}/main.js +58 -5
  87. package/build/main.js.map +1 -0
  88. package/io-package.json +27 -27
  89. package/package.json +13 -12
  90. package/adminWww/assets/Adapters-yQiEeoh8.js +0 -7
  91. package/adminWww/assets/Config-C1Gjt8DC.js +0 -1
  92. package/adminWww/assets/CustomModal-CgEtkfCG.js +0 -1
  93. package/adminWww/assets/CustomTab-CwioFUor.js +0 -1
  94. package/adminWww/assets/DefaultPropsProvider-DxsXuIyE.js +0 -30
  95. package/adminWww/assets/EasyMode-CN-reJVI.js +0 -1
  96. package/adminWww/assets/FilledInput-CKswrNd5.js +0 -2
  97. package/adminWww/assets/Hosts-BPdUlXV5.js +0 -121
  98. package/adminWww/assets/Instances-BStmfz4r.js +0 -2
  99. package/adminWww/assets/Intro-Dr1-aqO9.js +0 -2
  100. package/adminWww/assets/Logs-xCCrWV30.js +0 -1
  101. package/adminWww/assets/Objects-BF8brXaf.js +0 -60
  102. package/adminWww/assets/Tabs-BJ1rL35Z.js +0 -138
  103. package/adminWww/assets/Users-B0iasrSG.js +0 -1
  104. package/adminWww/assets/ace-CFIUQz8j.js +0 -970
  105. package/adminWww/assets/blueGrey-D-KQaGde.js +0 -29
  106. package/adminWww/assets/bootstrap-DDrI3lVl.js +0 -2036
  107. package/adminWww/assets/emotion-cache.browser.esm-B8BFze5o.js +0 -1
  108. package/adminWww/assets/emotion-react.browser.esm-DApPKwaj.js +0 -8
  109. package/adminWww/assets/emotion-serialize.esm-BUa21YfQ.js +0 -1
  110. package/adminWww/assets/emotion-styled.browser.esm-Dc05hmeu.js +0 -1
  111. package/adminWww/assets/emotion-utils.browser.esm-DOjH6Olm.js +0 -1
  112. package/adminWww/assets/geosearch.module-CWfRtxrN.js +0 -1
  113. package/adminWww/assets/iconBase-CgxZ5MMC.js +0 -1
  114. package/adminWww/assets/index-BjVqnwqt.js +0 -10
  115. package/adminWww/assets/index-RvHSqeMD.js +0 -55
  116. package/adminWww/assets/iobroker_admin__loadShare___mf_0_emotion_mf_1_react__loadShare__-gw2iqPBS.js +0 -1
  117. package/adminWww/assets/iobroker_admin__loadShare__prop_mf_2_types__loadShare__-BGyJCibC.js +0 -1
  118. package/adminWww/assets/iobroker_admin__mf_v__runtimeInit__mf_v__-VDoBF19b.js +0 -10
  119. package/adminWww/assets/leaflet-src-A2ZHl6nF.js +0 -4
  120. package/build-backend/i18n/de.json +0 -18
  121. package/build-backend/i18n/es.json +0 -18
  122. package/build-backend/i18n/fr.json +0 -18
  123. package/build-backend/i18n/it.json +0 -18
  124. package/build-backend/i18n/nl.json +0 -18
  125. package/build-backend/i18n/pl.json +0 -18
  126. package/build-backend/i18n/pt.json +0 -18
  127. package/build-backend/i18n/ru.json +0 -18
  128. package/build-backend/i18n/uk.json +0 -18
  129. package/build-backend/i18n/zh-cn.json +0 -18
  130. package/build-backend/lib/translations.js +0 -27
  131. package/build-backend/lib/translations.js.map +0 -1
  132. package/build-backend/main.js.map +0 -1
  133. package/build-backend/src/lib/checkLinuxPass.js +0 -280
  134. package/build-backend/src/lib/checkLinuxPass.js.map +0 -1
  135. package/build-backend/src/lib/testPassword.js +0 -58
  136. package/build-backend/src/lib/testPassword.js.map +0 -1
  137. package/build-backend/src/lib/translations.js.map +0 -1
  138. package/build-backend/src/lib/utils.js +0 -344
  139. package/build-backend/src/lib/utils.js.map +0 -1
  140. package/build-backend/src/lib/web.js +0 -1165
  141. package/build-backend/src/lib/web.js.map +0 -1
  142. package/build-backend/src/main.js +0 -1704
  143. package/build-backend/src/main.js.map +0 -1
  144. package/build-backend/src-admin/src/components/Adapters/Utils.js +0 -39
  145. package/build-backend/src-admin/src/components/Adapters/Utils.js.map +0 -1
  146. /package/{build-backend → build}/i18n/en.json +0 -0
  147. /package/{build-backend → build}/lib/checkLinuxPass.js +0 -0
  148. /package/{build-backend → build}/lib/checkLinuxPass.js.map +0 -0
  149. /package/{build-backend → build}/lib/utils.js +0 -0
  150. /package/{build-backend → build}/lib/utils.js.map +0 -0
  151. /package/{build-backend → build}/lib/web.js +0 -0
  152. /package/{build-backend → build}/lib/web.js.map +0 -0
@@ -1,1165 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const adapter_core_1 = require("@iobroker/adapter-core");
4
- const webserver_1 = require("@iobroker/webserver");
5
- const express = require("express");
6
- const node_fs_1 = require("node:fs");
7
- const util_1 = require("util");
8
- const node_path_1 = require("node:path");
9
- const node_stream_1 = require("node:stream");
10
- const compression = require("compression");
11
- const mime_1 = require("mime");
12
- const node_zlib_1 = require("node:zlib");
13
- const archiver = require("archiver");
14
- const axios_1 = require("axios");
15
- const ajv_1 = require("ajv");
16
- const json5_1 = require("json5");
17
- const passport = require("passport");
18
- const fileUpload = require("express-fileupload");
19
- const passport_local_1 = require("passport-local");
20
- const session = require("express-session");
21
- const bodyParser = require("body-parser");
22
- const cookieParser = require("cookie-parser");
23
- let AdapterStore;
24
- /** Content of a socket-io file */
25
- let socketIoFile;
26
- /** UUID of the installation */
27
- let uuid;
28
- const page404 = (0, node_fs_1.readFileSync)(`${__dirname}/../../public/404.html`).toString('utf8');
29
- const logTemplate = (0, node_fs_1.readFileSync)(`${__dirname}/../../public/logTemplate.html`).toString('utf8');
30
- // const FORBIDDEN_CHARS = /[\]\[*,;'"`<>\\\s?]/g; // with space
31
- const ONE_MONTH_SEC = 30 * 24 * 3_600;
32
- // copied from here: https://github.com/component/escape-html/blob/master/index.js
33
- const matchHtmlRegExp = /["'&<>]/;
34
- function escapeHtml(string) {
35
- const str = `${string}`;
36
- const match = matchHtmlRegExp.exec(str);
37
- if (!match) {
38
- return str;
39
- }
40
- let escape;
41
- let html = '';
42
- let index;
43
- let lastIndex = 0;
44
- for (index = match.index; index < str.length; index++) {
45
- switch (str.charCodeAt(index)) {
46
- case 34: // "
47
- escape = '&quot;';
48
- break;
49
- case 38: // &
50
- escape = '&amp;';
51
- break;
52
- case 39: // '
53
- escape = '&#39;';
54
- break;
55
- case 60: // <
56
- escape = '&lt;';
57
- break;
58
- case 62: // >
59
- escape = '&gt;';
60
- break;
61
- default:
62
- continue;
63
- }
64
- if (lastIndex !== index) {
65
- html += str.substring(lastIndex, index);
66
- }
67
- lastIndex = index + 1;
68
- html += escape;
69
- }
70
- return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
71
- }
72
- function isLocalUrl(path) {
73
- try {
74
- return new URL(path, 'http://127.0.0.1:3000').origin === 'http://127.0.0.1:3000';
75
- }
76
- catch {
77
- return false;
78
- }
79
- }
80
- function get404Page(customText) {
81
- if (customText) {
82
- return page404.replace('<div class="custom-message"></div>', `<div class="custom-message">${customText}</div>`);
83
- }
84
- return page404;
85
- }
86
- /**
87
- * Read folder recursive
88
- *
89
- * @param adapter the adapter instance
90
- * @param adapterName name of the adapter or dir
91
- * @param url url of the specific file or directory
92
- */
93
- async function readFolderRecursive(adapter, adapterName, url) {
94
- const filesOfDir = [];
95
- const fileMetas = await adapter.readDirAsync(adapterName, url);
96
- for (const fileMeta of fileMetas) {
97
- if (!fileMeta.isDir) {
98
- const file = await adapter.readFileAsync(adapterName, `${url}/${fileMeta.file}`);
99
- if (file.file instanceof Buffer) {
100
- filesOfDir.push({ name: url ? `${url}/${fileMeta.file}` : fileMeta.file, file: file.file });
101
- }
102
- else {
103
- filesOfDir.push({
104
- name: url ? `${url}/${fileMeta.file}` : fileMeta.file,
105
- file: Buffer.from(file.file, 'utf-8'),
106
- });
107
- }
108
- }
109
- else {
110
- filesOfDir.push(...(await readFolderRecursive(adapter, adapterName, `${url}/${fileMeta.file}`)));
111
- }
112
- }
113
- return filesOfDir;
114
- }
115
- function MemoryWriteStream() {
116
- node_stream_1.Transform.call(this);
117
- this._chunks = [];
118
- this._transform = (chunk, _enc, cb) => {
119
- this._chunks.push(chunk);
120
- cb();
121
- };
122
- this.collect = () => {
123
- const result = Buffer.concat(this._chunks);
124
- this._chunks = [];
125
- return result;
126
- };
127
- }
128
- (0, util_1.inherits)(MemoryWriteStream, node_stream_1.Transform);
129
- /**
130
- * Webserver class
131
- */
132
- class Web {
133
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
134
- server = {
135
- app: null,
136
- server: null,
137
- };
138
- LOGIN_PAGE = '/index.html?login';
139
- /** URL to the JSON config schema */
140
- JSON_CONFIG_SCHEMA_URL = 'https://raw.githubusercontent.com/ioBroker/adapter-react-v5/main/schemas/jsonConfig.json';
141
- bruteForce = {};
142
- store = null;
143
- indexHTML;
144
- baseDir = (0, node_path_1.join)(__dirname, '..', '..');
145
- dirName = (0, node_path_1.normalize)(`${this.baseDir}/admin/`.replace(/\\/g, '/')).replace(/\\/g, '/');
146
- unprotectedFiles;
147
- systemConfig;
148
- // todo delete after React will be main
149
- wwwDir = (0, node_path_1.join)(this.baseDir, 'adminWww');
150
- settings;
151
- adapter;
152
- options;
153
- onReady;
154
- systemLanguage;
155
- checkTimeout;
156
- /**
157
- * Create a new instance of Web
158
- *
159
- * @param settings settings of the adapter
160
- * @param adapter instance of the adapter
161
- * @param onReady callback when the server is ready
162
- * @param options options for the webserver
163
- */
164
- constructor(settings, adapter, onReady, options) {
165
- this.settings = settings;
166
- this.adapter = adapter;
167
- this.onReady = onReady;
168
- this.options = options;
169
- this.systemLanguage = this.options?.systemLanguage || 'en';
170
- void this.#init();
171
- }
172
- decorateLogFile(fileName, text) {
173
- const log = text || (0, node_fs_1.readFileSync)(fileName).toString();
174
- return logTemplate.replace('@@title@@', (0, node_path_1.parse)(fileName).name).replace('@@body@@', log);
175
- }
176
- setLanguage(lang) {
177
- this.systemLanguage = lang;
178
- }
179
- close() {
180
- if (this.checkTimeout) {
181
- this.adapter.clearTimeout(this.checkTimeout);
182
- this.checkTimeout = null;
183
- }
184
- void this.adapter.setState('info.connection', false, true);
185
- this.server.server?.close();
186
- }
187
- prepareIndex() {
188
- let template = (0, node_fs_1.readFileSync)((0, node_path_1.join)(this.wwwDir, 'index.html')).toString('utf8');
189
- const m = template.match(/(["']?@@\w+@@["']?)/g);
190
- m.forEach(pattern => {
191
- pattern = pattern.replace(/@/g, '').replace(/'/g, '').replace(/"/g, '');
192
- if (pattern === 'disableDataReporting') {
193
- template = template.replace(/['"]@@disableDataReporting@@["']/g,
194
- // @ts-expect-error deprecated: this is not used on instance objects use system.adapter.xy.plugins.sentry.enabled
195
- this.adapter.common?.disableDataReporting ? 'true' : 'false');
196
- }
197
- else if (pattern === 'loginBackgroundImage') {
198
- if (this.adapter.config.loginBackgroundImage) {
199
- template = template.replace('@@loginBackgroundImage@@', `files/${this.adapter.namespace}/login-bg.png`);
200
- }
201
- else {
202
- template = template.replace('@@loginBackgroundImage@@', '');
203
- }
204
- }
205
- else if (pattern === 'loginBackgroundColor') {
206
- template = template.replace('@@loginBackgroundColor@@', this.adapter.config.loginBackgroundColor || 'inherit');
207
- }
208
- else if (pattern === 'loadingBackgroundImage') {
209
- if (this.adapter.config.loadingBackgroundImage) {
210
- template = template.replace('@@loadingBackgroundImage@@', `files/${this.adapter.namespace}/loading-bg.png`);
211
- }
212
- else {
213
- template = template.replace('@@loadingBackgroundImage@@', '');
214
- }
215
- }
216
- else if (pattern === 'loadingBackgroundColor') {
217
- template = template.replace('@@loadingBackgroundColor@@', this.adapter.config.loadingBackgroundColor || '');
218
- }
219
- else if (pattern === 'vendorPrefix') {
220
- template = template.replace(`@@vendorPrefix@@`, this.systemConfig.native.vendor.uuidPrefix || (uuid.length > 36 ? uuid.substring(0, 2) : ''));
221
- }
222
- else if (pattern === 'loginMotto') {
223
- template = template.replace(`@@loginMotto@@`, this.systemConfig.native.vendor.admin.login.motto || this.adapter.config.loginMotto || '');
224
- }
225
- else if (pattern === 'loginLogo') {
226
- template = template.replace(`@@loginLogo@@`, this.systemConfig.native.vendor.icon || '');
227
- }
228
- else if (pattern === 'loginLink') {
229
- template = template.replace(`@@loginLink@@`, this.systemConfig.native.vendor.admin.login.link || '');
230
- }
231
- else if (pattern === 'loginTitle') {
232
- template = template.replace(`@@loginTitle@@`, this.systemConfig.native.vendor.admin.login.title || '');
233
- }
234
- else {
235
- template = template.replace(`@@${pattern}@@`, this.adapter.config[pattern] !== undefined
236
- ? this.adapter.config[pattern]
237
- : '');
238
- }
239
- });
240
- return template;
241
- }
242
- getInfoJs() {
243
- const result = [`window.sysLang = "${this.systemLanguage}";`];
244
- return result.join('\n');
245
- }
246
- getErrorRedirect(origin) {
247
- // LOGIN_PAGE /index.html?login
248
- // origin can be "?login&href=" -
249
- // or "/?login&href=" -
250
- //
251
- if (origin) {
252
- const parts = origin.split('&');
253
- if (!parts.includes('error')) {
254
- parts.splice(1, 0, 'error');
255
- origin = parts.join('&');
256
- }
257
- if (origin.startsWith('?login')) {
258
- return this.LOGIN_PAGE + origin.substring(6);
259
- }
260
- if (origin.startsWith('/?login')) {
261
- return this.LOGIN_PAGE + origin.substring(7);
262
- }
263
- if (origin.startsWith(this.LOGIN_PAGE)) {
264
- return origin;
265
- }
266
- return this.LOGIN_PAGE + origin;
267
- }
268
- return `${this.LOGIN_PAGE}?error`;
269
- }
270
- /**
271
- * Validate, al JSON configs from alla adapters against the current schema
272
- *
273
- * @param adapterName name of the adapter
274
- */
275
- async validateJsonConfig(adapterName) {
276
- let schema = null;
277
- try {
278
- const schemaRes = await axios_1.default.get(this.JSON_CONFIG_SCHEMA_URL);
279
- schema = schemaRes.data;
280
- }
281
- catch (e) {
282
- this.adapter.log.debug(`Could not get jsonConfig schema: ${e.message}`);
283
- return;
284
- }
285
- const res = await this.adapter.getForeignObjectAsync(`system.adapter.${adapterName}`);
286
- if (res?.common.adminUI?.config === 'json') {
287
- try {
288
- const ajv = new ajv_1.Ajv({
289
- allErrors: false,
290
- strict: 'log',
291
- });
292
- const adapterPath = (0, node_path_1.dirname)(require.resolve(`iobroker.${adapterName}/package.json`));
293
- const jsonConfPath = (0, node_path_1.join)(adapterPath, 'admin', 'jsonConfig.json');
294
- const json5ConfPath = (0, node_path_1.join)(adapterPath, 'admin', 'jsonConfig.json5');
295
- let jsonConf;
296
- if ((0, node_fs_1.existsSync)(jsonConfPath)) {
297
- jsonConf = (0, node_fs_1.readFileSync)(jsonConfPath, {
298
- encoding: 'utf-8',
299
- });
300
- }
301
- else {
302
- jsonConf = (0, node_fs_1.readFileSync)(json5ConfPath, {
303
- encoding: 'utf-8',
304
- });
305
- }
306
- const validate = ajv.compile(schema);
307
- const valid = validate((0, json5_1.parse)(jsonConf));
308
- if (!valid) {
309
- this.adapter.log.warn(`${adapterName} has an invalid jsonConfig: ${JSON.stringify(validate.errors)}`);
310
- }
311
- }
312
- catch (e) {
313
- this.adapter.log.debug(`Error validating schema of ${adapterName}: ${e.message}`);
314
- }
315
- }
316
- }
317
- unzipFile(fileName, data, res) {
318
- // extract the file
319
- try {
320
- const text = (0, node_zlib_1.gunzipSync)(data).toString('utf8');
321
- if (text.length > 2 * 1024 * 1024) {
322
- res.header('Content-Type', 'text/plain');
323
- res.send(text);
324
- }
325
- else {
326
- res.header('Content-Type', 'text/html');
327
- res.send(this.decorateLogFile(fileName, text));
328
- }
329
- }
330
- catch (e) {
331
- res.header('Content-Type', 'application/gzip');
332
- res.send(data);
333
- this.adapter.log.error(`Cannot extract file ${fileName}: ${e}`);
334
- }
335
- }
336
- /**
337
- * Initialize the server
338
- */
339
- async #init() {
340
- if (this.settings.port) {
341
- this.server.app = express();
342
- this.server.app.use(compression());
343
- this.settings.ttl = Math.round(this.settings.ttl) || 3_600;
344
- this.settings.accessAllowedConfigs = this.settings.accessAllowedConfigs || [];
345
- this.settings.accessAllowedTabs = this.settings.accessAllowedTabs || [];
346
- this.server.app.disable('x-powered-by');
347
- // enable use of i-frames together with HTTPS
348
- this.server.app.get('/*', (_req, res, next) => {
349
- res.header('X-Frame-Options', 'SAMEORIGIN');
350
- next(); // http://expressjs.com/guide.html#passing-route control
351
- });
352
- // ONLY for DEBUG
353
- /*server.app.use((req: Request, res: Response, next: NextFunction): void => {
354
- res.header('Access-Control-Allow-Origin', '*');
355
- res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
356
- next();
357
- });*/
358
- this.server.app.get('/version', (_req, res) => {
359
- res.status(200).send(this.adapter.version);
360
- });
361
- // replace socket.io
362
- this.server.app.use((req, res, next) => {
363
- // return favicon always
364
- if (req.url === '/favicon.ico') {
365
- res.set('Content-Type', 'image/x-icon');
366
- if (this.systemConfig.native.vendor.ico) {
367
- // convert base64 to ico
368
- const text = this.systemConfig.native.vendor.ico.split(',')[1];
369
- res.send(Buffer.from(text, 'base64'));
370
- return;
371
- }
372
- res.send((0, node_fs_1.readFileSync)((0, node_path_1.join)(this.wwwDir, 'favicon.ico')));
373
- return;
374
- }
375
- else if (socketIoFile !== false &&
376
- (req.url.startsWith('socket.io.js') || req.url.match(/\/socket\.io\.js(\?.*)?$/))) {
377
- if (socketIoFile) {
378
- res.contentType('text/javascript');
379
- res.status(200).send(socketIoFile);
380
- return;
381
- }
382
- socketIoFile = (0, node_fs_1.readFileSync)((0, node_path_1.join)(this.wwwDir, 'lib', 'js', 'socket.io.js'), {
383
- encoding: 'utf-8',
384
- });
385
- if (socketIoFile) {
386
- res.contentType('text/javascript');
387
- res.status(200).send(socketIoFile);
388
- return;
389
- }
390
- socketIoFile = false;
391
- res.status(404).send(get404Page());
392
- return;
393
- }
394
- next();
395
- });
396
- this.server.app.get('*/_socket/info.js', (_req, res) => {
397
- res.set('Content-Type', 'application/javascript');
398
- res.status(200).send(this.getInfoJs());
399
- });
400
- if (this.settings.auth) {
401
- AdapterStore = adapter_core_1.commonTools.session(session, this.settings.ttl);
402
- const flash = await Promise.resolve().then(() => require('connect-flash'));
403
- this.store = new AdapterStore({ adapter: this.adapter });
404
- passport.use(new passport_local_1.Strategy((username, password, done) => {
405
- username = (username || '').toString();
406
- if (this.bruteForce[username] && this.bruteForce[username].errors > 4) {
407
- let minutes = new Date().getTime() - this.bruteForce[username].time;
408
- if (this.bruteForce[username].errors < 7) {
409
- if (new Date().getTime() - this.bruteForce[username].time < 60_000) {
410
- minutes = 1;
411
- }
412
- else {
413
- minutes = 0;
414
- }
415
- }
416
- else if (this.bruteForce[username].errors < 10) {
417
- if (new Date().getTime() - this.bruteForce[username].time < 180_000) {
418
- minutes = Math.ceil((180_000 - minutes) / 60000);
419
- }
420
- else {
421
- minutes = 0;
422
- }
423
- }
424
- else if (this.bruteForce[username].errors < 15) {
425
- if (new Date().getTime() - this.bruteForce[username].time < 600_000) {
426
- minutes = Math.ceil((60_0000 - minutes) / 60_000);
427
- }
428
- else {
429
- minutes = 0;
430
- }
431
- }
432
- else if (new Date().getTime() - this.bruteForce[username].time < 3_600_000) {
433
- minutes = Math.ceil((3_600_000 - minutes) / 60_000);
434
- }
435
- else {
436
- minutes = 0;
437
- }
438
- if (minutes) {
439
- return done(`Too many errors. Try again in ${minutes} ${minutes === 1 ? 'minute' : 'minutes'}.`, false);
440
- }
441
- }
442
- const mayBePromise = this.adapter.checkPassword(username, password, (res, user) => {
443
- if (!res) {
444
- this.bruteForce[username] = this.bruteForce[username] || { errors: 0 };
445
- this.bruteForce[username].time = new Date().getTime();
446
- this.bruteForce[username].errors++;
447
- }
448
- else if (this.bruteForce[username]) {
449
- delete this.bruteForce[username];
450
- }
451
- if (res) {
452
- return done(null, (user || username).replace(/^system\.user\./, ''));
453
- }
454
- return done(null, false);
455
- });
456
- if (mayBePromise instanceof Promise) {
457
- mayBePromise.catch(e => {
458
- this.adapter.log.error(`Cannot check password: ${e}`);
459
- done(null, false);
460
- });
461
- }
462
- }));
463
- passport.serializeUser((user, done) => done(null, user));
464
- passport.deserializeUser((user, done) => done(null, user));
465
- this.server.app.use(cookieParser());
466
- this.server.app.use(bodyParser.urlencoded({ extended: true }));
467
- this.server.app.use(bodyParser.json());
468
- this.server.app.use(session({
469
- secret: this.adapter.secret,
470
- saveUninitialized: true,
471
- resave: true,
472
- cookie: { maxAge: this.settings.ttl * 1000 },
473
- // rolling: true, // The expiration is reset to the original maxAge, resetting the expiration countdown.
474
- store: this.store,
475
- }));
476
- this.server.app.use(passport.initialize());
477
- this.server.app.use(passport.session());
478
- this.server.app.use(flash());
479
- this.server.app.post('/login', (req, res, next) => {
480
- let redirect = '/';
481
- req.body = req.body || {};
482
- const isDev = req.url.includes('?dev&');
483
- const origin = (req.body.origin || '?href=%2F').trim();
484
- if (origin) {
485
- const parts = origin.split('href=');
486
- if (parts?.length > 1 && parts[1]) {
487
- redirect = decodeURIComponent(parts[1]);
488
- // if some invalid characters in redirect
489
- if (redirect.match(/[^-_a-zA-Z0-9&%?./]/) || !isLocalUrl(redirect)) {
490
- redirect = '/';
491
- }
492
- }
493
- else {
494
- // extract pathname
495
- redirect = origin.split('?')[0] || '/';
496
- }
497
- }
498
- req.body.password = (req.body.password || '').toString();
499
- req.body.username = (req.body.username || '').toString();
500
- req.body.stayLoggedIn =
501
- req.body.stayloggedin === 'true' ||
502
- req.body.stayloggedin === true ||
503
- req.body.stayloggedin === 'on';
504
- passport.authenticate('local', (err, user) => {
505
- if (err) {
506
- this.adapter.log.warn(`Cannot login user: ${err.message}`);
507
- return res.redirect(this.getErrorRedirect(origin));
508
- }
509
- if (!user) {
510
- return res.redirect(this.getErrorRedirect(origin));
511
- }
512
- req.logIn(user, err => {
513
- if (err) {
514
- this.adapter.log.warn(`Cannot login user: ${err}`);
515
- return res.redirect(this.getErrorRedirect(origin));
516
- }
517
- if (req.body.stayLoggedIn) {
518
- req.session.cookie.httpOnly = true;
519
- // https://www.npmjs.com/package/express-session#cookiemaxage-1
520
- // Interval in ms
521
- req.session.cookie.maxAge =
522
- (this.settings.ttl > ONE_MONTH_SEC ? this.settings.ttl : ONE_MONTH_SEC) * 1000;
523
- }
524
- else {
525
- req.session.cookie.httpOnly = true;
526
- // https://www.npmjs.com/package/express-session#cookiemaxage-1
527
- // Interval in ms
528
- req.session.cookie.maxAge = this.settings.ttl * 1000;
529
- }
530
- if (isDev) {
531
- return res.redirect(`http://127.0.0.1:3000${redirect}`);
532
- }
533
- return res.redirect(redirect);
534
- });
535
- })(req, res, next);
536
- });
537
- this.server.app.get('/session', (req, res) => {
538
- res.json({ expireInSec: Math.round(req.session.cookie.maxAge / 1_000) });
539
- });
540
- this.server.app.get('/logout', (req, res) => {
541
- const isDev = req.url.includes('?dev');
542
- let origin = req.url.split('origin=')[1];
543
- if (origin) {
544
- const pos = origin.lastIndexOf('/');
545
- if (pos !== -1) {
546
- origin = origin.substring(0, pos);
547
- }
548
- }
549
- req.logout(() => {
550
- if (isDev) {
551
- res.redirect('http://127.0.0.1:3000/index.html?login');
552
- }
553
- else {
554
- res.redirect(origin ? origin + this.LOGIN_PAGE : this.LOGIN_PAGE);
555
- }
556
- });
557
- });
558
- // route middleware to make sure a user is logged in
559
- this.server.app.use((req, res, next) => {
560
- // return favicon always
561
- if (req.url === '/favicon.ico') {
562
- res.set('Content-Type', 'image/x-icon');
563
- if (this.systemConfig.native.vendor.ico) {
564
- // convert base64 to ico
565
- const text = this.systemConfig.native.vendor.ico.split(',')[1];
566
- res.send(Buffer.from(text, 'base64'));
567
- return;
568
- }
569
- res.send((0, node_fs_1.readFileSync)((0, node_path_1.join)(this.wwwDir, 'favicon.ico')));
570
- return;
571
- }
572
- if (/admin\.\d+\/login-bg\.png(\?.*)?$/.test(req.originalUrl)) {
573
- // Read the names of files for gong
574
- this.adapter.readFile(this.adapter.namespace, 'login-bg.png', null, (err, file) => {
575
- if (!err && file) {
576
- res.set('Content-Type', 'image/png');
577
- res.status(200).send(file);
578
- }
579
- else {
580
- res.status(404).send(get404Page());
581
- }
582
- });
583
- return;
584
- }
585
- if (!req.isAuthenticated()) {
586
- if (/^\/login\//.test(req.originalUrl) || /\.ico(\?.*)?$/.test(req.originalUrl)) {
587
- return next();
588
- }
589
- const pathName = req.url.split('?')[0];
590
- // protect all paths except
591
- this.unprotectedFiles =
592
- this.unprotectedFiles ||
593
- (0, node_fs_1.readdirSync)(this.wwwDir).map(file => {
594
- const stat = (0, node_fs_1.lstatSync)((0, node_path_1.join)(this.wwwDir, file));
595
- return { name: file, isDir: stat.isDirectory() };
596
- });
597
- if (pathName &&
598
- pathName !== '/' &&
599
- !this.unprotectedFiles.find(file => file.isDir ? pathName.startsWith(`/${file.name}/`) : `/${file.name}` === pathName)) {
600
- res.redirect(`${this.LOGIN_PAGE}&href=${encodeURIComponent(req.originalUrl)}`);
601
- }
602
- else {
603
- next();
604
- return;
605
- }
606
- }
607
- else {
608
- next();
609
- return;
610
- }
611
- });
612
- }
613
- else {
614
- this.server.app.get('/logout', (_req, res) => res.redirect('/'));
615
- }
616
- this.server.app.get('/iobroker_check.html', (_req, res) => {
617
- res.status(200).send('ioBroker');
618
- });
619
- this.server.app.get('/validate_config/*', async (req, res) => {
620
- const adapterName = req.url.split('/').pop();
621
- await this.validateJsonConfig(adapterName.toLowerCase());
622
- res.status(200).send('validated');
623
- });
624
- // send log files
625
- this.server.app.get('/log/*', (req, res) => {
626
- let parts = decodeURIComponent(req.url).split('/');
627
- if (parts.length === 5) {
628
- // remove first "/"
629
- parts.shift();
630
- // remove "log"
631
- parts.shift();
632
- const [host, transport] = parts;
633
- parts = parts.splice(2);
634
- const fileName = parts.join('/');
635
- if (fileName.includes('..')) {
636
- res.status(404).send(get404Page(`File ${escapeHtml(fileName)} not found. Do not use relative paths!`));
637
- return;
638
- }
639
- this.adapter.sendToHost(`system.host.${host}`, 'getLogFile', { filename: fileName, transport }, result => {
640
- const _result = result;
641
- if (!_result || _result.error) {
642
- if (_result.error) {
643
- this.adapter.log.warn(`Cannot read log file ${fileName}: ${_result.error}`);
644
- }
645
- res.status(404).send(get404Page(`File ${escapeHtml(fileName)} not found`));
646
- }
647
- else {
648
- if (_result.gz) {
649
- if (_result.size > 1024 * 1024) {
650
- res.header('Content-Type', 'application/gzip');
651
- res.send(_result.data);
652
- }
653
- else {
654
- try {
655
- this.unzipFile(fileName, _result.data, res);
656
- }
657
- catch (e) {
658
- res.header('Content-Type', 'application/gzip');
659
- res.send(_result.data);
660
- this.adapter.log.error(`Cannot extract file ${fileName}: ${e}`);
661
- }
662
- }
663
- }
664
- else if (_result.data === undefined || _result.data === null) {
665
- res.status(404).send(get404Page(`File ${escapeHtml(fileName)} not found`));
666
- }
667
- else if (_result.size > 2 * 1024 * 1024) {
668
- res.header('Content-Type', 'text/plain');
669
- res.send(_result.data);
670
- }
671
- else {
672
- res.header('Content-Type', 'text/html');
673
- res.send(this.decorateLogFile(fileName, _result.data));
674
- }
675
- }
676
- });
677
- }
678
- else {
679
- parts = parts.splice(2);
680
- const transport = parts.shift();
681
- let fileName = parts.join('/');
682
- const config = this.adapter.systemConfig;
683
- // detect file log
684
- if (transport && config?.log?.transport) {
685
- if (transport in config.log.transport && config.log.transport[transport].type === 'file') {
686
- let logFolder;
687
- if (config.log.transport[transport].filename) {
688
- parts = config.log.transport[transport].filename.replace(/\\/g, '/').split('/');
689
- parts.pop();
690
- logFolder = (0, node_path_1.normalize)(parts.join('/'));
691
- }
692
- else {
693
- logFolder = (0, node_path_1.join)(process.cwd(), 'log');
694
- }
695
- if (logFolder[0] !== '/' && logFolder[0] !== '\\' && !logFolder.match(/^[a-zA-Z]:/)) {
696
- const _logFolder = (0, node_path_1.normalize)((0, node_path_1.join)(`${this.baseDir}/../../`, logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
697
- if (!(0, node_fs_1.existsSync)(_logFolder)) {
698
- logFolder = (0, node_path_1.normalize)((0, node_path_1.join)(`${this.baseDir}/../`, logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
699
- }
700
- else {
701
- logFolder = _logFolder;
702
- }
703
- }
704
- fileName = (0, node_path_1.normalize)((0, node_path_1.join)(logFolder, fileName).replace(/\\/g, '/')).replace(/\\/g, '/');
705
- if (fileName.startsWith(logFolder) && (0, node_fs_1.existsSync)(fileName)) {
706
- const stat = (0, node_fs_1.lstatSync)(fileName);
707
- // if a file is an archive
708
- if (fileName.toLowerCase().endsWith('.gz')) {
709
- // try to not process to big files
710
- if (stat.size > 1024 * 1024 /* || !existsSync('/dev/null')*/) {
711
- res.header('Content-Type', 'application/gzip');
712
- res.sendFile(fileName);
713
- }
714
- else {
715
- try {
716
- this.unzipFile(fileName, (0, node_fs_1.readFileSync)(fileName, { encoding: 'utf-8' }), res);
717
- }
718
- catch (e) {
719
- res.header('Content-Type', 'application/gzip');
720
- res.sendFile(fileName);
721
- this.adapter.log.error(`Cannot extract file ${fileName}: ${e}`);
722
- }
723
- }
724
- }
725
- else if (stat.size > 2 * 1024 * 1024) {
726
- res.header('Content-Type', 'text/plain');
727
- res.sendFile(fileName);
728
- }
729
- else {
730
- res.header('Content-Type', 'text/html');
731
- res.send(this.decorateLogFile(fileName));
732
- }
733
- return;
734
- }
735
- }
736
- }
737
- res.status(404).send(get404Page(`File ${escapeHtml(fileName)} not found`));
738
- }
739
- });
740
- const appOptions = {};
741
- if (this.settings.cache) {
742
- appOptions.maxAge = 30_758_400_000;
743
- }
744
- if (this.settings.tmpPathAllow && this.settings.tmpPath) {
745
- this.server.app.use('/tmp/', express.static(this.settings.tmpPath, { maxAge: 0 }));
746
- this.server.app.use(fileUpload({
747
- useTempFiles: true,
748
- tempFileDir: this.settings.tmpPath,
749
- }));
750
- this.server.app.post('/upload', (req, res) => {
751
- if (!req.files) {
752
- res.status(400).send('No files were uploaded.');
753
- return;
754
- }
755
- // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
756
- let myFile;
757
- // take the first non-empty file
758
- for (const file of Object.values(req.files)) {
759
- if (file) {
760
- myFile = file;
761
- break;
762
- }
763
- }
764
- if (myFile) {
765
- if (myFile.data && myFile.data.length > 600 * 1024 * 1024) {
766
- res.header('Content-Type', 'text/plain');
767
- res.status(500).send('File is too big. (Max 600MB)');
768
- return;
769
- }
770
- // Use the mv() method to place the file somewhere on your server
771
- myFile.mv(`${this.settings.tmpPath}/restore.iob`, err => {
772
- if (err) {
773
- res.status(500).send(escapeHtml(typeof err === 'string' ? err : JSON.stringify(err)));
774
- }
775
- else {
776
- res.header('Content-Type', 'text/plain');
777
- res.status(200).send('File uploaded!');
778
- }
779
- });
780
- }
781
- else {
782
- res.header('Content-Type', 'text/plain');
783
- res.status(500).send('File not uploaded');
784
- }
785
- });
786
- }
787
- if (!(0, node_fs_1.existsSync)(this.wwwDir)) {
788
- this.server.app.use('/', (_req, res) => {
789
- res.header('Content-Type', 'text/plain');
790
- res.status(404).send('This adapter cannot be installed directly from GitHub.<br>You must install it from npm.<br>Write for that <i>"npm install iobroker.admin"</i> in according directory.');
791
- });
792
- }
793
- else {
794
- this.server.app.get('/empty.html', (_req, res) => {
795
- res.status(200).send('');
796
- });
797
- this.server.app.get('/index.html', (_req, res) => {
798
- this.indexHTML = this.indexHTML || this.prepareIndex();
799
- res.header('Content-Type', 'text/html');
800
- res.status(200).send(this.indexHTML);
801
- });
802
- this.server.app.get('/', (_req, res) => {
803
- this.indexHTML = this.indexHTML || this.prepareIndex();
804
- res.header('Content-Type', 'text/html');
805
- res.status(200).send(this.indexHTML);
806
- });
807
- this.server.app.use('/', express.static(this.wwwDir, appOptions));
808
- }
809
- // reverse proxy with url rewrite for couchdb attachments in <adapter-name>.admin
810
- this.server.app.use('/adapter/', (req, res) => {
811
- // Example: /example/?0&attr=1
812
- let url;
813
- try {
814
- url = decodeURIComponent(req.url);
815
- }
816
- catch {
817
- // ignore
818
- url = req.url;
819
- }
820
- // sanitize url
821
- // add index.html
822
- url = url.replace(/\/($|\?|#)/, '/index.html$1');
823
- // Read config files for admin from /adapters/admin/admin/...
824
- if (url.startsWith(`/${this.adapter.name}/`)) {
825
- url = url.replace(`/${this.adapter.name}/`, this.dirName);
826
- // important: Linux does not normalize "\" but readFile accepts it as '/'
827
- url = (0, node_path_1.normalize)(url.replace(/\?.*/, '').replace(/\\/g, '/')).replace(/\\/g, '/');
828
- if (url.startsWith(this.dirName)) {
829
- try {
830
- if ((0, node_fs_1.existsSync)(url)) {
831
- res.contentType((0, mime_1.getType)(url) || 'text/javascript');
832
- (0, node_fs_1.createReadStream)(url).pipe(res);
833
- }
834
- else {
835
- res.status(404).send(get404Page(`File not found`));
836
- }
837
- }
838
- catch (e) {
839
- res.status(404).send(get404Page(`File not found: ${escapeHtml(JSON.stringify(e))}`));
840
- }
841
- }
842
- else {
843
- res.status(404).send(get404Page(`File ${escapeHtml(url)} not found`));
844
- }
845
- return;
846
- }
847
- const parts = url.split('/');
848
- // Skip first /
849
- parts.shift();
850
- // Get ID
851
- const adapterName = parts.shift();
852
- const id = `${adapterName}.admin`;
853
- url = parts.join('/');
854
- const pos = url.indexOf('?');
855
- let _instance = 0;
856
- if (pos !== -1) {
857
- _instance = parseInt(url.substring(pos + 1), 10) || 0;
858
- url = url.substring(0, pos);
859
- }
860
- if (this.settings.accessLimit) {
861
- if (url === 'index.html' || url === 'index_m.html') {
862
- const anyConfig = this.settings.accessAllowedConfigs.includes(`${adapterName}.${_instance}`);
863
- if (!anyConfig) {
864
- res.contentType('text/html');
865
- res.status(403).send('You are not allowed to access this page');
866
- return;
867
- }
868
- }
869
- if (url === 'tab.html' || url === 'tab_m.html') {
870
- const anyTabs = this.settings.accessAllowedTabs.includes(`${adapterName}.${_instance}`);
871
- if (!anyTabs) {
872
- res.contentType('text/html');
873
- res.status(403).send('You are not allowed to access this page');
874
- return;
875
- }
876
- }
877
- }
878
- // this.adapter.readFile is sanitized
879
- this.adapter.readFile(id, url, null, (err, buffer, mimeType) => {
880
- if (!buffer || err) {
881
- res.contentType('text/html');
882
- res.status(404).send(get404Page(`File ${escapeHtml(url)} not found`));
883
- }
884
- else {
885
- if (mimeType) {
886
- res.contentType(mimeType);
887
- }
888
- else {
889
- try {
890
- const _mimeType = (0, mime_1.getType)(url);
891
- res.contentType(_mimeType || 'text/javascript');
892
- }
893
- catch {
894
- res.contentType('text/javascript');
895
- }
896
- }
897
- res.send(buffer);
898
- }
899
- });
900
- });
901
- // reverse proxy with url rewrite for couchdb attachments in <adapter-name>
902
- this.server.app.use('/files/', async (req, res) => {
903
- // Example: /vis.0/main/img/image.png
904
- let url;
905
- try {
906
- url = decodeURIComponent(req.url);
907
- }
908
- catch {
909
- // ignore
910
- url = req.url;
911
- }
912
- // add index.html
913
- url = url.replace(/\/($|\?|#)/, '/index.html$1');
914
- const parts = url.split('/');
915
- // Skip first /files
916
- parts.shift();
917
- // Get ID
918
- const adapterName = parts.shift();
919
- url = parts.join('/');
920
- const pos = url.indexOf('?');
921
- let _instance = 0;
922
- if (pos !== -1) {
923
- _instance = parseInt(url.substring(pos + 1), 10) || 0;
924
- url = url.substring(0, pos);
925
- }
926
- if (this.settings.accessLimit) {
927
- if (url === 'index.html' || url === 'index_m.html') {
928
- const anyConfig = this.settings.accessAllowedConfigs.includes(`${adapterName}.${_instance}`);
929
- if (!anyConfig) {
930
- res.contentType('text/html');
931
- res.status(403).send('You are not allowed to access this page');
932
- return;
933
- }
934
- }
935
- if (url === 'tab.html' || url === 'tab_m.html') {
936
- const anyTabs = this.settings.accessAllowedTabs.includes(`${adapterName}.${_instance}`);
937
- if (!anyTabs) {
938
- res.contentType('text/html');
939
- res.status(403).send('You are not allowed to access this page');
940
- return;
941
- }
942
- }
943
- }
944
- try {
945
- if (await this.adapter.fileExists(adapterName, url)) {
946
- const { mimeType, file } = await this.adapter.readFileAsync(adapterName, url);
947
- // special case for svg stored into logo.png
948
- if (url.endsWith('.png') && file.length < 30000) {
949
- const str = file.toString('utf8');
950
- if (str.startsWith('<svg') || str.startsWith('<xml') || str.startsWith('<?xml')) {
951
- // it is svg
952
- res.contentType('image/svg+xml');
953
- res.send(str);
954
- return;
955
- }
956
- res.contentType('image/png');
957
- }
958
- else {
959
- res.contentType(mimeType || 'text/javascript');
960
- }
961
- if (adapterName === this.adapter.namespace && url.startsWith('zip/')) {
962
- // special files, that can be read-only one time
963
- this.adapter.unlink(adapterName, url, () => { });
964
- }
965
- res.send(file);
966
- }
967
- else {
968
- const filesOfDir = await readFolderRecursive(this.adapter, adapterName, url);
969
- const archive = archiver('zip', {
970
- zlib: { level: 9 },
971
- });
972
- for (const file of filesOfDir) {
973
- archive.append(file.file, { name: file.name });
974
- }
975
- const zip = [];
976
- archive.on('data', chunk => zip.push(chunk));
977
- await archive.finalize();
978
- res.contentType('application/zip');
979
- res.send(Buffer.concat(zip));
980
- }
981
- }
982
- catch (e) {
983
- this.adapter.log.warn(`Cannot read file ("${adapterName}"/"${url}"): ${e.message}`);
984
- res.contentType('text/html');
985
- res.status(404).send(get404Page(`File ${escapeHtml(url)} not found`));
986
- }
987
- });
988
- // handler for oauth2 redirects
989
- this.server.app.use('/oauth2_callbacks/', (req, res) => {
990
- // extract instance from "http://localhost:8081/oauth2_callbacks/netatmo.0/?state=ABC&code=CDE"
991
- const [_instance, params] = req.url.split('?');
992
- const instance = _instance.replace(/^\//, '').replace(/\/$/, ''); // remove last and first "/" in "/netatmo.0/"
993
- const query = {};
994
- params.split('&').forEach(param => {
995
- const [key, value] = param.split('=');
996
- query[key] = value === undefined ? true : value;
997
- if (Number.isFinite(query[key])) {
998
- query[key] = parseFloat(query[key]);
999
- }
1000
- else if (query[key] === 'true') {
1001
- query[key] = true;
1002
- }
1003
- else if (query[key] === 'false') {
1004
- query[key] = false;
1005
- }
1006
- });
1007
- if (query.timeout > 30_000) {
1008
- query.timeout = 30_000;
1009
- }
1010
- let timeout = setTimeout(() => {
1011
- if (timeout) {
1012
- timeout = null;
1013
- let text = (0, node_fs_1.readFileSync)(`${this.baseDir}/public/oauthError.html`).toString('utf8');
1014
- text = text.replace('%LANGUAGE%', this.systemLanguage);
1015
- text = text.replace('%ERROR%', 'TIMEOUT');
1016
- res.setHeader('Content-Type', 'text/html');
1017
- res.status(408).send(text);
1018
- }
1019
- }, query.timeout || 5_000);
1020
- this.adapter.sendTo(instance, 'oauth2Callback', query, result => {
1021
- const _result = result;
1022
- if (timeout) {
1023
- clearTimeout(timeout);
1024
- timeout = null;
1025
- if (_result?.error) {
1026
- let text = (0, node_fs_1.readFileSync)(`${this.baseDir}/public/oauthError.html`).toString('utf8');
1027
- text = text.replace('%LANGUAGE%', this.systemLanguage);
1028
- text = text.replace('%ERROR%', _result.error);
1029
- res.setHeader('Content-Type', 'text/html');
1030
- res.status(500).send(text);
1031
- }
1032
- else {
1033
- let text = (0, node_fs_1.readFileSync)(`${this.baseDir}/public/oauthSuccess.html`).toString('utf8');
1034
- text = text.replace('%LANGUAGE%', this.systemLanguage);
1035
- text = text.replace('%MESSAGE%', _result?.result || '');
1036
- res.setHeader('Content-Type', 'text/html');
1037
- res.status(200).send(text);
1038
- }
1039
- }
1040
- });
1041
- });
1042
- // 404 handler
1043
- this.server.app.use((req, res) => {
1044
- res.status(404).send(get404Page(`File ${escapeHtml(req.url)} not found`));
1045
- });
1046
- try {
1047
- const webserver = new webserver_1.WebServer({
1048
- app: this.server.app,
1049
- adapter: this.adapter,
1050
- secure: this.settings.secure,
1051
- });
1052
- this.server.server = await webserver.init();
1053
- }
1054
- catch (err) {
1055
- this.adapter.log.error(`Cannot create web-server: ${err}`);
1056
- if (this.adapter.terminate) {
1057
- this.adapter.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1058
- }
1059
- else {
1060
- process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1061
- }
1062
- return;
1063
- }
1064
- if (!this.server.server) {
1065
- this.adapter.log.error(`Cannot create web-server`);
1066
- if (this.adapter.terminate) {
1067
- this.adapter.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1068
- }
1069
- else {
1070
- process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1071
- }
1072
- return;
1073
- }
1074
- this.server.server.__server = this.server;
1075
- }
1076
- else {
1077
- this.adapter.log.error('port missing');
1078
- if (this.adapter.terminate) {
1079
- this.adapter.terminate('port missing', adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1080
- }
1081
- else {
1082
- process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1083
- }
1084
- }
1085
- void this.adapter
1086
- .getForeignObjectAsync('system.config')
1087
- .then(obj => {
1088
- this.systemConfig = obj || {};
1089
- this.systemConfig.native = this.systemConfig.native || {};
1090
- this.systemConfig.native.vendor = this.systemConfig.native.vendor || {};
1091
- this.systemConfig.native.vendor.admin = this.systemConfig.native.vendor.admin || {};
1092
- this.systemConfig.native.vendor.admin.login = this.systemConfig.native.vendor.admin.login || {};
1093
- return this.adapter.getForeignObjectAsync('system.meta.uuid');
1094
- })
1095
- .then(obj => {
1096
- if (obj && obj.native) {
1097
- uuid = obj.native.uuid;
1098
- }
1099
- if (this.server.server) {
1100
- let serverListening = false;
1101
- let serverPort;
1102
- this.server.server.on('error', e => {
1103
- if (e.toString().includes('EACCES') && serverPort <= 1024) {
1104
- this.adapter.log.error(`node.js process has no rights to start server on the port ${serverPort}.\n` +
1105
- `Do you know that on linux you need special permissions for ports under 1024?\n` +
1106
- `You can call in shell following scrip to allow it for node.js: "iobroker fix"`);
1107
- }
1108
- else {
1109
- this.adapter.log.error(`Cannot start server on ${this.settings.bind || '0.0.0.0'}:${serverPort}: ${e.toString()}`);
1110
- }
1111
- if (!serverListening) {
1112
- if (this.adapter.terminate) {
1113
- this.adapter.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1114
- }
1115
- else {
1116
- process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION);
1117
- }
1118
- }
1119
- });
1120
- this.settings.port = parseInt(this.settings.port, 10) || 8081;
1121
- serverPort = this.settings.port;
1122
- this.adapter.getPort(this.settings.port, !this.settings.bind || this.settings.bind === '0.0.0.0'
1123
- ? undefined
1124
- : this.settings.bind || undefined, port => {
1125
- serverPort = port;
1126
- // Start the web server
1127
- this.server.server.listen(port, !this.settings.bind || this.settings.bind === '0.0.0.0'
1128
- ? undefined
1129
- : this.settings.bind || undefined, () => {
1130
- void this.adapter.setState('info.connection', true, true);
1131
- serverListening = true;
1132
- this.adapter.log.info(`http${this.settings.secure ? 's' : ''} server listening on port ${port}`);
1133
- this.adapter.log.info(`Use link "http${this.settings.secure ? 's' : ''}://127.0.0.1:${port}" to configure.`);
1134
- if (!this.adapter.config.doNotCheckPublicIP && !this.adapter.config.auth) {
1135
- this.checkTimeout = this.adapter.setTimeout(async () => {
1136
- this.checkTimeout = null;
1137
- try {
1138
- await (0, webserver_1.checkPublicIP)(this.settings.port, 'ioBroker', '/iobroker_check.html');
1139
- }
1140
- catch (e) {
1141
- // this supported first from js-controller 5.0.
1142
- this.adapter.sendToHost(`system.host.${this.adapter.host}`, 'addNotification', {
1143
- scope: 'system',
1144
- category: 'securityIssues',
1145
- message: 'Your admin instance is accessible from the internet without any protection. ' +
1146
- 'Please enable authentication or disable the access from the internet.',
1147
- instance: `system.adapter.${this.adapter.namespace}`,
1148
- }, ( /* result */) => {
1149
- /* ignore */
1150
- });
1151
- this.adapter.log.error(e.toString());
1152
- }
1153
- }, 1000);
1154
- }
1155
- });
1156
- if (typeof this.onReady === 'function') {
1157
- this.onReady(this.server.server, this.store, this.adapter);
1158
- }
1159
- });
1160
- }
1161
- });
1162
- }
1163
- }
1164
- exports.default = Web;
1165
- //# sourceMappingURL=web.js.map