iobroker.mywebui 1.37.28 → 1.37.29
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/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "mywebui",
|
|
4
|
-
"version": "1.37.
|
|
4
|
+
"version": "1.37.29",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "mywebui",
|
|
7
7
|
"de": "mywebui",
|
|
@@ -29,6 +29,13 @@
|
|
|
29
29
|
"zh-cn": "使用万维网传送器的高锰用户接口"
|
|
30
30
|
},
|
|
31
31
|
"news": {
|
|
32
|
+
"1.37.29": {
|
|
33
|
+
"en": "security fix: lang sync via session-verified HTTP endpoint instead of sendTo — username no longer trusted from frontend",
|
|
34
|
+
"az": "təhlükəsizlik düzəlişi: dil sinxronizasiyası session-yoxlamalı HTTP endpoint ilə — username artıq frontend-dən alınmır",
|
|
35
|
+
"tr": "güvenlik düzeltmesi: dil senkronizasyonu session doğrulamalı HTTP endpoint ile — username artık frontend'den gelmiyor",
|
|
36
|
+
"ru": "исправление безопасности: синхронизация языка через HTTP endpoint с проверкой сессии — username больше не берётся из frontend",
|
|
37
|
+
"de": "Sicherheitsfix: Sprachsync über session-verifizierten HTTP-Endpoint — username kommt nicht mehr vom Frontend"
|
|
38
|
+
},
|
|
32
39
|
"1.37.28": {
|
|
33
40
|
"en": "fix: rename webExtension.js to .cjs to avoid ES module conflict",
|
|
34
41
|
"az": "düzəliş: ES module konflikti aradan qaldırmaq üçün webExtension.js → .cjs",
|
package/package.json
CHANGED
package/webExtension.cjs
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
|
|
6
|
+
function mapGrafanaLanguage(lang) {
|
|
7
|
+
switch (lang) {
|
|
8
|
+
case 'az': return 'az-AZ';
|
|
9
|
+
case 'ru': return 'ru-RU';
|
|
10
|
+
case 'en': return 'en-US';
|
|
11
|
+
case 'de': return 'de-DE';
|
|
12
|
+
case 'tr': return 'tr-TR';
|
|
13
|
+
default: return 'en-US';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function httpPatch(urlStr, headers, body) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const parsed = new URL(urlStr);
|
|
20
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
21
|
+
const req = lib.request(parsed, { method: 'PATCH', headers }, (res) => {
|
|
22
|
+
let data = '';
|
|
23
|
+
res.on('data', c => data += c);
|
|
24
|
+
res.on('end', () => resolve({ status: res.statusCode, body: data }));
|
|
25
|
+
});
|
|
26
|
+
req.on('error', reject);
|
|
27
|
+
req.setTimeout(3000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
28
|
+
req.write(body);
|
|
29
|
+
req.end();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
3
33
|
class MywebuiWebExtension {
|
|
4
34
|
/**
|
|
5
35
|
* @param {import('http').Server} server
|
|
@@ -10,25 +40,66 @@ class MywebuiWebExtension {
|
|
|
10
40
|
*/
|
|
11
41
|
constructor(server, settings, adapter, config, app) {
|
|
12
42
|
this._adapter = adapter;
|
|
43
|
+
const native = config?.native || {};
|
|
13
44
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!ssoEnabled) {
|
|
17
|
-
adapter.log.debug('[mywebui/WebExt] SSO disabled — /mywebui-auth-check not registered');
|
|
45
|
+
if (!native.grafanaEnabled) {
|
|
46
|
+
adapter.log.debug('[mywebui/WebExt] Grafana disabled — no endpoints registered');
|
|
18
47
|
return;
|
|
19
48
|
}
|
|
20
49
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
const grafanaUrl = (native.grafanaUrl || 'http://localhost:3000').replace(/\/$/, '');
|
|
51
|
+
|
|
52
|
+
// ── SSO auth check (nginx auth_request) ──────────────────────────────
|
|
53
|
+
if (native.grafanaSsoEnabled) {
|
|
54
|
+
app.get('/mywebui-auth-check', (req, res) => {
|
|
55
|
+
const username = req.session?.passport?.user;
|
|
56
|
+
if (!username) { res.status(401).end(); return; }
|
|
57
|
+
res.setHeader('X-User', username);
|
|
58
|
+
res.status(200).end();
|
|
59
|
+
});
|
|
60
|
+
adapter.log.info('[mywebui/WebExt] /mywebui-auth-check registered for nginx SSO');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Language sync (session-verified, no username from frontend) ──────
|
|
64
|
+
if (native.grafanaLangSyncEnabled) {
|
|
65
|
+
app.post('/mywebui-grafana-lang', async (req, res) => {
|
|
66
|
+
const username = req.session?.passport?.user;
|
|
67
|
+
if (!username) { res.status(401).json({ error: 'not authenticated' }); return; }
|
|
30
68
|
|
|
31
|
-
|
|
69
|
+
let body = '';
|
|
70
|
+
req.on('data', c => body += c);
|
|
71
|
+
await new Promise(r => req.on('end', r));
|
|
72
|
+
|
|
73
|
+
let lang;
|
|
74
|
+
try { lang = JSON.parse(body).lang; } catch (e) { /**/ }
|
|
75
|
+
if (!lang) { res.status(400).json({ error: 'missing lang' }); return; }
|
|
76
|
+
|
|
77
|
+
const grafanaLang = mapGrafanaLanguage(lang);
|
|
78
|
+
const payload = JSON.stringify({ language: grafanaLang });
|
|
79
|
+
try {
|
|
80
|
+
const r = await httpPatch(
|
|
81
|
+
grafanaUrl + '/api/user/preferences',
|
|
82
|
+
{
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
85
|
+
'X-WEBAUTH-USER': username
|
|
86
|
+
},
|
|
87
|
+
payload
|
|
88
|
+
);
|
|
89
|
+
if (r.status === 200) {
|
|
90
|
+
adapter.log.debug(`[mywebui/WebExt] lang synced: ${username} → ${grafanaLang}`);
|
|
91
|
+
res.json({ success: true });
|
|
92
|
+
} else {
|
|
93
|
+
adapter.log.warn(`[mywebui/WebExt] Grafana ${r.status}: ${r.body}`);
|
|
94
|
+
res.status(502).json({ error: `Grafana HTTP ${r.status}` });
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
adapter.log.warn(`[mywebui/WebExt] lang sync error: ${e.message}`);
|
|
98
|
+
res.status(500).json({ error: e.message });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
adapter.log.info('[mywebui/WebExt] /mywebui-grafana-lang registered (session-verified)');
|
|
102
|
+
}
|
|
32
103
|
}
|
|
33
104
|
}
|
|
34
105
|
|
|
@@ -497,9 +497,12 @@ export class IobrokerHandler {
|
|
|
497
497
|
localStorage.setItem('webui-language', lang);
|
|
498
498
|
this.languageChanged.emit(lang);
|
|
499
499
|
window.dispatchEvent(new CustomEvent('languageChanged', { detail: lang }));
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
fetch('/mywebui-grafana-lang', {
|
|
501
|
+
method: 'POST',
|
|
502
|
+
headers: { 'Content-Type': 'application/json' },
|
|
503
|
+
credentials: 'include',
|
|
504
|
+
body: JSON.stringify({ lang })
|
|
505
|
+
}).catch(() => {});
|
|
503
506
|
}
|
|
504
507
|
/** Translation helper - hər yerdən istifadə üçün
|
|
505
508
|
* iobrokerHandler.t('pid.op')
|