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.
- package/README.md +44 -11
- package/admin/admin.png +0 -0
- package/admin/i18n/de.json +1 -1
- package/admin/i18n/es.json +1 -1
- package/admin/i18n/fr.json +1 -1
- package/admin/i18n/it.json +1 -1
- package/admin/i18n/nl.json +1 -1
- package/admin/i18n/pl.json +1 -1
- package/admin/i18n/pt.json +1 -1
- package/admin/i18n/ru.json +1 -1
- package/admin/i18n/uk.json +1 -1
- package/admin/i18n/zh-cn.json +1 -1
- package/admin/jsonConfig.json5 +1 -2
- package/adminWww/assets/{AdapterUpdateDialog-Bgswiqs9.js → AdapterUpdateDialog-BQUTMe7v.js} +1 -1
- package/adminWww/assets/Adapters-KuMqwfzU.js +9 -0
- package/adminWww/assets/Config-LH40Fzyp.js +1 -0
- package/adminWww/assets/CustomModal-UQ6ICKdg.js +1 -0
- package/adminWww/assets/CustomTab-CE2qvngn.js +1 -0
- package/adminWww/assets/DefaultPropsProvider-BxjOYH1h.js +30 -0
- package/adminWww/assets/EasyMode-XXQKZmWs.js +1 -0
- package/adminWww/assets/{Enums-8m252lzC.js → Enums-Ddmi-fYo.js} +3 -3
- package/adminWww/assets/{Fields-lI3YkIzQ.js → Fields-CQNdUYkd.js} +1 -1
- package/adminWww/assets/{Files-fQpSPYWI.js → Files-DgOiCRiN.js} +2 -2
- package/adminWww/assets/FilledInput-B9JSi0Ta.js +2 -0
- package/adminWww/assets/Hosts-BgDpLhdl.js +121 -0
- package/adminWww/assets/Instances-DikJwBl0.js +2 -0
- package/adminWww/assets/Intro-vxIHtYzr.js +2 -0
- package/adminWww/assets/Logs-DZUWTbJn.js +1 -0
- package/adminWww/assets/Objects-Dm1_4aGR.js +60 -0
- package/adminWww/assets/{State-nZ6sjU9_.js → State-CrcWiBdx.js} +1 -1
- package/adminWww/assets/TextField-j-uewRwI.js +138 -0
- package/adminWww/assets/ThemeProvider-DHntAcOp.js +29 -0
- package/adminWww/assets/Users-GcWjwa7A.js +1 -0
- package/adminWww/assets/ace-1DqSbvTN.js +985 -0
- package/adminWww/assets/bootstrap-B3ZI_-0g.js +2024 -0
- package/adminWww/assets/{createSvgIcon-DuKI6CvW.js → createSvgIcon-Ds12z7B3.js} +1 -1
- package/adminWww/assets/emotion-cache.browser.esm-BgJfNZJN.js +1 -0
- package/adminWww/assets/emotion-react.browser.esm-CBtpmfsG.js +8 -0
- package/adminWww/assets/emotion-serialize.esm-Q4o_CgeF.js +1 -0
- package/adminWww/assets/emotion-styled.browser.esm-BeD8nBzN.js +1 -0
- package/adminWww/assets/emotion-use-insertion-effect-with-fallbacks.browser.esm-BIy6Leuu.js +1 -0
- package/adminWww/assets/geosearch.module-B6h9BuIR.js +1 -0
- package/adminWww/assets/hostInit-fzxL8JAx.js +1 -1
- package/adminWww/assets/iconBase-BAOnvyiQ.js +1 -0
- package/adminWww/assets/{index-BP5kWU3L.js → index-BOnh7es7.js} +1 -1
- package/adminWww/assets/{index-BmWdDCX_.js → index-BSfwmoAE.js} +4 -4
- package/adminWww/assets/{index-gHyOgbGJ.js → index-BVgYaw7t.js} +1 -1
- package/adminWww/assets/{index-D7Rlw-5R.js → index-C9i3HTsG.js} +1 -1
- package/adminWww/assets/{index-DH1kmwK1.js → index-CELb-gCK.js} +2 -2
- package/adminWww/assets/{index-HOidq_rM.js → index-CU6eZCem.js} +1 -1
- package/adminWww/assets/{index-Bsv_DXxB.js → index-DZ0MncEz.js} +2 -2
- package/adminWww/assets/index-Dg-s3k0-.js +10 -0
- package/adminWww/assets/index-tS5iCAF7.js +55 -0
- package/adminWww/assets/iobroker_admin__loadShare___mf_0_emotion_mf_1_react__loadShare__-CXogaIXO.js +1 -0
- package/adminWww/assets/{iobroker_admin__loadShare__leaflet__loadShare__-B1OZ7tj-.js → iobroker_admin__loadShare__leaflet__loadShare__-DIpcqFbm.js} +1 -1
- package/adminWww/assets/iobroker_admin__loadShare__prop_mf_2_types__loadShare__-pDONiJUe.js +1 -0
- package/adminWww/assets/{iobroker_admin__loadShare__react__loadShare__-DtlEM_52.js → iobroker_admin__loadShare__react__loadShare__-CuzHmAOj.js} +1 -1
- package/adminWww/assets/iobroker_admin__mf_v__runtimeInit__mf_v__-CHE4rLsT.js +5 -0
- package/adminWww/assets/leaflet-src-BRBM-1HK.js +4 -0
- package/adminWww/assets/{sentry-CNy_SQq1.js → sentry-B6yHtsbd.js} +1 -1
- package/adminWww/assets/{zh-cn-DZTx1vAh.js → zh-cn-heTcsLnU.js} +15 -15
- package/adminWww/img/admin.png +0 -0
- package/adminWww/index.html +2 -2
- package/adminWww/mf-manifest.json +1 -1
- package/adminWww/remoteEntry.js +2 -2
- package/adminWww/static/js/worker-javascript.js +1 -1
- package/adminWww/static/js/worker-yaml.js +1 -1
- package/build/i18n/de.json +18 -0
- package/build/i18n/es.json +18 -0
- package/build/i18n/fr.json +18 -0
- package/build/i18n/it.json +18 -0
- package/build/i18n/nl.json +18 -0
- package/build/i18n/pl.json +18 -0
- package/build/i18n/pt.json +18 -0
- package/build/i18n/ru.json +18 -0
- package/build/i18n/uk.json +18 -0
- package/build/i18n/zh-cn.json +18 -0
- package/build/lib/DockerManager.js +1315 -0
- package/build/lib/DockerManager.js.map +1 -0
- package/build/lib/dockerManager.types.js +3 -0
- package/build/lib/dockerManager.types.js.map +1 -0
- package/{build-backend → build}/lib/testPassword.js +1 -1
- package/{build-backend → build}/lib/testPassword.js.map +1 -1
- package/{build-backend/src → build}/lib/translations.js +16 -1
- package/build/lib/translations.js.map +1 -0
- package/{build-backend → build}/main.js +58 -5
- package/build/main.js.map +1 -0
- package/io-package.json +27 -27
- package/package.json +13 -12
- package/adminWww/assets/Adapters-yQiEeoh8.js +0 -7
- package/adminWww/assets/Config-C1Gjt8DC.js +0 -1
- package/adminWww/assets/CustomModal-CgEtkfCG.js +0 -1
- package/adminWww/assets/CustomTab-CwioFUor.js +0 -1
- package/adminWww/assets/DefaultPropsProvider-DxsXuIyE.js +0 -30
- package/adminWww/assets/EasyMode-CN-reJVI.js +0 -1
- package/adminWww/assets/FilledInput-CKswrNd5.js +0 -2
- package/adminWww/assets/Hosts-BPdUlXV5.js +0 -121
- package/adminWww/assets/Instances-BStmfz4r.js +0 -2
- package/adminWww/assets/Intro-Dr1-aqO9.js +0 -2
- package/adminWww/assets/Logs-xCCrWV30.js +0 -1
- package/adminWww/assets/Objects-BF8brXaf.js +0 -60
- package/adminWww/assets/Tabs-BJ1rL35Z.js +0 -138
- package/adminWww/assets/Users-B0iasrSG.js +0 -1
- package/adminWww/assets/ace-CFIUQz8j.js +0 -970
- package/adminWww/assets/blueGrey-D-KQaGde.js +0 -29
- package/adminWww/assets/bootstrap-DDrI3lVl.js +0 -2036
- package/adminWww/assets/emotion-cache.browser.esm-B8BFze5o.js +0 -1
- package/adminWww/assets/emotion-react.browser.esm-DApPKwaj.js +0 -8
- package/adminWww/assets/emotion-serialize.esm-BUa21YfQ.js +0 -1
- package/adminWww/assets/emotion-styled.browser.esm-Dc05hmeu.js +0 -1
- package/adminWww/assets/emotion-utils.browser.esm-DOjH6Olm.js +0 -1
- package/adminWww/assets/geosearch.module-CWfRtxrN.js +0 -1
- package/adminWww/assets/iconBase-CgxZ5MMC.js +0 -1
- package/adminWww/assets/index-BjVqnwqt.js +0 -10
- package/adminWww/assets/index-RvHSqeMD.js +0 -55
- package/adminWww/assets/iobroker_admin__loadShare___mf_0_emotion_mf_1_react__loadShare__-gw2iqPBS.js +0 -1
- package/adminWww/assets/iobroker_admin__loadShare__prop_mf_2_types__loadShare__-BGyJCibC.js +0 -1
- package/adminWww/assets/iobroker_admin__mf_v__runtimeInit__mf_v__-VDoBF19b.js +0 -10
- package/adminWww/assets/leaflet-src-A2ZHl6nF.js +0 -4
- package/build-backend/i18n/de.json +0 -18
- package/build-backend/i18n/es.json +0 -18
- package/build-backend/i18n/fr.json +0 -18
- package/build-backend/i18n/it.json +0 -18
- package/build-backend/i18n/nl.json +0 -18
- package/build-backend/i18n/pl.json +0 -18
- package/build-backend/i18n/pt.json +0 -18
- package/build-backend/i18n/ru.json +0 -18
- package/build-backend/i18n/uk.json +0 -18
- package/build-backend/i18n/zh-cn.json +0 -18
- package/build-backend/lib/translations.js +0 -27
- package/build-backend/lib/translations.js.map +0 -1
- package/build-backend/main.js.map +0 -1
- package/build-backend/src/lib/checkLinuxPass.js +0 -280
- package/build-backend/src/lib/checkLinuxPass.js.map +0 -1
- package/build-backend/src/lib/testPassword.js +0 -58
- package/build-backend/src/lib/testPassword.js.map +0 -1
- package/build-backend/src/lib/translations.js.map +0 -1
- package/build-backend/src/lib/utils.js +0 -344
- package/build-backend/src/lib/utils.js.map +0 -1
- package/build-backend/src/lib/web.js +0 -1165
- package/build-backend/src/lib/web.js.map +0 -1
- package/build-backend/src/main.js +0 -1704
- package/build-backend/src/main.js.map +0 -1
- package/build-backend/src-admin/src/components/Adapters/Utils.js +0 -39
- package/build-backend/src-admin/src/components/Adapters/Utils.js.map +0 -1
- /package/{build-backend → build}/i18n/en.json +0 -0
- /package/{build-backend → build}/lib/checkLinuxPass.js +0 -0
- /package/{build-backend → build}/lib/checkLinuxPass.js.map +0 -0
- /package/{build-backend → build}/lib/utils.js +0 -0
- /package/{build-backend → build}/lib/utils.js.map +0 -0
- /package/{build-backend → build}/lib/web.js +0 -0
- /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 = '"';
|
|
48
|
-
break;
|
|
49
|
-
case 38: // &
|
|
50
|
-
escape = '&';
|
|
51
|
-
break;
|
|
52
|
-
case 39: // '
|
|
53
|
-
escape = ''';
|
|
54
|
-
break;
|
|
55
|
-
case 60: // <
|
|
56
|
-
escape = '<';
|
|
57
|
-
break;
|
|
58
|
-
case 62: // >
|
|
59
|
-
escape = '>';
|
|
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
|